diff --git a/api/consts/consts.go b/api/consts/consts.go index 8a20e9c2..04fe0472 100644 --- a/api/consts/consts.go +++ b/api/consts/consts.go @@ -35,27 +35,28 @@ const ( // apollo_config const ( - MaxPswdWrongTimesKey = "max_pswd_wrong_times" - MaxVeriCodeValidDurationKey = "max_veri_code_valid_duration" - IosKey = "ios" - AndroidKey = "android" - AccountInitKey = "account_init" - TagNumKey = "tag_num" - PlatformNumKey = "platform_num" - SupportWxIdNumKey = "support_wx_id_num" - MaxDailyVeriCodeSendTimesKey = "max_daily_veri_code_send_times" - ImageIdForUploadFail = "image_id_for_upload_fail" - VideoIdForUploadFail = "video_id_for_upload_fail" - RestrictedVisitorKey = "restricted_visitor" - MaxDailyMomentCreateTimesKey = "max_daily_moment_create_times" - DefaultMomentTextKey = "default_moment_text" - MaxDailyZoneMomentCreateTimesKey = "max_daily_zone_moment_create_times" - ReferentialZoneMomentKey = "referential_zone_moment" - IsMomentImageEncryptEnabledKey = "is_moment_image_encrypt_enabled" - RestrictedVisitorMomentKey = "restricted_visitor_moment" - AppConfigReflectKey = "app_config_reflect" - ZoneVIPConfigKey = "zone_vip_config" - StreamerScoreFormulaKey = "streamer_score_formula" + MaxPswdWrongTimesKey = "max_pswd_wrong_times" + MaxVeriCodeValidDurationKey = "max_veri_code_valid_duration" + IosKey = "ios" + AndroidKey = "android" + AccountInitKey = "account_init" + TagNumKey = "tag_num" + PlatformNumKey = "platform_num" + SupportWxIdNumKey = "support_wx_id_num" + MaxDailyVeriCodeSendTimesKey = "max_daily_veri_code_send_times" + ImageIdForUploadFail = "image_id_for_upload_fail" + VideoIdForUploadFail = "video_id_for_upload_fail" + RestrictedVisitorKey = "restricted_visitor" + MaxDailyMomentCreateTimesKey = "max_daily_moment_create_times" + DefaultMomentTextKey = "default_moment_text" + MaxDailyZoneMomentCreateTimesKey = "max_daily_zone_moment_create_times" + ReferentialZoneMomentKey = "referential_zone_moment" + IsMomentImageEncryptEnabledKey = "is_moment_image_encrypt_enabled" + RestrictedVisitorMomentKey = "restricted_visitor_moment" + AppConfigReflectKey = "app_config_reflect" + ZoneVIPConfigKey = "zone_vip_config" + StreamerScoreFormulaKey = "streamer_score_formula" + HvyogoSingleDistributeChargePercentageKey = "hvyogo_single_distribute_charge_percentage" ) // del_flag diff --git a/api/errcode/errcode.go b/api/errcode/errcode.go index 4da010af..2dd43560 100644 --- a/api/errcode/errcode.go +++ b/api/errcode/errcode.go @@ -215,8 +215,9 @@ var ErrCodeMsgMap = map[ErrCode]string{ ErrCodeWorkerIdSrvFail: "用户职业者id映射表服务错误", ErrCodeWorkerIdNotExist: "用户职业者id映射表不存在", - ErrCodeSingleDistributeHisSrvFail: "慧用工下发打款历史表服务错误", - ErrCodeSingleDistributeHisNotExist: "慧用工下发打款历史表不存在", + ErrCodeSingleDistributeHisSrvFail: "慧用工下发打款历史表服务错误", + ErrCodeSingleDistributeHisNotExist: "慧用工下发打款历史表不存在", + ErrCodeCurrentSingleDistributeNotTerminated: "当前慧用工下发打款申请尚未结束", ErrCodeHvyogoSrvFail: "慧用工接口服务错误", } @@ -519,9 +520,10 @@ const ( ErrCodeWorkerIdNotExist ErrCode = -41002 // 用户职业者id映射表不存在 // SingleDistributeHis: 42xxx - ErrCodeSingleDistributeHisSrvOk ErrCode = ErrCodeOk - ErrCodeSingleDistributeHisSrvFail ErrCode = -42001 // 慧用工下发打款历史表服务错误 - ErrCodeSingleDistributeHisNotExist ErrCode = -42002 // 慧用工下发打款历史表不存在 + ErrCodeSingleDistributeHisSrvOk ErrCode = ErrCodeOk + ErrCodeSingleDistributeHisSrvFail ErrCode = -42001 // 慧用工下发打款历史表服务错误 + ErrCodeSingleDistributeHisNotExist ErrCode = -42002 // 慧用工下发打款历史表不存在 + ErrCodeCurrentSingleDistributeNotTerminated ErrCode = -42003 // 当前慧用工下发打款申请尚未结束 // Media: 60xxx ErrCodeMediaSrvOk ErrCode = ErrCodeOk diff --git a/api/proto/vas/proto/vas.go b/api/proto/vas/proto/vas.go index f282d539..83f62e89 100644 --- a/api/proto/vas/proto/vas.go +++ b/api/proto/vas/proto/vas.go @@ -1,10 +1,11 @@ package proto import ( - "github.com/go-pay/gopay/alipay" "service/api/base" "service/dbstruct" "service/library/payclients/wxpaycli" + + "github.com/go-pay/gopay/alipay" ) // 待添加微信列表 @@ -120,6 +121,13 @@ type WithdrawApplyData struct { TransferResp *alipay.FundTransUniTransferResponse `json:"transfer_resp"` } +// 单侧提现申请 +type UnilaterallyWithdrawApplyReq struct { + base.BaseRequest + Diamonds int64 `json:"diamonds"` // 本次提现的钻石 + Ip string `json:"ip"` +} + // 任意额度提现 var WithdrawAnyDiasMap = map[int64]bool{ 74: true, diff --git a/app/mix/dao/mongo.go b/app/mix/dao/mongo.go index f9b62604..3818c7ce 100644 --- a/app/mix/dao/mongo.go +++ b/app/mix/dao/mongo.go @@ -219,8 +219,9 @@ const ( DBWorkerId = "worker_id" COLWorkerId = "worker_id" - DBSingleDistributeHis = "single_distribute_his" - COLSingleDistributeHis = "single_distribute_his" + DBSingleDistributeHis = "single_distribute_his" + COLSingleDistributeHis = "single_distribute_his" + COLSingleDistributeLock = "single_distribute_lock" ) // 商品表 @@ -547,16 +548,21 @@ func (m *Mongo) getColStreamerScore() *qmgo.Collection { return m.clientMix.Database(DBStreamerScore).Collection(COLStreamerScore) } -// 用户职业者id映射表表 +// 用户职业者id映射表 func (m *Mongo) getColWorkerId() *qmgo.Collection { return m.clientMix.Database(DBWorkerId).Collection(COLWorkerId) } -// 慧用工下发打款历史表表 +// 慧用工下发打款历史表 func (m *Mongo) getColSingleDistributeHis() *qmgo.Collection { return m.clientMix.Database(DBSingleDistributeHis).Collection(COLSingleDistributeHis) } +// 慧用工下发打款锁表 +func (m *Mongo) getColSingleDistributeLock() *qmgo.Collection { + return m.clientMix.Database(DBSingleDistributeHis).Collection(COLSingleDistributeLock) +} + // 商品相关 func (m *Mongo) CreateProduct(ctx *gin.Context, product *dbstruct.Product) error { col := m.getColProduct() @@ -5322,3 +5328,43 @@ func (m *Mongo) GetSingleDistributeHisList(ctx *gin.Context, req *single_distrib } return list, err } + +func (m *Mongo) GetAndUpdateSingleDistributeLock(ctx *gin.Context, mid int64) (singleDistributeLock *dbstruct.SingleDistributeLock, err error) { + col := m.getColSingleDistributeLock() + + change := qmgo.Change{ + Update: qmgo.M{"$inc": qmgo.M{"lock": 1}}, + Upsert: true, + ReturnNew: false, + } + + singleDistributeLockInstance := dbstruct.SingleDistributeLock{} + if err = col.Find(ctx, qmgo.M{"_id": mid}).Apply(change, &singleDistributeLockInstance); err != nil { + logger.Error("change error : %v", err) + return + } + + return &singleDistributeLockInstance, err +} + +func (m *Mongo) ClearSingleDistributeLock(ctx *gin.Context, mid int64) (err error) { + col := m.getColSingleDistributeLock() + + setClause := qmgo.M{ + "lock": int64(0), + } + + change := qmgo.Change{ + Update: qmgo.M{"$set": setClause}, + Upsert: true, + ReturnNew: false, + } + + singleDistributeLockInstance := dbstruct.SingleDistributeLock{} + if err = col.Find(ctx, qmgo.M{"_id": mid}).Apply(change, &singleDistributeLockInstance); err != nil { + logger.Error("change error : %v", err) + return + } + + return nil +} diff --git a/app/mix/dao/mysql.go b/app/mix/dao/mysql.go index e065611d..61479a61 100644 --- a/app/mix/dao/mysql.go +++ b/app/mix/dao/mysql.go @@ -1027,6 +1027,26 @@ func (m *Mysql) GetWithdrawOrdersByMid(ctx *gin.Context, tx *sqlx.Tx, mid, st, e return } +// 从订单号获取提现订单 +func (m *Mysql) GetWithdrawOrderById(ctx *gin.Context, tx *sqlx.Tx, id string) (wOrder *dbstruct.WithdrawOrder, err error) { + wOrder = &dbstruct.WithdrawOrder{} + sqlStr := fmt.Sprintf("select * from %s where id=?", TableWithdrawOrder) + if tx != nil { + err = tx.SelectContext(ctx, wOrder, sqlStr, id) + } else { + db := m.getDBVas() + err = db.SelectContext(ctx, wOrder, sqlStr, id) + } + if err == sql.ErrNoRows { + err = nil + return + } + if err != nil { + return + } + return +} + // 获取指定任务中所有已经执行成功的xxl_job任务 func (m *Mysql) GetSuccessXxlJobLogs(ctx *gin.Context, tx *sqlx.Tx, jobIdsStr string, errorPrefix string) (list []*dbstruct.XxlJobLog, err error) { list = make([]*dbstruct.XxlJobLog, 0) diff --git a/app/mix/service/apiservice.go b/app/mix/service/apiservice.go index dfd225c4..b9b24c9c 100644 --- a/app/mix/service/apiservice.go +++ b/app/mix/service/apiservice.go @@ -5,7 +5,9 @@ import ( "service/api/base" "service/api/consts" "service/api/errcode" + "service/api/errs" "service/api/message/request" + "service/api/message/response" accountproto "service/api/proto/account/proto" account_cancellationproto "service/api/proto/account_cancellation/proto" accountrelationproto "service/api/proto/accountrelation/proto" @@ -42,6 +44,7 @@ import ( "service/library/apollo" "service/library/logger" interceptor "service/library/taginterceptor" + "strconv" "time" "go.mongodb.org/mongo-driver/mongo" @@ -3342,7 +3345,7 @@ func (s *Service) ApiHvyogoSingleDistribute(ctx *gin.Context, req *hvyogoproto.A ec = errcode.ErrCodeHvyogoSrvOk - // 1.查询workerId + // 查询workerId workerId, err := _DefaultWorkerId.OpListByMid(ctx, &workeridproto.OpListByMidReq{ BaseRequest: req.BaseRequest, }) @@ -3353,17 +3356,20 @@ func (s *Service) ApiHvyogoSingleDistribute(ctx *gin.Context, req *hvyogoproto.A } if workerId == nil { logger.Error("No worker_id entity was found") - ec = errcode.ErrCodeWorkerIdNotExist + data = &hvyogoproto.ApiSingleDistributeData{ + StatusCode: response.StatusCodeFail, + StatusText: "用户尚未认证", + } return } - // 2.组装HYG10000002报文,查询详细信息 + // 组装HYG10000002报文,查询详细信息 detailMsg := &request.HYG10000002Req{ HYGBaseReq: &request.HYGBaseReq{}, WorkerId: workerId.GetWorkerId(), } - // 3.调用查询接口 + // 调用查询接口 detailResp, err := DefaultHvyogoService.WorkerFindDetail(detailMsg) if err != nil { logger.Error("DefaultHvyogoService WorkerAgreeState fail, err: %v", err) @@ -3371,7 +3377,21 @@ func (s *Service) ApiHvyogoSingleDistribute(ctx *gin.Context, req *hvyogoproto.A return } - // 4.组装HYG10010001报文,准备下发打款 + // 扣除手续费 + distributeAmount, err := strconv.Atoi(req.DistributeAmount) + if err != nil || distributeAmount <= 0 { + err = fmt.Errorf("下发金额必须为大于0的整数!") + return + } + chargePercentage, err := apollo.GetFloat64Value(consts.HvyogoSingleDistributeChargePercentageKey, apollo.ApolloOpts().SetNamespace("application")) + if err != nil { + logger.Error("Apollo read failed : %v", err) + ec, err = errcode.ErrCodeApolloReadFail, nil + return + } + finalDistributeAmount := int(float64(distributeAmount) * (1 - chargePercentage)) + + // 组装HYG10010001报文,准备下发打款 msg := &request.HYG10010001Req{ HYGBaseReq: &request.HYGBaseReq{}, WorkerName: detailResp.WorkerName, @@ -3380,54 +3400,80 @@ func (s *Service) ApiHvyogoSingleDistribute(ctx *gin.Context, req *hvyogoproto.A WorkerType: detailResp.CertificateType, IdNumber: detailResp.IdentNo, WorkerMobile: detailResp.WorkerMobile, - DistributeAmount: req.DistributeAmount, + DistributeAmount: fmt.Sprint(finalDistributeAmount), } - // 5.若上送了别的渠道,则设置参数 + // 若上送了别的渠道,则设置参数 if req.ReceiptChannel != 0 { msg.ReceiptChannel = int(req.ReceiptChannel) msg.WorkerAccount = req.WorkerAccount } - // 6.写入下发历史表,将历史表id作为requestNo - singleDistributeHis := &single_distribute_his_proto.OpCreateReq{ - SingleDistributeHis: &dbstruct.SingleDistributeHis{ - Mid: goproto.Int64(req.BaseRequest.Mid), - }, - } - err = _DefaultSingleDistributeHis.OpCreate(ctx, singleDistributeHis) + // 锁操作,当前提前申请结束之前,禁止再发起提现申请 + lock, err := _DefaultSingleDistributeHis.GetAndUpdateLock(ctx, req.BaseRequest.Mid) if err != nil { - logger.Error("_DefaultSingleDistributeHis OpCreate fail, err: %v", err) - ec = errcode.ErrCodeSingleDistributeHisSrvFail + logger.Error("_DefaultSingleDistributeHis GetAndUpdateLock failed : %v", err) + ec, err = errcode.ErrCodeSingleDistributeHisSrvFail, nil return } - msg.RequestNo = singleDistributeHis.GetId() - - // 7.调用下发打款接口 - resp, err := DefaultHvyogoService.SingleDistribute(msg) - if err != nil { - logger.Error("DefaultHvyogoService SingleDistribute fail, err: %v", err) - ec = errcode.ErrCodeHvyogoSrvFail + if lock.IsLocked() { + logger.Error("Unhandled single distribution request is found") + ec = errcode.ErrCodeCurrentSingleDistributeNotTerminated return } - // 8.更新至历史表 - err = _DefaultSingleDistributeHis.OpUpdate(ctx, &single_distribute_his_proto.OpUpdateReq{ - SingleDistributeHis: &dbstruct.SingleDistributeHis{ - Id: singleDistributeHis.Id, - StatusCode: goproto.String(resp.StatusCode), - StatusText: goproto.String(resp.StatusText), - DistributeId: goproto.String(resp.DistributeId), - DistributeAmount: goproto.String(resp.DistributeAmount), - }, - }) + // 外部提现申请函数 + extWithdrawFunc := func(orderId string) (*hvyogoproto.SingleDistributeVO, error) { + // 写入下发历史表,将历史表id作为requestNo + singleDistributeHis := &single_distribute_his_proto.OpCreateReq{ + SingleDistributeHis: &dbstruct.SingleDistributeHis{ + Mid: goproto.Int64(req.BaseRequest.Mid), + OrderId: goproto.String(orderId), + }, + } + err = _DefaultSingleDistributeHis.OpCreate(ctx, singleDistributeHis) + if err != nil { + logger.Error("_DefaultSingleDistributeHis OpCreate fail, err: %v", err) + return nil, err + } + msg.RequestNo = singleDistributeHis.GetId() + + // 调用下发打款接口 + resp, err := DefaultHvyogoService.SingleDistribute(msg) + if err != nil { + logger.Error("DefaultHvyogoService SingleDistribute fail, err: %v", err) + return nil, err + } + + // 更新至历史表 + err = _DefaultSingleDistributeHis.OpUpdate(ctx, &single_distribute_his_proto.OpUpdateReq{ + SingleDistributeHis: &dbstruct.SingleDistributeHis{ + Id: singleDistributeHis.Id, + StatusCode: goproto.String(resp.StatusCode), + StatusText: goproto.String(resp.StatusText), + DistributeId: goproto.String(resp.DistributeId), + DistributeAmount: goproto.String(resp.DistributeAmount), + }, + }) + if err != nil { + logger.Error("_DefaultSingleDistributeHis OpUpdate fail, err: %v", err) + } + + return resp, nil + } + + // 发起单侧提现申请 + resp, err := _DefaultVas.UnilaterallyHvyogoWithdrawApply(ctx, &vasproto.UnilaterallyWithdrawApplyReq{ + BaseRequest: req.BaseRequest, + Diamonds: int64(distributeAmount), + }, extWithdrawFunc) + ec, err = errs.DealVasErr(err) if err != nil { - logger.Error("_DefaultSingleDistributeHis OpUpdate fail, err: %v", err) - ec = errcode.ErrCodeSingleDistributeHisSrvFail + logger.Error("WithdrawApply fail, req: %v, err: %v", util.ToJson(req), err) return } - // 9.组装返回结果 + // 组装返回结果 data = &hvyogoproto.ApiSingleDistributeData{} deepcopier.Copy(resp).To(data) diff --git a/app/mix/service/logic/single_distribute_his.go b/app/mix/service/logic/single_distribute_his.go index 29292472..55ceb9fc 100644 --- a/app/mix/service/logic/single_distribute_his.go +++ b/app/mix/service/logic/single_distribute_his.go @@ -63,3 +63,21 @@ func (p *SingleDistributeHis) OpList(ctx *gin.Context, req *single_distribute_hi } return list, nil } + +func (p *SingleDistributeHis) GetAndUpdateLock(ctx *gin.Context, mid int64) (*dbstruct.SingleDistributeLock, error) { + lock, err := p.store.GetAndUpdateSingleDistributeLock(ctx, mid) + if err != nil { + logger.Error("GetAndUpdateSingleDistributeLock fail, err: %v", err) + return nil, err + } + return lock, nil +} + +func (p *SingleDistributeHis) ClearLock(ctx *gin.Context, mid int64) error { + err := p.store.ClearSingleDistributeLock(ctx, mid) + if err != nil { + logger.Error("ClearSingleDistributeLock fail, err: %v", err) + return err + } + return nil +} diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index b4bfb3fe..83eb5b46 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -12,6 +12,7 @@ import ( "service/api/base" "service/api/errs" accountproto "service/api/proto/account/proto" + hvyogoproto "service/api/proto/hvyogo/proto" vasproto "service/api/proto/vas/proto" "service/app/mix/dao" "service/bizcommon/common" @@ -3591,3 +3592,210 @@ func (v *Vas) GetIncomeByTimeSpanGroupByMid(ctx *gin.Context, tx *sqlx.Tx, st, e func (v *Vas) GetRefundRateGroupByMid(ctx *gin.Context, tx *sqlx.Tx) ([]*dbstruct.RefundRate, error) { return v.store.GetRefundRateGroupByMid(ctx, tx) } + +// 单侧提现申请 +func (v *Vas) UnilaterallyHvyogoWithdrawApply(ctx *gin.Context, req *vasproto.UnilaterallyWithdrawApplyReq, extWithdrawFunc func(orderId string) (*hvyogoproto.SingleDistributeVO, error)) (resp *hvyogoproto.SingleDistributeVO, err error) { + var ( + mid = req.Mid + diamonds = req.Diamonds + money = req.Diamonds * 10 + ) + + // 今日提现次数 + st := util.GetTodayZeroTime().Unix() + et := st + 86400 + list, _ := v.store.GetWithdrawOrdersByMid(ctx, nil, mid, st, et) + if len(list) > 0 { + err = errs.ErrVasOverTodayWithdrawCnt + return + } + + // todo 分布式锁 + + // 检查余额 + wallet, _ := v.CheckWalletExist(ctx, nil, mid) + if wallet == nil { + err = errs.ErrVasWalletNotExist + return + } + if wallet.GetWithdrawDiamonds() < diamonds { + err = errs.ErrVasNoEnoughWithdrawDias + return + } + + var ( + wOrder *dbstruct.WithdrawOrder + orderId = idgenerator.GenWithdrawOrderId() + ) + + // 开启事务 + tx, err := v.store.VasBegin(ctx) + if err != nil { + logger.Error("vas begin fail, err: %v", err) + return + } + defer func() { + if err != nil { + logger.Error("global err, req: %v, err: %v", util.ToJson(req), err) + } + errTx := v.store.DealTxCR(tx, err) + if errTx != nil { + logger.Error("DealTxCR fail, err: %v", errTx) + return + } + }() + + // 锁定钱包 + walletLock, err := v.store.GetWalletForUpdate(ctx, tx, mid) + if err != nil { + logger.Error("GetWalletForUpdate fail, mid: %, err: %v", mid, err) + return + } + + // 扣提现钻石 + err = v.store.DecWithdrawDiamonds(ctx, tx, mid, diamonds) + if err != nil { + logger.Error("DecWithdrawDiamonds fail, mid: %, err: %v", mid, err) + return + } + + // 扣钻石 + err = v.store.DecDiamonds(ctx, tx, mid, diamonds) + if err != nil { + logger.Error("DecDiamonds fail, mid: %, err: %v", mid, err) + return + } + + // 添加提现订单 + wOrder = &dbstruct.WithdrawOrder{ + ID: goproto.String(orderId), + Mid: goproto.Int64(mid), + Did: goproto.String(req.Did), + ApplyTime: goproto.Int64(time.Now().Unix()), + WithdrawDias: goproto.Int64(diamonds), + WithdrawMoney: goproto.Int64(money), + Ip: goproto.String(req.Ip), + OrderStatus: goproto.Int32(dbstruct.VasWithdrawOrderStatusInit), + Operator: goproto.String(""), + OpTime: goproto.Int64(0), + } + err = v.store.CreateWithdrawOrder(ctx, tx, wOrder) + if err != nil { + logger.Error("CreateWithdrawOrder fail, req: %v, order: %v, err: %v", req, wOrder.ToString(), err) + return + } + + // 更改状态 + err = v.store.UpdateWithdrawOrderStatus(ctx, tx, orderId, dbstruct.VasWithdrawOrderStatusInit, dbstruct.VasWithdrawOrderStatusWaitHvyogoDeal) + if err != nil { + logger.Error("UpdateWithdrawOrderStatus fail, order: %v, err: %v", wOrder.ToString(), err) + return + } + + // 添加提现记录 + chWithdraw := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(mid), + Did: goproto.String(req.Did), + Type: goproto.Int32(dbstruct.CHTypeWithdraw), + SType: goproto.Int32(dbstruct.CHSTypeWithdrawDiamondHvyogo), + TypeId: goproto.String("hvyogo_withdraw_diamonds"), + OrderId: goproto.String(orderId), + Change: goproto.Int64(-diamonds), + Before: goproto.Int64(walletLock.GetWithdrawDiamonds()), + After: goproto.Int64(walletLock.GetWithdrawDiamonds() - diamonds), + Ct: goproto.Int64(time.Now().Unix()), + } + err = v.store.CreateConsumeHistory(ctx, tx, chWithdraw) + if err != nil { + logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chWithdraw), err) + return + } + + // 外部提现申请函数执行 + resp, err = extWithdrawFunc(orderId) + if err != nil { + logger.Error("extWithdrawFunc fail, err: %v", err) + return + } + + return +} + +// 单侧提现冲正 +func (v *Vas) UnilaterallyWithdrawReverse(ctx *gin.Context, orderId string) (err error) { + + // 从订单号查询提现订单 + wOrder, err := v.store.GetWithdrawOrderById(ctx, nil, orderId) + if err != nil { + logger.Error("GetWithdrawOrderById fail, orderId : %v, err: %v", orderId, err) + return + } + + // 开启事务 + tx, err := v.store.VasBegin(ctx) + if err != nil { + logger.Error("vas begin fail, err: %v", err) + return + } + defer func() { + if err != nil { + logger.Error("global err, err: %v", err) + } + errTx := v.store.DealTxCR(tx, err) + if errTx != nil { + logger.Error("DealTxCR fail, err: %v", errTx) + return + } + }() + + mid := wOrder.GetMid() + diamonds := wOrder.GetWithdrawDias() + + // 锁定钱包 + walletLock, err := v.store.GetWalletForUpdate(ctx, tx, mid) + if err != nil { + logger.Error("GetWalletForUpdate fail, mid: %v, err: %v", mid, err) + return + } + + // 恢复提现钻石 + err = v.store.IncWithdrawDiamonds(ctx, tx, mid, diamonds) + if err != nil { + logger.Error("DecWithdrawDiamonds fail, mid: %, err: %v", mid, err) + return + } + + // 恢复钻石 + err = v.store.IncDiamonds(ctx, tx, mid, diamonds) + if err != nil { + logger.Error("DecDiamonds fail, mid: %, err: %v", mid, err) + return + } + + // 更改提现订单状态 + err = v.store.UpdateWithdrawOrderStatus(ctx, tx, orderId, dbstruct.VasWithdrawOrderStatusWaitHvyogoDeal, dbstruct.VasWithdrawOrderStatusReversed) + if err != nil { + logger.Error("UpdateWithdrawOrderStatus fail, order: %v, err: %v", wOrder.ToString(), err) + return + } + + // 添加提现记录 + chWithdraw := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(mid), + Type: goproto.Int32(dbstruct.CHTypeWithdraw), + SType: goproto.Int32(dbstruct.CHSTypeWithdrawDiamondReversal), + TypeId: goproto.String("withdraw_diamonds_reversal"), + OrderId: goproto.String(orderId), + Change: goproto.Int64(diamonds), + Before: goproto.Int64(walletLock.GetWithdrawDiamonds()), + After: goproto.Int64(walletLock.GetWithdrawDiamonds() + diamonds), + Ct: goproto.Int64(time.Now().Unix()), + } + err = v.store.CreateConsumeHistory(ctx, tx, chWithdraw) + if err != nil { + logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chWithdraw), err) + return + } + + return +} diff --git a/dbstruct/single_distribute_his.go b/dbstruct/single_distribute_his.go index d1b8ac82..aef0f3b7 100644 --- a/dbstruct/single_distribute_his.go +++ b/dbstruct/single_distribute_his.go @@ -12,6 +12,7 @@ type SingleDistributeHis struct { Timestamp *string `json:"timestamp" bson:"timestamp"` // 下单时间 Remark *string `json:"remark" bson:"remark"` // 银行返回打款备注 ReasonCode *string `json:"reason_code" bson:"reason_code"` // 余额不足时会返回E00001 + OrderId *string `json:"order_id" bson:""order_id` // 订单号 Ct *int64 `json:"ct" bson:"ct"` // 创建时间 Ut *int64 `json:"ut" bson:"ut"` // 更新时间 DelFlag *int64 `json:"del_flag" bson:"del_flag"` // 删除标记 diff --git a/dbstruct/single_distribute_lock.go b/dbstruct/single_distribute_lock.go new file mode 100644 index 00000000..50248a51 --- /dev/null +++ b/dbstruct/single_distribute_lock.go @@ -0,0 +1,13 @@ +package dbstruct + +type SingleDistributeLock struct { + Id int64 `json:"id" bson:"_id"` //id,主播的mid + Lock int64 `json:"lock" bson:"lock"` //发帖次数 +} + +func (p *SingleDistributeLock) IsLocked() bool { + if p == nil { + return false + } + return p.Lock > 0 +} diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 96338fee..39267909 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -550,7 +550,9 @@ const ( CHSTypeIncomeRefundCollaborator = 30011 // 收入明细,协作者 CHSTypeIncomeRefundZoneStreamer = 30012 // 收入明细,主播空间收益 - CHSTypeWithdrawDiamondAuto = 40001 // 自动提现明细 + CHSTypeWithdrawDiamondAuto = 40001 // 自动提现明细 + CHSTypeWithdrawDiamondHvyogo = 40002 // 慧用工提现明细 + CHSTypeWithdrawDiamondReversal = 40002 // 提现冲正明细 ) type ConsumeHistory struct { @@ -723,21 +725,25 @@ type VasOrderStatusCount struct { // 钱包 const ( - VasWithdrawOrderStatusFail = -2 // 失败 - VasWithdrawOrderStatusNone = -1 // 零状态 - VasWithdrawOrderStatusInit = 0 // 初始化 - VasWithdrawOrderStatusWaitDeal = 1 // 等待运营处理 - VasWithdrawOrderStatusAuto = 2 // 小额自动提现 - VasWithdrawOrderStatusDeal = 3 // 已处理 + VasWithdrawOrderStatusFail = -2 // 失败 + VasWithdrawOrderStatusNone = -1 // 零状态 + VasWithdrawOrderStatusInit = 0 // 初始化 + VasWithdrawOrderStatusWaitDeal = 1 // 等待运营处理 + VasWithdrawOrderStatusAuto = 2 // 小额自动提现 + VasWithdrawOrderStatusDeal = 3 // 已处理 + VasWithdrawOrderStatusWaitHvyogoDeal = 4 // 等待慧用工处理 + VasWithdrawOrderStatusReversed = 5 // 已冲正 ) var WithdrawOrderStatusDescMap = map[int32]string{ - VasWithdrawOrderStatusFail: "提现失败", - VasWithdrawOrderStatusNone: "零状态", - VasWithdrawOrderStatusInit: "初始化", - VasWithdrawOrderStatusWaitDeal: "等待运营处理", - VasWithdrawOrderStatusAuto: "小额自动提醒", - VasWithdrawOrderStatusDeal: "已处理", + VasWithdrawOrderStatusFail: "提现失败", + VasWithdrawOrderStatusNone: "零状态", + VasWithdrawOrderStatusInit: "初始化", + VasWithdrawOrderStatusWaitDeal: "等待运营处理", + VasWithdrawOrderStatusAuto: "小额自动提醒", + VasWithdrawOrderStatusDeal: "已处理", + VasWithdrawOrderStatusWaitHvyogoDeal: "等待慧用工处理", + VasWithdrawOrderStatusReversed: "已冲正", } type WithdrawOrder struct { diff --git a/library/apollo/apollo.go b/library/apollo/apollo.go index 81dff3e6..87e96a6e 100644 --- a/library/apollo/apollo.go +++ b/library/apollo/apollo.go @@ -34,9 +34,10 @@ func Init(cfg *configcenter.ApolloConfig) (err error) { } type ApolloOptions struct { - Namespace *string - DefaultValue *string - DefaultIntValue *int + Namespace *string + DefaultValue *string + DefaultIntValue *int + DefaultFloat64Value *float64 } func mergeApolloOptions(opts ...*ApolloOptions) *ApolloOptions { @@ -54,6 +55,9 @@ func mergeApolloOptions(opts ...*ApolloOptions) *ApolloOptions { if opt.DefaultIntValue != nil { ao.DefaultIntValue = opt.DefaultIntValue } + if opt.DefaultFloat64Value != nil { + ao.DefaultFloat64Value = opt.DefaultFloat64Value + } } return ao } @@ -93,6 +97,13 @@ func (ao *ApolloOptions) GetDefaultIntValue() int { return 0 } +func (ao *ApolloOptions) GetDefaultFloat64Value() float64 { + if ao != nil && ao.DefaultFloat64Value != nil { + return *ao.DefaultFloat64Value + } + return 0 +} + func GetJson(key string, v interface{}, opts ...*ApolloOptions) (err error) { opt := mergeApolloOptions(opts...) var value string @@ -126,3 +137,13 @@ func GetIntValue(key string, opts ...*ApolloOptions) (value int, err error) { } return } + +func GetFloat64Value(key string, opts ...*ApolloOptions) (value float64, err error) { + opt := mergeApolloOptions(opts...) + if opt.GetNamespace() != "" { + value = defaultApolloClient.GetConfig(opt.GetNamespace()).GetFloatValue(key, opt.GetDefaultFloat64Value()) + } else { + value = defaultApolloClient.GetFloatValue(key, opt.GetDefaultFloat64Value()) + } + return +}