From 8113868cc0bddd7d6696e68da3dc817e2d45eb21 Mon Sep 17 00:00:00 2001 From: hucan <951870319@qq.com> Date: Sat, 17 May 2025 09:10:14 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E8=B0=83=E6=95=B4gas=E8=B4=B9?= =?UTF-8?q?=E6=B5=AE=E5=8A=A8=E6=AF=94=E4=BE=8B=E4=B8=BA90%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/apis/wm_transfer_item.go | 22 +++- app/admin/apis/wm_wallet_info.go | 46 ++++++++ app/admin/models/wm_transfer.go | 2 + app/admin/models/wm_wallet_info.go | 1 + app/admin/router/wm_transfer_item.go | 1 + app/admin/router/wm_wallet_info.go | 3 + app/admin/service/dto/wm_token.go | 1 + app/admin/service/dto/wm_transfer.go | 8 ++ app/admin/service/dto/wm_transfer_item.go | 7 +- app/admin/service/dto/wm_wallet_info.go | 16 ++- app/admin/service/wm_token.go | 11 +- app/admin/service/wm_transfer.go | 25 ++++- app/admin/service/wm_transfer_item.go | 104 ++++++++++++++++-- app/admin/service/wm_wallet_info.go | 73 +++++++++++- static/excel/钱包导入模板.xlsx | Bin 0 -> 9216 bytes ...alanceof_helper.go => balanceof_helper.go} | 24 ++++ .../balanceof_helper_test.go | 25 +++++ utils/ethtransferhelper/transfer_helper.go | 8 +- utils/excelhelper/excel_helper.go | 43 ++++++++ 19 files changed, 397 insertions(+), 23 deletions(-) create mode 100644 static/excel/钱包导入模板.xlsx rename utils/ethbalanceofhelper/{baalanceof_helper.go => balanceof_helper.go} (74%) create mode 100644 utils/ethbalanceofhelper/balanceof_helper_test.go diff --git a/app/admin/apis/wm_transfer_item.go b/app/admin/apis/wm_transfer_item.go index e7c2d6c..7245157 100644 --- a/app/admin/apis/wm_transfer_item.go +++ b/app/admin/apis/wm_transfer_item.go @@ -43,7 +43,7 @@ func (e WmTransferItem) GetPage(c *gin.Context) { } p := actions.GetPermissionFromContext(c) - list := make([]models.WmTransferItem, 0) + list := make([]dto.WmTransferItemResp, 0) var count int64 err = s.GetPage(&req, p, &list, &count) @@ -262,3 +262,23 @@ func (e WmTransferItem) GetAutoTransferLogPage(c *gin.Context) { } e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") } + +// 清除自动转账 +func (e WmTransferItem) ClearAutoLog(c *gin.Context) { + s := service.WmTransferItem{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.ClearAutoLog() + if err != nil { + e.Error(500, err, fmt.Sprintf("清除自动转账日志失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(nil, "清除成功") +} diff --git a/app/admin/apis/wm_wallet_info.go b/app/admin/apis/wm_wallet_info.go index 0ddafb2..fc1661b 100644 --- a/app/admin/apis/wm_wallet_info.go +++ b/app/admin/apis/wm_wallet_info.go @@ -174,3 +174,49 @@ func (e WmWalletInfo) ClearAll(c *gin.Context) { } e.OK(nil, "清空成功") } + +// 根据excel导入钱包信息 +func (e WmWalletInfo) ExcelImport(c *gin.Context) { + s := service.WmWalletInfo{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + errs := s.ExcelImport(c) + + e.OK(strings.Join(errs, "
"), "创建成功") +} + +// UpdateRemark 更新备注信息 +func (e WmWalletInfo) UpdateRemark(c *gin.Context) { + s := service.WmWalletInfo{} + req := dto.WmWalletInfoBatchUpdateReq{} + + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置更新人 + req.SetUpdateBy(user.GetUserId(c)) + + err = s.BatchUpdateRemark(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("更新备注信息失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(nil, "更新成功") +} diff --git a/app/admin/models/wm_transfer.go b/app/admin/models/wm_transfer.go index c9bd0e7..9f7ca08 100644 --- a/app/admin/models/wm_transfer.go +++ b/app/admin/models/wm_transfer.go @@ -17,6 +17,8 @@ type WmTransfer struct { Sucess int `json:"sucess" gorm:"-"` Fail int `json:"fail" gorm:"-"` Pending int `json:"pending" gorm:"-"` + NetworkName string `json:"networkName" gorm:"-"` + TokenName string `json:"tokenName" gorm:"-"` models.ModelTime models.ControlBy } diff --git a/app/admin/models/wm_wallet_info.go b/app/admin/models/wm_wallet_info.go index 5041a68..a18af14 100644 --- a/app/admin/models/wm_wallet_info.go +++ b/app/admin/models/wm_wallet_info.go @@ -12,6 +12,7 @@ type WmWalletInfo struct { PrivateKey string `json:"privateKey" gorm:"type:varchar(50);comment:钱包私钥"` Address string `json:"address" gorm:"type:varchar(50);comment:钱包地址"` UsdcAmount decimal.Decimal `json:"usdcAmount" gorm:"type:decimal(18,8);comment:USDC余额"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注信息"` models.ModelTime models.ControlBy } diff --git a/app/admin/router/wm_transfer_item.go b/app/admin/router/wm_transfer_item.go index d6b837c..70b0035 100644 --- a/app/admin/router/wm_transfer_item.go +++ b/app/admin/router/wm_transfer_item.go @@ -24,6 +24,7 @@ func registerWmTransferItemRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJW r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) + r.DELETE("/clear", api.ClearAutoLog) r.GET("/export", actions.PermissionAction(), api.Export) r.GET("/export-auto-log", actions.PermissionAction(), api.ExportAutoLog) r.GET("/auto-log", actions.PermissionAction(), api.GetAutoTransferLogPage) diff --git a/app/admin/router/wm_wallet_info.go b/app/admin/router/wm_wallet_info.go index 949d073..5c2f2ab 100644 --- a/app/admin/router/wm_wallet_info.go +++ b/app/admin/router/wm_wallet_info.go @@ -24,5 +24,8 @@ func registerWmWalletInfoRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTM // r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) r.DELETE("clear", api.ClearAll) + + r.POST("/excel-import", api.ExcelImport) //根据excel导入数据 + r.PUT("/batch", api.UpdateRemark) //批量更新备注 } } diff --git a/app/admin/service/dto/wm_token.go b/app/admin/service/dto/wm_token.go index 37f74f5..3af02e1 100644 --- a/app/admin/service/dto/wm_token.go +++ b/app/admin/service/dto/wm_token.go @@ -13,6 +13,7 @@ type WmTokenGetPageReq struct { dto.Pagination `search:"-"` NetworkId string `form:"networkId" search:"type:contains;column:network_id;table:wm_token" comment:"网络id"` TokenName string `form:"tokenName" search:"type:contains;column:token_name;table:wm_token" comment:"代币名称"` + TransType int `form:"transType" search:"-" comment:"转账类型 1-百分比 2-实际金额"` WmTokenOrder } diff --git a/app/admin/service/dto/wm_transfer.go b/app/admin/service/dto/wm_transfer.go index 8f4084e..65df912 100644 --- a/app/admin/service/dto/wm_transfer.go +++ b/app/admin/service/dto/wm_transfer.go @@ -2,9 +2,11 @@ package dto import ( "errors" + "fmt" "go-admin/app/admin/models" "go-admin/common/dto" common "go-admin/common/models" + "go-admin/utils/ethtransferhelper" "strings" "github.com/shopspring/decimal" @@ -105,6 +107,12 @@ func (s *WmTransferInsertReq) Valid() error { } } + for _, content := range contents { + if !ethtransferhelper.IsValidAddress(content) { + return fmt.Errorf("%s 地址格式错误", content) + } + } + return nil } diff --git a/app/admin/service/dto/wm_transfer_item.go b/app/admin/service/dto/wm_transfer_item.go index 27d0e25..1e1489f 100644 --- a/app/admin/service/dto/wm_transfer_item.go +++ b/app/admin/service/dto/wm_transfer_item.go @@ -17,7 +17,10 @@ type WmTransferItemGetPageReq struct { type WmTransferItemAutoLogPageReq struct { dto.Pagination `search:"-"` - NetworkId int `form:"networkId" search:"type:exact;column:network_id;table:wm_transfer_item"` + NetworkId int `form:"networkId" search:"type:exact;column:network_id;table:wm_transfer_item"` + StartAmount float64 `form:"startAmount" search:"-"` + EndAmount float64 `form:"endAmount" search:"-"` + Status int `form:"status" search:"-"` WmTransferItemOrder } @@ -146,6 +149,8 @@ type WmTransferItemExportReq struct { } type WmTransferItemExportData struct { + NetworkName string `json:"networkName" excel:"网络名称"` + TokenName string `json:"tokenName" excel:"代币名称"` PrivateKey string `json:"privateKey" excel:"私钥"` TokenAddress string `json:"tokenAddress" excel:"代币地址"` FromAddress string `json:"fromAddress" excel:"来源地址"` diff --git a/app/admin/service/dto/wm_wallet_info.go b/app/admin/service/dto/wm_wallet_info.go index ae21b21..450345a 100644 --- a/app/admin/service/dto/wm_wallet_info.go +++ b/app/admin/service/dto/wm_wallet_info.go @@ -10,7 +10,7 @@ import ( type WmWalletInfoGetPageReq struct { dto.Pagination `search:"-"` - PrivateKey string `form:"privateKey" search:"type:exact;column:private_key;table:wm_wallet_info" comment:"钱包私钥"` + PrivateKey string `form:"privateKey" search:"-" comment:"钱包私钥"` WmWalletInfoOrder } @@ -30,7 +30,8 @@ func (m *WmWalletInfoGetPageReq) GetNeedSearch() interface{} { } type WmWalletInfoBatchInsertReq struct { - Keys string `json:"keys"` + Keys string `json:"keys"` + Remark string `json:"remark"` common.ControlBy } @@ -103,3 +104,14 @@ type WmWalletInfoDeleteReq struct { func (s *WmWalletInfoDeleteReq) GetId() interface{} { return s.Ids } + +type WmWalletExcelImportReq struct { + PrivateKey string `json:"privateKey" excel:"钱包私钥"` + Remark string `json:"remark" excel:"备注"` +} + +type WmWalletInfoBatchUpdateReq struct { + Ids []int `json:"ids" form:"ids"` + Remark string `json:"remark" form:"remark"` + common.ControlBy +} diff --git a/app/admin/service/wm_token.go b/app/admin/service/wm_token.go index 8973f8a..8950a13 100644 --- a/app/admin/service/wm_token.go +++ b/app/admin/service/wm_token.go @@ -25,13 +25,18 @@ type WmToken struct { func (e *WmToken) GetPage(c *dto.WmTokenGetPageReq, p *actions.DataPermission, list *[]models.WmToken, count *int64) error { var err error var data models.WmToken - - err = e.Orm.Model(&data). + query := e.Orm.Model(&data). Scopes( cDto.MakeCondition(c.GetNeedSearch()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), actions.Permission(data.TableName(), p), - ). + ) + + if c.TransType > 0 { + query.Where("trans_type = ?", c.TransType) + } + + err = query. Find(list).Limit(-1).Offset(-1). Count(count).Error if err != nil { diff --git a/app/admin/service/wm_transfer.go b/app/admin/service/wm_transfer.go index 1001ae6..e403169 100644 --- a/app/admin/service/wm_transfer.go +++ b/app/admin/service/wm_transfer.go @@ -56,6 +56,10 @@ func (e *WmTransfer) GetPage(c *dto.WmTransferGetPageReq, p *actions.DataPermiss } itemData, _ := e.GetItemData(transferIds) + networkService := WmNetwork{Service: e.Service} + networks, _ := networkService.GetAll() + tokenService := WmToken{Service: e.Service} + tokens, _ := tokenService.GetAll() for i := range *list { var total int @@ -74,6 +78,20 @@ func (e *WmTransfer) GetPage(c *dto.WmTransferGetPageReq, p *actions.DataPermiss } } + for _, network := range networks { + if network.Id == (*list)[i].NetworkId { + (*list)[i].NetworkName = network.NetworkName + break + } + } + + for _, token := range tokens { + if token.NetworkId == (*list)[i].NetworkId && token.TokenAddress == (*list)[i].TokenAddress { + (*list)[i].TokenName = token.TokenName + break + } + } + (*list)[i].Total = total } @@ -196,6 +214,7 @@ func (e *WmTransfer) Insert(c *dto.WmTransferInsertReq) error { caches[i].TransferId = data.Id caches[i].IsAuto = 2 caches[i].TokenAddress = c.TokenAddress + caches[i].Decimals = token.Decimals } if err := tx.CreateInBatches(&caches, 100).Error; err != nil { @@ -226,7 +245,7 @@ func (e *WmTransfer) getPrivateKeyAmount(privateKey, tokenAddress string, decima return amount, errors.New("连接区块链失败") } - amount, err = ethbalanceofhelper.GetERC20Balance(client, tokenAddress, accountAddress.String(), decimals) + amount, err = ethbalanceofhelper.GetBalance(client, tokenAddress, accountAddress.String(), decimals) if err != nil { return amount, errors.New("查询主账号余额失败") @@ -372,11 +391,11 @@ func (e *WmTransfer) CheckHashStatus() error { // 清理数据 func (e *WmTransfer) ClearAll() error { err := e.Orm.Transaction(func(tx *gorm.DB) error { - if err1 := tx.Exec("truncate table wm_transfer_item").Error; err1 != nil { + if err1 := tx.Exec("delete from wm_transfer_item where is_auto = 2").Error; err1 != nil { return err1 } - if err2 := tx.Exec("truncate table wm_transfer").Error; err2 != nil { + if err2 := tx.Exec("delete from wm_transfer ").Error; err2 != nil { return err2 } diff --git a/app/admin/service/wm_transfer_item.go b/app/admin/service/wm_transfer_item.go index b79d512..d026037 100644 --- a/app/admin/service/wm_transfer_item.go +++ b/app/admin/service/wm_transfer_item.go @@ -21,9 +21,10 @@ type WmTransferItem struct { } // GetPage 获取WmTransferItem列表 -func (e *WmTransferItem) GetPage(c *dto.WmTransferItemGetPageReq, p *actions.DataPermission, list *[]models.WmTransferItem, count *int64) error { +func (e *WmTransferItem) GetPage(c *dto.WmTransferItemGetPageReq, p *actions.DataPermission, list *[]dto.WmTransferItemResp, count *int64) error { var err error var data models.WmTransferItem + var datas []models.WmTransferItem err = e.Orm.Model(&data). Scopes( @@ -31,15 +32,48 @@ func (e *WmTransferItem) GetPage(c *dto.WmTransferItemGetPageReq, p *actions.Dat cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), actions.Permission(data.TableName(), p), ). - Find(list).Limit(-1).Offset(-1). + Find(&datas).Limit(-1).Offset(-1). Count(count).Error if err != nil { e.Log.Errorf("WmTransferItemService GetPage error:%s \r\n", err) return err } - for i := 0; i < len(*list); i++ { - (*list)[i].PrivateKey = stringhelper.DesensitizeWalletAddress(aeshelper.AesEcbDecrypt((*list)[i].PrivateKey)) + networkService := WmNetwork{Service: e.Service} + networks, _ := networkService.GetAll() + tokenService := WmToken{Service: e.Service} + tokens, _ := tokenService.GetAll() + + for i := 0; i < len(datas); i++ { + item := dto.WmTransferItemResp{} + item.Id = datas[i].Id + item.PrivateKey = stringhelper.DesensitizeWalletAddress(aeshelper.AesEcbDecrypt(datas[i].PrivateKey)) + item.FromAddress = datas[i].FromAddress + item.ToAddress = datas[i].ToAddress + item.TokenAddress = datas[i].TokenAddress + item.Amount = datas[i].Amount + item.Status = datas[i].Status + item.Remark = datas[i].Remark + item.Type = datas[i].Type + item.TypeValue = datas[i].TypeValue + item.Hash = datas[i].Hash + item.CreateAt = datas[i].CreatedAt + + for _, network := range networks { + if network.Id == datas[i].NetworkId { + item.NetworkName = network.NetworkName + break + } + } + + for _, token := range tokens { + if token.NetworkId == datas[i].NetworkId && token.TokenAddress == datas[i].TokenAddress { + item.TokenName = token.TokenName + break + } + } + + *list = append(*list, item) } return nil @@ -50,9 +84,22 @@ func (e *WmTransferItem) GetAutoTransferLogPage(c *dto.WmTransferItemAutoLogPage var err error var data models.WmTransferItem var datas []models.WmTransferItem + query := e.Orm.Model(&data). + Where("is_auto = 1") - err = e.Orm.Model(&data). - Where("is_auto = 1"). + if c.StartAmount >= 0 { + query.Where("amount >=?", c.StartAmount) + } + + if c.EndAmount > 0 { + query.Where("amount <=?", c.EndAmount) + } + + if c.Status > 0 { + query.Where("status =?", c.Status) + } + + err = query. Scopes( cDto.MakeCondition(c.GetNeedSearch()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), @@ -111,8 +158,21 @@ func (e *WmTransferItem) ExportAutoLog(req *dto.WmTransferItemAutoLogPageReq, c var err error var data models.WmTransferItem exportDatas := make([]dto.WmTransferItemResp, 0) + query := e.Orm.Model(&data) - err = e.Orm.Model(&data). + if req.StartAmount >= 0 { + query.Where("amount >=?", req.StartAmount) + } + + if req.EndAmount > 0 { + query.Where("amount <=?", req.EndAmount) + } + + if req.Status > 0 { + query.Where("status =?", req.Status) + } + + err = query. Where("is_auto = 1"). Scopes( cDto.MakeCondition(req.GetNeedSearch()), @@ -259,6 +319,11 @@ func (e *WmTransferItem) ExportExcel(req *dto.WmTransferItemExportReq, p *action return err } + networkService := WmNetwork{Service: e.Service} + networks, _ := networkService.GetAll() + tokenService := WmToken{Service: e.Service} + tokens, _ := tokenService.GetAll() + for i := 0; i < len(datas); i++ { (datas)[i].PrivateKey = stringhelper.DesensitizeWalletAddress(aeshelper.AesEcbDecrypt((datas)[i].PrivateKey)) exportData := dto.WmTransferItemExportData{ @@ -281,6 +346,21 @@ func (e *WmTransferItem) ExportExcel(req *dto.WmTransferItemExportReq, p *action default: exportData.Status = "默认" } + + for _, network := range networks { + if network.Id == datas[i].NetworkId { + exportData.NetworkName = network.NetworkName + break + } + } + + for _, token := range tokens { + if token.NetworkId == datas[i].NetworkId && token.TokenAddress == datas[i].TokenAddress { + exportData.TokenName = token.TokenName + break + } + } + exportDatas = append(exportDatas, exportData) } @@ -290,3 +370,13 @@ func (e *WmTransferItem) ExportExcel(req *dto.WmTransferItemExportReq, p *action return excelhelper.ExportExcel(c, "转账明细", exportDatas, []string{}) } + +// 批量删除自动转账日志 +func (e *WmTransferItem) ClearAutoLog() error { + if err := e.Orm.Where("is_auto=1").Unscoped().Delete(&models.WmTransferItem{}).Error; err != nil { + e.Log.Errorf("Service ClearAutoLog error:%s \r\n", err) + return err + } + + return nil +} diff --git a/app/admin/service/wm_wallet_info.go b/app/admin/service/wm_wallet_info.go index 746338d..495f9fa 100644 --- a/app/admin/service/wm_wallet_info.go +++ b/app/admin/service/wm_wallet_info.go @@ -4,6 +4,7 @@ import ( "errors" "strings" + "github.com/gin-gonic/gin" "github.com/go-admin-team/go-admin-core/sdk/service" "github.com/shopspring/decimal" "gorm.io/gorm" @@ -16,6 +17,7 @@ import ( "go-admin/utils/aeshelper" "go-admin/utils/ethbalanceofhelper" "go-admin/utils/ethtransferhelper" + "go-admin/utils/excelhelper" "go-admin/utils/stringhelper" ) @@ -27,8 +29,13 @@ type WmWalletInfo struct { func (e *WmWalletInfo) GetPage(c *dto.WmWalletInfoGetPageReq, p *actions.DataPermission, list *[]models.WmWalletInfo, count *int64) error { var err error var data models.WmWalletInfo + query := e.Orm.Model(&data) + if c.PrivateKey != "" { + key := aeshelper.AesEcbDecrypt(c.PrivateKey) + query.Where("private_key =?", key) + } - err = e.Orm.Model(&data). + err = query. Scopes( cDto.MakeCondition(c.GetNeedSearch()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), @@ -174,7 +181,7 @@ func (e *WmWalletInfo) ScheduledTask() error { } for i := range datas { - amount, err := ethbalanceofhelper.GetERC20Balance(client, token.TokenAddress, datas[i].Address, token.Decimals) + amount, err := ethbalanceofhelper.GetBalance(client, token.TokenAddress, datas[i].Address, token.Decimals) if err != nil { e.Log.Errorf("GetERC20Balance error:%s", err) @@ -245,3 +252,65 @@ func (e *WmWalletInfo) ClearAll() error { return err } + +// 根据excel导入数据 +func (e *WmWalletInfo) ExcelImport(c *gin.Context) []string { + dataRows, headers, err := excelhelper.GetExcelContent(c) + errors := []string{} + + if err != nil { + errors = append(errors, err.Error()) + return errors + } + + datas, err := excelhelper.MapExcelToStruct[dto.WmWalletExcelImportReq](dataRows, headers) + + if err != nil { + errors = append(errors, err.Error()) + return errors + } + // entitys := make([]models.WmWalletInfo, 0) + var count int64 + for _, data := range datas { + privateKey := strings.ReplaceAll(data.PrivateKey, " ", "") + if privateKey == "" { + continue + } + + _, address, _ := ethtransferhelper.GetAddressFromPrivateKey(privateKey) + entity := models.WmWalletInfo{ + PrivateKey: aeshelper.AesEcbEncrypt(privateKey), + Address: address.String(), + Remark: data.Remark, + } + + if err := e.Orm.Model(entity).Where("private_key =?", entity.PrivateKey).Count(&count).Error; err != nil { + e.Log.Errorf("db error:%s", err) + } + if count == 0 { + if err := e.Orm.Create(&entity).Error; err != nil { + e.Log.Errorf("db error:%s", err) + errors = append(errors, err.Error()) + } + } + // entitys = append(entitys, entity) + } + + // if len(entitys) > 0 { + // if err := e.Orm.CreateInBatches(entitys, 100).Error; err != nil { + // e.Log.Errorf("定时转账保存数据库失败 error:%s", err) + // return err + // } + // } + + return errors +} + +// 批量修改备注 +func (e *WmWalletInfo) BatchUpdateRemark(req *dto.WmWalletInfoBatchUpdateReq) error { + if err := e.Orm.Model(&models.WmWalletInfo{}).Where("id in ?", req.Ids).Update("remark", req.Remark).Error; err != nil { + return err + } + + return nil +} diff --git a/static/excel/钱包导入模板.xlsx b/static/excel/钱包导入模板.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..08f068d21a87ca7161baa30bc69e1bce35608a0b GIT binary patch literal 9216 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