package nft import ( "context" "encoding/json" "errors" "fmt" "github.com/spf13/cast" "github.com/zeromicro/go-zero/core/logx" "net/http" "nova_task/internal/consts" "nova_task/internal/model" "nova_task/internal/svc" "time" ) type HolderUpdateLogic struct { ctx context.Context svcCtx *svc.ServiceContext } func NewHolderUpdateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HolderUpdateLogic { return &HolderUpdateLogic{ ctx: ctx, svcCtx: svcCtx, } } func (l *HolderUpdateLogic) HolderUpdate() { conf := struct { Network string `json:"network"` Contract string `json:"contract"` }{} cf, err := l.svcCtx.ConfigModel.FindOneByName(l.ctx, consts.NftHolderApiConf) if err != nil { logx.Errorw("find nft holder api conf error", logx.Field("error", err)) return } err = json.Unmarshal([]byte(cf.Value), &conf) if err != nil { logx.Errorw("unmarshal nft holder api conf error", logx.Field("error", err), logx.Field("value", cf.Value)) return } ols, err := l.GetOwnerList(conf.Network, conf.Contract) if err != nil { logx.Errorw("get owner list error", logx.Field("error", err)) return } updateSeq := int(time.Now().Unix()) for _, o := range ols.Owners { for _, tk := range o.TokenBalances { balance := cast.ToInt(tk.Balance) logx.Debugw("owner token", logx.Field("address", o.OwnerAddress), logx.Field("token", tk.TokenID), logx.Field("balance", tk.Balance)) nft, err := l.svcCtx.NftHolderModel.FindOneByTokenId(l.ctx, tk.TokenID) if err != nil { if errors.Is(err, model.ErrNotFound) { nft = &model.NhNftHolder{ Address: o.OwnerAddress, TokenId: tk.TokenID, Balance: balance, UpdateSeq: updateSeq, } } else { logx.Errorw("find nft holder error", logx.Field("error", err), logx.Field("address", o.OwnerAddress), logx.Field("token", tk.TokenID)) continue } } var value int if nft.Id == 0 { // 新增 _, err = l.svcCtx.NftHolderModel.Insert(l.ctx, nft) value = balance } else { // 持有数量变化 value = balance - nft.Balance nft.Balance = balance nft.UpdateSeq = updateSeq err = l.svcCtx.NftHolderModel.Update(l.ctx, nft) } if err != nil { logx.Errorw("insert or update nft holder error", logx.Field("error", err), logx.Field("address", o.OwnerAddress), logx.Field("token", tk.TokenID), logx.Field("balance", balance)) } // 余额有变化,记录变化日志 if value != 0 { _, err = l.svcCtx.NftHolderChangeLogModel.Insert(l.ctx, &model.NhNftHolderChangeLog{ Address: o.OwnerAddress, TokenId: tk.TokenID, Value: balance, Balance: balance, }) if err != nil { logx.Errorw("insert nft holder change log error", logx.Field("error", err), logx.Field("address", o.OwnerAddress), logx.Field("token", tk.TokenID)) } } } } // 删除已经不持有的地址,且添加变化日志 nfts, err := l.svcCtx.NftHolderModel.FindOtherUpdateSeq(l.ctx, updateSeq) if err != nil { logx.Errorw("find other update seq error", logx.Field("error", err)) return } for _, nft := range nfts { _, err = l.svcCtx.NftHolderChangeLogModel.Insert(l.ctx, &model.NhNftHolderChangeLog{ Address: nft.Address, TokenId: nft.TokenId, Value: -nft.Balance, Balance: 0, }) if err != nil { logx.Errorw("delete nft holder error", logx.Field("error", err), logx.Field("address", nft.Address), logx.Field("token", nft.TokenId)) } uid, err := l.svcCtx.WalletModel.FindUidByAddress(l.ctx, nft.Address) if err != nil { logx.Errorw("find uid by address error", logx.Field("error", err), logx.Field("address", nft.Address)) continue } err = l.svcCtx.StakeNftModel.UnStakeNft(l.ctx, uid, nft.TokenId, true) if err != nil { logx.Errorw("un stake nft error", logx.Field("error", err), logx.Field("address", nft.Address), logx.Field("token", nft.TokenId)) } } err = l.svcCtx.NftHolderModel.DeleteOtherUpdateSeq(l.ctx, updateSeq) if err != nil { logx.Errorw("delete other update seq error", logx.Field("error", err), logx.Field("update_seq", updateSeq)) } } type OwnerList struct { Owners []struct { OwnerAddress string `json:"ownerAddress"` TokenBalances []struct { TokenID string `json:"tokenId"` Balance string `json:"balance"` } `json:"tokenBalances"` } `json:"owners"` PageKey string `json:"pageKey"` } func (l *HolderUpdateLogic) GetOwnerList(network, contractAddress string) (*OwnerList, error) { url := fmt.Sprintf("https://eth-%s.g.alchemy.com/nft/v3/alcht_1a183fAsPqF9upfTfp3AC1l0iedGLo/getOwnersForContract?contractAddress=%s&withTokenBalances=true", network, contractAddress) resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() result := new(OwnerList) err = json.NewDecoder(resp.Body).Decode(result) return result, err }