diff --git a/app/admin/apis/wm_network.go b/app/admin/apis/wm_network.go new file mode 100644 index 0000000..7fb5f1f --- /dev/null +++ b/app/admin/apis/wm_network.go @@ -0,0 +1,194 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type WmNetwork struct { + api.Api +} + +// GetPage 获取网络配置列表 +// @Summary 获取网络配置列表 +// @Description 获取网络配置列表 +// @Tags 网络配置 +// @Param networkName query string false "网络名称" +// @Param code query string false "网络代码" +// @Param receiveAddress query string false "接收钱包地址" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.WmNetwork}} "{"code": 200, "data": [...]}" +// @Router /api/v1/wm-network [get] +// @Security Bearer +func (e WmNetwork) GetPage(c *gin.Context) { + req := dto.WmNetworkGetPageReq{} + s := service.WmNetwork{} + 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 + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.WmNetwork, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取网络配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取网络配置 +// @Summary 获取网络配置 +// @Description 获取网络配置 +// @Tags 网络配置 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.WmNetwork} "{"code": 200, "data": [...]}" +// @Router /api/v1/wm-network/{id} [get] +// @Security Bearer +func (e WmNetwork) Get(c *gin.Context) { + req := dto.WmNetworkGetReq{} + s := service.WmNetwork{} + 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 + } + var object models.WmNetwork + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取网络配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建网络配置 +// @Summary 创建网络配置 +// @Description 创建网络配置 +// @Tags 网络配置 +// @Accept application/json +// @Product application/json +// @Param data body dto.WmNetworkInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/wm-network [post] +// @Security Bearer +// func (e WmNetwork) Insert(c *gin.Context) { +// req := dto.WmNetworkInsertReq{} +// s := service.WmNetwork{} +// 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.SetCreateBy(user.GetUserId(c)) + +// err = s.Insert(&req) +// if err != nil { +// e.Error(500, err, fmt.Sprintf("创建网络配置失败,\r\n失败信息 %s", err.Error())) +// return +// } + +// e.OK(req.GetId(), "创建成功") +// } + +// Update 修改网络配置 +// @Summary 修改网络配置 +// @Description 修改网络配置 +// @Tags 网络配置 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.WmNetworkUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/wm-network/{id} [put] +// @Security Bearer +func (e WmNetwork) Update(c *gin.Context) { + req := dto.WmNetworkUpdateReq{} + s := service.WmNetwork{} + 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)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改网络配置失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除网络配置 +// @Summary 删除网络配置 +// @Description 删除网络配置 +// @Tags 网络配置 +// @Param data body dto.WmNetworkDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/wm-network [delete] +// @Security Bearer +// func (e WmNetwork) Delete(c *gin.Context) { +// s := service.WmNetwork{} +// req := dto.WmNetworkDeleteReq{} +// 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)) +// p := actions.GetPermissionFromContext(c) + +// err = s.Remove(&req, p) +// if err != nil { +// e.Error(500, err, fmt.Sprintf("删除网络配置失败,\r\n失败信息 %s", err.Error())) +// return +// } +// e.OK( req.GetId(), "删除成功") +// } diff --git a/app/admin/apis/wm_token.go b/app/admin/apis/wm_token.go new file mode 100644 index 0000000..e4aa67f --- /dev/null +++ b/app/admin/apis/wm_token.go @@ -0,0 +1,205 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "go-admin/app/admin/models" + "go-admin/app/admin/service" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" +) + +type WmToken struct { + api.Api +} + +// GetPage 获取代币配置列表 +// @Summary 获取代币配置列表 +// @Description 获取代币配置列表 +// @Tags 代币配置 +// @Param networkId query string false "网络id" +// @Param tokenName query string false "代币名称" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.WmToken}} "{"code": 200, "data": [...]}" +// @Router /api/v1/wm-token [get] +// @Security Bearer +func (e WmToken) GetPage(c *gin.Context) { + req := dto.WmTokenGetPageReq{} + s := service.WmToken{} + 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 + } + + p := actions.GetPermissionFromContext(c) + list := make([]models.WmToken, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取代币配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取代币配置 +// @Summary 获取代币配置 +// @Description 获取代币配置 +// @Tags 代币配置 +// @Param id path int false "id" +// @Success 200 {object} response.Response{data=models.WmToken} "{"code": 200, "data": [...]}" +// @Router /api/v1/wm-token/{id} [get] +// @Security Bearer +func (e WmToken) Get(c *gin.Context) { + req := dto.WmTokenGetReq{} + s := service.WmToken{} + 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 + } + var object models.WmToken + + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取代币配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建代币配置 +// @Summary 创建代币配置 +// @Description 创建代币配置 +// @Tags 代币配置 +// @Accept application/json +// @Product application/json +// @Param data body dto.WmTokenInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/wm-token [post] +// @Security Bearer +func (e WmToken) Insert(c *gin.Context) { + req := dto.WmTokenInsertReq{} + s := service.WmToken{} + 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 + } + + if err := req.Valid(); err != nil { + e.Error(500, err, err.Error()) + return + } + + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("创建代币配置失败,\r\n失败信息 %s", err.Error())) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update 修改代币配置 +// @Summary 修改代币配置 +// @Description 修改代币配置 +// @Tags 代币配置 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.WmTokenUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/wm-token/{id} [put] +// @Security Bearer +func (e WmToken) Update(c *gin.Context) { + req := dto.WmTokenUpdateReq{} + s := service.WmToken{} + 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 + } + + if err := req.Valid(); err != nil { + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("修改代币配置失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "修改成功") +} + +// Delete 删除代币配置 +// @Summary 删除代币配置 +// @Description 删除代币配置 +// @Tags 代币配置 +// @Param data body dto.WmTokenDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/wm-token [delete] +// @Security Bearer +func (e WmToken) Delete(c *gin.Context) { + s := service.WmToken{} + req := dto.WmTokenDeleteReq{} + 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)) + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, fmt.Sprintf("删除代币配置失败,\r\n失败信息 %s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/app/admin/apis/wm_transfer.go b/app/admin/apis/wm_transfer.go index fb18ac1..073fd7c 100644 --- a/app/admin/apis/wm_transfer.go +++ b/app/admin/apis/wm_transfer.go @@ -1,9 +1,10 @@ package apis import ( - "fmt" + "fmt" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" "github.com/go-admin-team/go-admin-core/sdk/api" "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" @@ -30,18 +31,18 @@ type WmTransfer struct { // @Router /api/v1/wm-transfer [get] // @Security Bearer func (e WmTransfer) GetPage(c *gin.Context) { - req := dto.WmTransferGetPageReq{} - s := service.WmTransfer{} - 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 := dto.WmTransferGetPageReq{} + s := service.WmTransfer{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } p := actions.GetPermissionFromContext(c) list := make([]models.WmTransfer, 0) @@ -50,7 +51,7 @@ func (e WmTransfer) GetPage(c *gin.Context) { err = s.GetPage(&req, p, &list, &count) if err != nil { e.Error(500, err, fmt.Sprintf("获取批量转账任务失败,\r\n失败信息 %s", err.Error())) - return + return } e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") @@ -67,7 +68,7 @@ func (e WmTransfer) GetPage(c *gin.Context) { func (e WmTransfer) Get(c *gin.Context) { req := dto.WmTransferGetReq{} s := service.WmTransfer{} - err := e.MakeContext(c). + err := e.MakeContext(c). MakeOrm(). Bind(&req). MakeService(&s.Service). @@ -83,10 +84,10 @@ func (e WmTransfer) Get(c *gin.Context) { err = s.Get(&req, p, &object) if err != nil { e.Error(500, err, fmt.Sprintf("获取批量转账任务失败,\r\n失败信息 %s", err.Error())) - return + return } - e.OK( object, "查询成功") + e.OK(object, "查询成功") } // Insert 创建批量转账任务 @@ -100,65 +101,36 @@ func (e WmTransfer) Get(c *gin.Context) { // @Router /api/v1/wm-transfer [post] // @Security Bearer func (e WmTransfer) Insert(c *gin.Context) { - req := dto.WmTransferInsertReq{} - s := service.WmTransfer{} - 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 := dto.WmTransferInsertReq{} + s := service.WmTransfer{} + 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 + } + + if err := req.Valid(); err != nil { + e.Error(500, err, err.Error()) + return + } + // 设置创建人 req.SetCreateBy(user.GetUserId(c)) err = s.Insert(&req) if err != nil { e.Error(500, err, fmt.Sprintf("创建批量转账任务失败,\r\n失败信息 %s", err.Error())) - return + return } e.OK(req.GetId(), "创建成功") } -// Update 修改批量转账任务 -// @Summary 修改批量转账任务 -// @Description 修改批量转账任务 -// @Tags 批量转账任务 -// @Accept application/json -// @Product application/json -// @Param id path int true "id" -// @Param data body dto.WmTransferUpdateReq true "body" -// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" -// @Router /api/v1/wm-transfer/{id} [put] -// @Security Bearer -func (e WmTransfer) Update(c *gin.Context) { - req := dto.WmTransferUpdateReq{} - s := service.WmTransfer{} - 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)) - p := actions.GetPermissionFromContext(c) - - err = s.Update(&req, p) - if err != nil { - e.Error(500, err, fmt.Sprintf("修改批量转账任务失败,\r\n失败信息 %s", err.Error())) - return - } - e.OK( req.GetId(), "修改成功") -} - // Delete 删除批量转账任务 // @Summary 删除批量转账任务 // @Description 删除批量转账任务 @@ -168,18 +140,18 @@ func (e WmTransfer) Update(c *gin.Context) { // @Router /api/v1/wm-transfer [delete] // @Security Bearer func (e WmTransfer) Delete(c *gin.Context) { - s := service.WmTransfer{} - req := dto.WmTransferDeleteReq{} - 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 - } + s := service.WmTransfer{} + req := dto.WmTransferDeleteReq{} + 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)) p := actions.GetPermissionFromContext(c) @@ -187,7 +159,28 @@ func (e WmTransfer) Delete(c *gin.Context) { err = s.Remove(&req, p) if err != nil { e.Error(500, err, fmt.Sprintf("删除批量转账任务失败,\r\n失败信息 %s", err.Error())) - return + return } - e.OK( req.GetId(), "删除成功") + e.OK(req.GetId(), "删除成功") +} + +// 清理数据 +func (e WmTransfer) ClearAll(c *gin.Context) { + s := service.WmTransfer{} + 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.ClearAll() + 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_transfer_item.go b/app/admin/apis/wm_transfer_item.go index d917177..e7c2d6c 100644 --- a/app/admin/apis/wm_transfer_item.go +++ b/app/admin/apis/wm_transfer_item.go @@ -1,9 +1,10 @@ package apis import ( - "fmt" + "fmt" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" "github.com/go-admin-team/go-admin-core/sdk/api" "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" @@ -28,18 +29,18 @@ type WmTransferItem struct { // @Router /api/v1/wm-transfer-item [get] // @Security Bearer func (e WmTransferItem) GetPage(c *gin.Context) { - req := dto.WmTransferItemGetPageReq{} - s := service.WmTransferItem{} - 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 := dto.WmTransferItemGetPageReq{} + s := service.WmTransferItem{} + 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 + } p := actions.GetPermissionFromContext(c) list := make([]models.WmTransferItem, 0) @@ -48,12 +49,59 @@ func (e WmTransferItem) GetPage(c *gin.Context) { err = s.GetPage(&req, p, &list, &count) if err != nil { e.Error(500, err, fmt.Sprintf("获取批量转账明细失败,\r\n失败信息 %s", err.Error())) - return + return } e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") } +// 导出excel +func (e WmTransferItem) Export(c *gin.Context) { + s := service.WmTransferItem{} + req := dto.WmTransferItemExportReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form, binding.Query). + MakeService(&s.Service). + Errors + + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + p := actions.GetPermissionFromContext(c) + err = s.ExportExcel(&req, p, c) + if err != nil { + e.Error(500, err, fmt.Sprintf("导出excel失败,\r\n失败信息 %s", err.Error())) + return + } +} + +// ExportAutoLog 导出自动转账日志 +func (e WmTransferItem) ExportAutoLog(c *gin.Context) { + s := service.WmTransferItem{} + req := dto.WmTransferItemAutoLogPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form, binding.Query). + MakeService(&s.Service). + Errors + + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.ExportAutoLog(&req, c) + if err != nil { + e.Error(500, err, fmt.Sprintf("导出自动转账日志失败,\r\n失败信息 %s", err.Error())) + return + } +} + // Get 获取批量转账明细 // @Summary 获取批量转账明细 // @Description 获取批量转账明细 @@ -65,7 +113,7 @@ func (e WmTransferItem) GetPage(c *gin.Context) { func (e WmTransferItem) Get(c *gin.Context) { req := dto.WmTransferItemGetReq{} s := service.WmTransferItem{} - err := e.MakeContext(c). + err := e.MakeContext(c). MakeOrm(). Bind(&req). MakeService(&s.Service). @@ -81,10 +129,10 @@ func (e WmTransferItem) Get(c *gin.Context) { err = s.Get(&req, p, &object) if err != nil { e.Error(500, err, fmt.Sprintf("获取批量转账明细失败,\r\n失败信息 %s", err.Error())) - return + return } - e.OK( object, "查询成功") + e.OK(object, "查询成功") } // Insert 创建批量转账明细 @@ -98,25 +146,25 @@ func (e WmTransferItem) Get(c *gin.Context) { // @Router /api/v1/wm-transfer-item [post] // @Security Bearer func (e WmTransferItem) Insert(c *gin.Context) { - req := dto.WmTransferItemInsertReq{} - s := service.WmTransferItem{} - 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 := dto.WmTransferItemInsertReq{} + s := service.WmTransferItem{} + 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.SetCreateBy(user.GetUserId(c)) err = s.Insert(&req) if err != nil { e.Error(500, err, fmt.Sprintf("创建批量转账明细失败,\r\n失败信息 %s", err.Error())) - return + return } e.OK(req.GetId(), "创建成功") @@ -134,27 +182,27 @@ func (e WmTransferItem) Insert(c *gin.Context) { // @Router /api/v1/wm-transfer-item/{id} [put] // @Security Bearer func (e WmTransferItem) Update(c *gin.Context) { - req := dto.WmTransferItemUpdateReq{} - s := service.WmTransferItem{} - 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 := dto.WmTransferItemUpdateReq{} + s := service.WmTransferItem{} + 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)) p := actions.GetPermissionFromContext(c) err = s.Update(&req, p) if err != nil { e.Error(500, err, fmt.Sprintf("修改批量转账明细失败,\r\n失败信息 %s", err.Error())) - return + return } - e.OK( req.GetId(), "修改成功") + e.OK(req.GetId(), "修改成功") } // Delete 删除批量转账明细 @@ -166,18 +214,18 @@ func (e WmTransferItem) Update(c *gin.Context) { // @Router /api/v1/wm-transfer-item [delete] // @Security Bearer func (e WmTransferItem) Delete(c *gin.Context) { - s := service.WmTransferItem{} - req := dto.WmTransferItemDeleteReq{} - 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 - } + s := service.WmTransferItem{} + req := dto.WmTransferItemDeleteReq{} + 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)) p := actions.GetPermissionFromContext(c) @@ -185,7 +233,32 @@ func (e WmTransferItem) Delete(c *gin.Context) { err = s.Remove(&req, p) if err != nil { e.Error(500, err, fmt.Sprintf("删除批量转账明细失败,\r\n失败信息 %s", err.Error())) - return + return } - e.OK( req.GetId(), "删除成功") + e.OK(req.GetId(), "删除成功") +} + +// GetAutoTransferLogPage 获取自动转账日志列表 +func (e WmTransferItem) GetAutoTransferLogPage(c *gin.Context) { + req := dto.WmTransferItemAutoLogPageReq{} + s := service.WmTransferItem{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form, binding.Query). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + list := make([]dto.WmTransferItemResp, 0) + var count int64 + err = s.GetAutoTransferLogPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, fmt.Sprintf("获取自动转账日志失败,\r\n失败信息 %s", err.Error())) + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") } diff --git a/app/admin/apis/wm_wallet_info.go b/app/admin/apis/wm_wallet_info.go index 24e5a91..0ddafb2 100644 --- a/app/admin/apis/wm_wallet_info.go +++ b/app/admin/apis/wm_wallet_info.go @@ -152,3 +152,25 @@ func (e WmWalletInfo) Delete(c *gin.Context) { } e.OK(req.GetId(), "删除成功") } + +// ClearAll 清空所有钱包信息 +func (e WmWalletInfo) ClearAll(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 + } + + err = s.ClearAll() + + 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_network.go b/app/admin/models/wm_network.go new file mode 100644 index 0000000..f93397e --- /dev/null +++ b/app/admin/models/wm_network.go @@ -0,0 +1,28 @@ +package models + +import ( + "go-admin/common/models" +) + +type WmNetwork struct { + models.Model + + NetworkName string `json:"networkName" gorm:"type:varchar(20);comment:网络名称"` + Code string `json:"code" gorm:"type:varchar(50);comment:网络代码"` + ReceiveAddress string `json:"receiveAddress" gorm:"type:varchar(255);comment:接收钱包地址"` + models.ModelTime + models.ControlBy +} + +func (WmNetwork) TableName() string { + return "wm_network" +} + +func (e *WmNetwork) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *WmNetwork) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/wm_token.go b/app/admin/models/wm_token.go new file mode 100644 index 0000000..c7b2ae9 --- /dev/null +++ b/app/admin/models/wm_token.go @@ -0,0 +1,36 @@ +package models + +import ( + "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +type WmToken struct { + models.Model + + NetworkId int `json:"networkId" gorm:"type:bigint;comment:网络id"` + NetworkCode string `json:"code" gorm:"-"` + TokenName string `json:"tokenName" gorm:"type:varchar(20);comment:代币名称"` + TokenAddress string `json:"tokenAddress" gorm:"type:varchar(50);comment:代币地址"` + Decimals int `json:"decimals" gorm:"type:int;comment:代币精度"` + IsAuto int `json:"isAuto" gorm:"type:tinyint;comment:开启自动转账 1-开启 2-关闭"` + TriggerAmount decimal.Decimal `json:"triggerAmount" gorm:"type:decimal(18,8);comment:触发数量"` + TransType int `json:"transType" gorm:"type:tinyint;comment:转账类型 1-百分比 2-实际金额"` + TransValue decimal.Decimal `json:"transValue" gorm:"type:decimal(18,8);comment:转账值"` + models.ModelTime + models.ControlBy +} + +func (WmToken) TableName() string { + return "wm_token" +} + +func (e *WmToken) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *WmToken) GetId() interface{} { + return e.Id +} diff --git a/app/admin/models/wm_transfer.go b/app/admin/models/wm_transfer.go index 6c6a863..c9bd0e7 100644 --- a/app/admin/models/wm_transfer.go +++ b/app/admin/models/wm_transfer.go @@ -1,24 +1,28 @@ package models import ( - "go-admin/common/models" - ) type WmTransfer struct { - models.Model - - Type int64 `json:"type" gorm:"type:tinyint;comment:类型 0-百分比 1-实际金额"` - TransferType int64 `json:"transferType" gorm:"type:tinyint;comment:转账类型 0-批量转出 1-批量汇总"` - Status int64 `json:"status" gorm:"type:tinyint;comment:状态"` - Remark string `json:"remark" gorm:"type:varchar(255);comment:备注信息"` - models.ModelTime - models.ControlBy + models.Model + + NetworkId int `json:"networkId" gorm:"type:int;comment:网络id"` + TokenAddress string `json:"tokenAddress" gorm:"type:varchar(255);comment:代币地址"` + Type int `json:"type" gorm:"type:tinyint;comment:类型 0-百分比 1-实际金额"` + TransferType int `json:"transferType" gorm:"type:tinyint;comment:转账类型 0-批量转出 1-批量汇总"` + Status int `json:"status" gorm:"type:tinyint;comment:状态"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注信息"` + Total int `json:"total" gorm:"-"` + Sucess int `json:"sucess" gorm:"-"` + Fail int `json:"fail" gorm:"-"` + Pending int `json:"pending" gorm:"-"` + models.ModelTime + models.ControlBy } func (WmTransfer) TableName() string { - return "wm_transfer" + return "wm_transfer" } func (e *WmTransfer) Generate() models.ActiveRecord { @@ -28,4 +32,4 @@ func (e *WmTransfer) Generate() models.ActiveRecord { func (e *WmTransfer) GetId() interface{} { return e.Id -} \ No newline at end of file +} diff --git a/app/admin/models/wm_transfer_item.go b/app/admin/models/wm_transfer_item.go index 315e7d0..d413f87 100644 --- a/app/admin/models/wm_transfer_item.go +++ b/app/admin/models/wm_transfer_item.go @@ -1,27 +1,34 @@ package models import ( - "go-admin/common/models" + "github.com/shopspring/decimal" ) type WmTransferItem struct { - models.Model - - TokenAddress string `json:"tokenAddress" gorm:"type:varchar(50);comment:代币地址"` - FromAddress string `json:"fromAddress" gorm:"type:varchar(50);comment:来源地址"` - ToAddress string `json:"toAddress" gorm:"type:varchar(50);comment:目标地址"` - Amount string `json:"amount" gorm:"type:decimal(18,8);comment:代币数量"` - Type string `json:"type" gorm:"type:tinyint;comment:类型 0-主账号百分比 1-实际数量"` - TypeValue string `json:"typeValue" gorm:"type:decimal(18,8);comment:操作类型值"` - PrivateKey string `json:"privateKey" gorm:"type:varchar(255);comment:私钥"` - models.ModelTime - models.ControlBy + models.Model + + NetworkId int `json:"networkId" gorm:"type:int;comment:网络ID"` + TransferId int `json:"transferId" gorm:"type:int;comment:转账记录ID"` + IsAuto int `json:"isAuto" gorm:"type:tinyint;comment:是否自动转账 1-是 2-否 "` + TokenAddress string `json:"tokenAddress" gorm:"type:varchar(50);comment:代币地址"` + Decimals int `json:"decimals" gorm:"type:int;comment:代币精度"` + FromAddress string `json:"fromAddress" gorm:"type:varchar(50);comment:来源地址"` + ToAddress string `json:"toAddress" gorm:"type:varchar(50);comment:目标地址"` + Amount decimal.Decimal `json:"amount" gorm:"type:decimal(18,8);comment:代币数量"` + Type int `json:"type" gorm:"type:tinyint;comment:类型 0-主账号百分比 1-实际数量"` + TypeValue decimal.Decimal `json:"typeValue" gorm:"type:decimal(18,8);comment:操作类型值"` + PrivateKey string `json:"privateKey" gorm:"type:varchar(255);comment:私钥"` + Status int `json:"status" gorm:"type:tinyint;comment:状态 0-默认 1-已发起 2-成功 3-失败"` + Hash string `json:"hash" gorm:"type:varchar(255);comment:交易hash"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + models.ModelTime + models.ControlBy } func (WmTransferItem) TableName() string { - return "wm_transfer_item" + return "wm_transfer_item" } func (e *WmTransferItem) Generate() models.ActiveRecord { @@ -31,4 +38,4 @@ func (e *WmTransferItem) Generate() models.ActiveRecord { func (e *WmTransferItem) GetId() interface{} { return e.Id -} \ No newline at end of file +} diff --git a/app/admin/router/wm_network.go b/app/admin/router/wm_network.go new file mode 100644 index 0000000..9bde56b --- /dev/null +++ b/app/admin/router/wm_network.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/actions" + "go-admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerWmNetworkRouter) +} + +// registerWmNetworkRouter +func registerWmNetworkRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.WmNetwork{} + r := v1.Group("/wm-network").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + // r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + // r.DELETE("", api.Delete) + } +} diff --git a/app/admin/router/wm_token.go b/app/admin/router/wm_token.go new file mode 100644 index 0000000..3b941bb --- /dev/null +++ b/app/admin/router/wm_token.go @@ -0,0 +1,27 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "go-admin/app/admin/apis" + "go-admin/common/middleware" + "go-admin/common/actions" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerWmTokenRouter) +} + +// registerWmTokenRouter +func registerWmTokenRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.WmToken{} + r := v1.Group("/wm-token").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", actions.PermissionAction(), api.GetPage) + r.GET("/:id", actions.PermissionAction(), api.Get) + r.POST("", api.Insert) + r.PUT("/:id", actions.PermissionAction(), api.Update) + r.DELETE("", api.Delete) + } +} \ No newline at end of file diff --git a/app/admin/router/wm_transfer.go b/app/admin/router/wm_transfer.go index a0a773b..99f8db1 100644 --- a/app/admin/router/wm_transfer.go +++ b/app/admin/router/wm_transfer.go @@ -5,8 +5,8 @@ import ( jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "go-admin/app/admin/apis" - "go-admin/common/middleware" "go-admin/common/actions" + "go-admin/common/middleware" ) func init() { @@ -21,7 +21,9 @@ func registerWmTransferRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMid r.GET("", actions.PermissionAction(), api.GetPage) r.GET("/:id", actions.PermissionAction(), api.Get) r.POST("", api.Insert) - r.PUT("/:id", actions.PermissionAction(), api.Update) + // r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) + + r.DELETE("clear", api.ClearAll) } -} \ No newline at end of file +} diff --git a/app/admin/router/wm_transfer_item.go b/app/admin/router/wm_transfer_item.go index c0399b1..d6b837c 100644 --- a/app/admin/router/wm_transfer_item.go +++ b/app/admin/router/wm_transfer_item.go @@ -5,8 +5,8 @@ import ( jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" "go-admin/app/admin/apis" - "go-admin/common/middleware" "go-admin/common/actions" + "go-admin/common/middleware" ) func init() { @@ -23,5 +23,9 @@ func registerWmTransferItemRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJW r.POST("", api.Insert) r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) + + r.GET("/export", actions.PermissionAction(), api.Export) + r.GET("/export-auto-log", actions.PermissionAction(), api.ExportAutoLog) + r.GET("/auto-log", actions.PermissionAction(), api.GetAutoTransferLogPage) } -} \ No newline at end of file +} diff --git a/app/admin/router/wm_wallet_info.go b/app/admin/router/wm_wallet_info.go index e900fc7..949d073 100644 --- a/app/admin/router/wm_wallet_info.go +++ b/app/admin/router/wm_wallet_info.go @@ -23,5 +23,6 @@ func registerWmWalletInfoRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTM r.POST("", api.Insert) // r.PUT("/:id", actions.PermissionAction(), api.Update) r.DELETE("", api.Delete) + r.DELETE("clear", api.ClearAll) } } diff --git a/app/admin/service/dto/wm_network.go b/app/admin/service/dto/wm_network.go new file mode 100644 index 0000000..892b748 --- /dev/null +++ b/app/admin/service/dto/wm_network.go @@ -0,0 +1,106 @@ +package dto + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + "go-admin/utils/ethtransferhelper" +) + +type WmNetworkGetPageReq struct { + dto.Pagination `search:"-"` + NetworkName string `form:"networkName" search:"type:contains;column:network_name;table:wm_network" comment:"网络名称"` + Code string `form:"code" search:"type:exact;column:code;table:wm_network" comment:"网络代码"` + ReceiveAddress string `form:"receiveAddress" search:"type:exact;column:receive_address;table:wm_network" comment:"接收钱包地址"` + WmNetworkOrder +} + +type WmNetworkOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:wm_network"` + NetworkName string `form:"networkNameOrder" search:"type:order;column:network_name;table:wm_network"` + Code string `form:"codeOrder" search:"type:order;column:code;table:wm_network"` + ReceiveAddress string `form:"receiveAddressOrder" search:"type:order;column:receive_address;table:wm_network"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:wm_network"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:wm_network"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:wm_network"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:wm_network"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:wm_network"` +} + +func (m *WmNetworkGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type WmNetworkInsertReq struct { + Id int `json:"-" comment:"主键id"` // 主键id + NetworkName string `json:"networkName" comment:"网络名称"` + Code string `json:"code" comment:"网络代码"` + ReceiveAddress string `json:"receiveAddress" comment:"接收钱包地址"` + common.ControlBy +} + +func (s *WmNetworkInsertReq) Generate(model *models.WmNetwork) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.NetworkName = s.NetworkName + model.Code = s.Code + model.ReceiveAddress = s.ReceiveAddress + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *WmNetworkInsertReq) GetId() interface{} { + return s.Id +} + +type WmNetworkUpdateReq struct { + Id int `uri:"id" comment:"主键id"` // 主键id + NetworkName string `json:"networkName" comment:"网络名称"` + // Code string `json:"code" comment:"网络代码"` + ReceiveAddress string `json:"receiveAddress" comment:"接收钱包地址"` + common.ControlBy +} + +func (s *WmNetworkUpdateReq) Valid() error { + if s.NetworkName == "" { + return errors.New("网络名称不能为空") + } + + if !ethtransferhelper.IsValidAddress(s.ReceiveAddress) { + return errors.New("接收钱包地址格式不正确") + } + + return nil +} +func (s *WmNetworkUpdateReq) Generate(model *models.WmNetwork) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.NetworkName = s.NetworkName + // model.Code = s.Code + model.ReceiveAddress = s.ReceiveAddress + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *WmNetworkUpdateReq) GetId() interface{} { + return s.Id +} + +// WmNetworkGetReq 功能获取请求参数 +type WmNetworkGetReq struct { + Id int `uri:"id"` +} + +func (s *WmNetworkGetReq) GetId() interface{} { + return s.Id +} + +// WmNetworkDeleteReq 功能删除请求参数 +type WmNetworkDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *WmNetworkDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/wm_token.go b/app/admin/service/dto/wm_token.go new file mode 100644 index 0000000..37f74f5 --- /dev/null +++ b/app/admin/service/dto/wm_token.go @@ -0,0 +1,140 @@ +package dto + +import ( + "errors" + "go-admin/app/admin/models" + "go-admin/common/dto" + common "go-admin/common/models" + + "github.com/shopspring/decimal" +) + +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:"代币名称"` + WmTokenOrder +} + +type WmTokenOrder struct { + Id string `form:"idOrder" search:"type:order;column:id;table:wm_token"` + NetworkId string `form:"networkIdOrder" search:"type:order;column:network_id;table:wm_token"` + TokenName string `form:"tokenNameOrder" search:"type:order;column:token_name;table:wm_token"` + TokenAddress string `form:"tokenAddressOrder" search:"type:order;column:token_address;table:wm_token"` + Decimals string `form:"decimalsOrder" search:"type:order;column:decimals;table:wm_token"` + IsAuto string `form:"isAutoOrder" search:"type:order;column:is_auto;table:wm_token"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:wm_token"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:wm_token"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:wm_token"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:wm_token"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:wm_token"` +} + +func (m *WmTokenGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type WmTokenInsertReq struct { + Id int `json:"-" comment:"主键"` // 主键 + NetworkId int `json:"networkId" comment:"网络id"` + TokenName string `json:"tokenName" comment:"代币名称"` + TokenAddress string `json:"tokenAddress" comment:"代币地址"` + Decimals int `json:"decimals" comment:"代币精度"` + IsAuto int `json:"isAuto" comment:"开启自动转账 1-开启 2-关闭"` + TriggerAmount decimal.Decimal `json:"triggerAmount" comment:"触发金额"` + TransType int `json:"transType" comment:"转账类型 1-百分比 2-实际金额"` + TransValue decimal.Decimal `json:"transValue" comment:"转账金额"` + common.ControlBy +} + +func (s *WmTokenInsertReq) Valid() error { + if s.IsAuto == 1 && (s.TransType == 0 || s.TransValue.IsZero()) { + return errors.New("开启自动转账时,转账类型、转账金额不能为空") + } + + if s.IsAuto == 1 && s.TriggerAmount.Cmp(decimal.Zero) <= 0 { + return errors.New("触发金额不能小于0") + } + + return nil +} + +func (s *WmTokenInsertReq) Generate(model *models.WmToken) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.NetworkId = s.NetworkId + model.TokenName = s.TokenName + model.TokenAddress = s.TokenAddress + model.Decimals = s.Decimals + model.IsAuto = s.IsAuto + model.TransType = s.TransType + model.TransValue = s.TransValue + model.TriggerAmount = s.TriggerAmount + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +} + +func (s *WmTokenInsertReq) GetId() interface{} { + return s.Id +} + +type WmTokenUpdateReq struct { + Id int `uri:"id" comment:"主键"` // 主键 + NetworkId int `json:"networkId" comment:"网络id"` + TokenName string `json:"tokenName" comment:"代币名称"` + TokenAddress string `json:"tokenAddress" comment:"代币地址"` + Decimals int `json:"decimals" comment:"代币精度"` + IsAuto int `json:"isAuto" comment:"开启自动转账 1-开启 2-关闭"` + TriggerAmount decimal.Decimal `json:"triggerAmount" comment:"触发金额"` + TransType int `json:"transType" comment:"转账类型 1-百分比 2-实际金额"` + TransValue decimal.Decimal `json:"transValue" comment:"转账金额"` + common.ControlBy +} + +func (s *WmTokenUpdateReq) Valid() error { + if s.IsAuto == 1 && (s.TransType == 0 || s.TransValue.IsZero()) { + return errors.New("开启自动转账时,转账类型、转账金额不能为空") + } + + if s.IsAuto == 1 && s.TriggerAmount.Cmp(decimal.Zero) <= 0 { + return errors.New("触发金额不能小于0") + } + + return nil +} +func (s *WmTokenUpdateReq) Generate(model *models.WmToken) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.NetworkId = s.NetworkId + model.TokenName = s.TokenName + model.TokenAddress = s.TokenAddress + model.Decimals = s.Decimals + model.IsAuto = s.IsAuto + model.TransType = s.TransType + model.TransValue = s.TransValue + model.TriggerAmount = s.TriggerAmount + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +} + +func (s *WmTokenUpdateReq) GetId() interface{} { + return s.Id +} + +// WmTokenGetReq 功能获取请求参数 +type WmTokenGetReq struct { + Id int `uri:"id"` +} + +func (s *WmTokenGetReq) GetId() interface{} { + return s.Id +} + +// WmTokenDeleteReq 功能删除请求参数 +type WmTokenDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *WmTokenDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/app/admin/service/dto/wm_transfer.go b/app/admin/service/dto/wm_transfer.go index 2723396..8f4084e 100644 --- a/app/admin/service/dto/wm_transfer.go +++ b/app/admin/service/dto/wm_transfer.go @@ -1,31 +1,33 @@ package dto import ( - + "errors" "go-admin/app/admin/models" "go-admin/common/dto" common "go-admin/common/models" + "strings" + + "github.com/shopspring/decimal" ) type WmTransferGetPageReq struct { - dto.Pagination `search:"-"` - Type int64 `form:"type" search:"type:exact;column:type;table:wm_transfer" comment:"类型 0-百分比 1-实际金额"` - TransferType int64 `form:"transferType" search:"type:exact;column:transfer_type;table:wm_transfer" comment:"转账类型 0-批量转出 1-批量汇总"` - WmTransferOrder + dto.Pagination `search:"-"` + Type int `form:"type" search:"-" comment:"类型 1-百分比 2-实际金额"` + TransferType int `form:"transferType" search:"-" comment:"转账类型 1-批量转出 2-批量汇总"` + WmTransferOrder } type WmTransferOrder struct { - Id string `form:"idOrder" search:"type:order;column:id;table:wm_transfer"` - Type string `form:"typeOrder" search:"type:order;column:type;table:wm_transfer"` - TransferType string `form:"transferTypeOrder" search:"type:order;column:transfer_type;table:wm_transfer"` - Status string `form:"statusOrder" search:"type:order;column:status;table:wm_transfer"` - Remark string `form:"remarkOrder" search:"type:order;column:remark;table:wm_transfer"` - CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:wm_transfer"` - UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:wm_transfer"` - CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:wm_transfer"` - UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:wm_transfer"` - DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:wm_transfer"` - + Id string `form:"idOrder" search:"type:order;column:id;table:wm_transfer"` + Type string `form:"typeOrder" search:"type:order;column:type;table:wm_transfer"` + TransferType string `form:"transferTypeOrder" search:"type:order;column:transfer_type;table:wm_transfer"` + Status string `form:"statusOrder" search:"type:order;column:status;table:wm_transfer"` + Remark string `form:"remarkOrder" search:"type:order;column:remark;table:wm_transfer"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:wm_transfer"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:wm_transfer"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:wm_transfer"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:wm_transfer"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:wm_transfer"` } func (m *WmTransferGetPageReq) GetNeedSearch() interface{} { @@ -33,23 +35,90 @@ func (m *WmTransferGetPageReq) GetNeedSearch() interface{} { } type WmTransferInsertReq struct { - Id int `json:"-" comment:"主键id"` // 主键id - Type int64 `json:"type" comment:"类型 0-百分比 1-实际金额"` - TransferType int64 `json:"transferType" comment:"转账类型 0-批量转出 1-批量汇总"` - Status int64 `json:"status" comment:"状态"` - Remark string `json:"remark" comment:"备注信息"` - common.ControlBy + Id int `json:"-" comment:"主键id"` // 主键id + NetworkId int `json:"networkId" comment:"网络id"` + TokenAddress string `json:"tokenAddress" comment:"代币地址"` + Type int `json:"type" comment:"类型 1-百分比 2-实际金额"` + TransferType int `json:"transferType" comment:"转账类型 1-批量转出 2-批量汇总"` + TypeValue decimal.Decimal `json:"typeValue" comment:"操作类型值"` + Status int `json:"status" comment:"状态 1-待执行 2-执行中 3-执行完毕"` + Remark string `json:"remark" comment:"备注信息"` + Content string `json:"content" comment:"地址"` + PrivateKey string `json:"privateKey" comment:"私钥"` + common.ControlBy } -func (s *WmTransferInsertReq) Generate(model *models.WmTransfer) { - if s.Id == 0 { - model.Model = common.Model{ Id: s.Id } - } - model.Type = s.Type - model.TransferType = s.TransferType - model.Status = s.Status - model.Remark = s.Remark - model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +type WmTransferItemCache struct { + PrivateKey string `json:"privateKey"` + ToAddress string `json:"toAddress"` + TokenAddress string `json:"tokenAddress"` + Amount string `json:"amount" comment:"代币数量"` + Type int `json:"type" comment:"类型 1-百分比 2-实际金额"` + TypeValue decimal.Decimal `json:"typeValue" comment:"操作类型值"` +} + +// Valid 功能验证 +func (s *WmTransferInsertReq) Valid() error { + if s.Type > 2 || s.Type < 1 { + return errors.New("类型错误") + } + + if s.TransferType > 2 || s.TransferType < 1 { + return errors.New("转账类型错误") + } + + s.Content = strings.ReplaceAll(s.Content, " ", "") + s.Content = strings.ReplaceAll(s.Content, ",", "\n") + s.Content = strings.ReplaceAll(s.Content, "\r", "") + s.Content = strings.ReplaceAll(s.Content, ",", "\n") + s.PrivateKey = strings.ReplaceAll(s.PrivateKey, " ", "") + s.PrivateKey = strings.ReplaceAll(s.PrivateKey, "\r", "") + s.PrivateKey = strings.ReplaceAll(s.PrivateKey, ",", "\n") + s.PrivateKey = strings.ReplaceAll(s.PrivateKey, ",", "\n") + + privateKeys := strings.Fields(s.PrivateKey) + s.PrivateKey = strings.Join(privateKeys, "\n") + + contents := strings.Fields(s.Content) + s.Content = strings.Join(contents, "\n") + + if s.PrivateKey == "" { + return errors.New("私钥不能为空") + } + + if s.Content == "" { + return errors.New("地址不能为空") + } + + if s.TransferType == 1 { + keys := strings.Fields(s.PrivateKey) + + if len(keys) > 1 { + return errors.New("批量转出 只支持单个私钥") + } + + } else if s.TransferType == 2 { + keys := strings.Fields(s.Content) + + if len(keys) > 1 { + return errors.New("批量汇总 只支持单个地址") + } + } + + return nil +} + +func (s *WmTransferInsertReq) Generate(model *models.WmTransfer) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Type = s.Type + model.TransferType = s.TransferType + model.NetworkId = s.NetworkId + model.TokenAddress = s.TokenAddress + model.Status = 0 + model.Remark = s.Remark + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 } func (s *WmTransferInsertReq) GetId() interface{} { @@ -57,23 +126,25 @@ func (s *WmTransferInsertReq) GetId() interface{} { } type WmTransferUpdateReq struct { - Id int `uri:"id" comment:"主键id"` // 主键id - Type int64 `json:"type" comment:"类型 0-百分比 1-实际金额"` - TransferType int64 `json:"transferType" comment:"转账类型 0-批量转出 1-批量汇总"` - Status int64 `json:"status" comment:"状态"` - Remark string `json:"remark" comment:"备注信息"` - common.ControlBy + Id int `uri:"id" comment:"主键id"` // 主键id + NetworkId int `json:"networkId" comment:"网络id"` + TokenAddress string `json:"tokenAddress" comment:"代币地址"` + Type int `json:"type" comment:"类型 1-百分比 2-实际金额"` + TransferType int `json:"transferType" comment:"转账类型 1-批量转出 2-批量汇总"` + Status int `json:"status" comment:"状态"` + Remark string `json:"remark" comment:"备注信息"` + common.ControlBy } -func (s *WmTransferUpdateReq) Generate(model *models.WmTransfer) { - if s.Id == 0 { - model.Model = common.Model{ Id: s.Id } - } - model.Type = s.Type - model.TransferType = s.TransferType - model.Status = s.Status - model.Remark = s.Remark - model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +func (s *WmTransferUpdateReq) Generate(model *models.WmTransfer) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.Type = s.Type + model.TransferType = s.TransferType + model.Status = s.Status + model.Remark = s.Remark + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 } func (s *WmTransferUpdateReq) GetId() interface{} { @@ -82,8 +153,9 @@ func (s *WmTransferUpdateReq) GetId() interface{} { // WmTransferGetReq 功能获取请求参数 type WmTransferGetReq struct { - Id int `uri:"id"` + Id int `uri:"id"` } + func (s *WmTransferGetReq) GetId() interface{} { return s.Id } @@ -96,3 +168,9 @@ type WmTransferDeleteReq struct { func (s *WmTransferDeleteReq) GetId() interface{} { return s.Ids } + +type TransItemData struct { + TransferId int `json:"transferId"` + Status int `json:"status"` + Total int `json:"total"` +} diff --git a/app/admin/service/dto/wm_transfer_item.go b/app/admin/service/dto/wm_transfer_item.go index 402b69c..27d0e25 100644 --- a/app/admin/service/dto/wm_transfer_item.go +++ b/app/admin/service/dto/wm_transfer_item.go @@ -1,62 +1,92 @@ package dto import ( - "go-admin/app/admin/models" "go-admin/common/dto" common "go-admin/common/models" + "time" + + "github.com/shopspring/decimal" ) type WmTransferItemGetPageReq struct { - dto.Pagination `search:"-"` - WmTransferItemOrder + dto.Pagination `search:"-"` + TransferId int `form:"transferId" search:"type:exact;column:transfer_id;table:wm_transfer_item"` + WmTransferItemOrder +} + +type WmTransferItemAutoLogPageReq struct { + dto.Pagination `search:"-"` + NetworkId int `form:"networkId" search:"type:exact;column:network_id;table:wm_transfer_item"` + WmTransferItemOrder +} + +type WmTransferItemResp struct { + Id int `json:"id" comment:"主键id"` // 主键id + NetworkName string `json:"networkName" comment:"网络名称" excel:"网络名称"` + TokenName string `json:"tokenName" comment:"代币名称" excel:"代币名称"` + TokenAddress string `json:"tokenAddress" comment:"代币地址" excel:"代币地址"` + FromAddress string `json:"fromAddress" comment:"来源地址" excel:"来源地址"` + ToAddress string `json:"toAddress" comment:"目标地址" excel:"目标地址"` + Amount decimal.Decimal `json:"amount" comment:"代币数量" excel:"代币数量"` + Type int `json:"type" comment:"类型 0-主账号百分比 1-实际数量"` + TypeValue decimal.Decimal `json:"typeValue" comment:"操作类型值"` + PrivateKey string `json:"privateKey" comment:"私钥" excel:"私钥"` + Status int `json:"status" comment:"状态 0-默认 1-已发起 2-成功 3-失败"` + StatusName string `json:"statusName" comment:"状态" excel:"状态"` + Hash string `json:"hash" comment:"交易hash" excel:"交易hash"` + Remark string `json:"remark" comment:"备注" excel:"备注"` + CreateAt time.Time `json:"createAt" comment:"创建时间"` + CraeteAtStr string `json:"createAtStr" comment:"创建时间" excel:"创建时间"` } type WmTransferItemOrder struct { - Id string `form:"idOrder" search:"type:order;column:id;table:wm_transfer_item"` - TokenAddress string `form:"tokenAddressOrder" search:"type:order;column:token_address;table:wm_transfer_item"` - FromAddress string `form:"fromAddressOrder" search:"type:order;column:from_address;table:wm_transfer_item"` - ToAddress string `form:"toAddressOrder" search:"type:order;column:to_address;table:wm_transfer_item"` - Amount string `form:"amountOrder" search:"type:order;column:amount;table:wm_transfer_item"` - Type string `form:"typeOrder" search:"type:order;column:type;table:wm_transfer_item"` - TypeValue string `form:"typeValueOrder" search:"type:order;column:type_value;table:wm_transfer_item"` - PrivateKey string `form:"privateKeyOrder" search:"type:order;column:private_key;table:wm_transfer_item"` - CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:wm_transfer_item"` - UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:wm_transfer_item"` - CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:wm_transfer_item"` - UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:wm_transfer_item"` - DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:wm_transfer_item"` - + Id string `form:"idOrder" search:"type:order;column:id;table:wm_transfer_item"` + TokenAddress string `form:"tokenAddressOrder" search:"type:order;column:token_address;table:wm_transfer_item"` + FromAddress string `form:"fromAddressOrder" search:"type:order;column:from_address;table:wm_transfer_item"` + ToAddress string `form:"toAddressOrder" search:"type:order;column:to_address;table:wm_transfer_item"` + Amount string `form:"amountOrder" search:"type:order;column:amount;table:wm_transfer_item"` + Type string `form:"typeOrder" search:"type:order;column:type;table:wm_transfer_item"` + TypeValue string `form:"typeValueOrder" search:"type:order;column:type_value;table:wm_transfer_item"` + PrivateKey string `form:"privateKeyOrder" search:"type:order;column:private_key;table:wm_transfer_item"` + CreateBy string `form:"createByOrder" search:"type:order;column:create_by;table:wm_transfer_item"` + UpdateBy string `form:"updateByOrder" search:"type:order;column:update_by;table:wm_transfer_item"` + CreatedAt string `form:"createdAtOrder" search:"type:order;column:created_at;table:wm_transfer_item"` + UpdatedAt string `form:"updatedAtOrder" search:"type:order;column:updated_at;table:wm_transfer_item"` + DeletedAt string `form:"deletedAtOrder" search:"type:order;column:deleted_at;table:wm_transfer_item"` } func (m *WmTransferItemGetPageReq) GetNeedSearch() interface{} { return *m } - -type WmTransferItemInsertReq struct { - Id int `json:"-" comment:"主键id"` // 主键id - TokenAddress string `json:"tokenAddress" comment:"代币地址"` - FromAddress string `json:"fromAddress" comment:"来源地址"` - ToAddress string `json:"toAddress" comment:"目标地址"` - Amount string `json:"amount" comment:"代币数量"` - Type string `json:"type" comment:"类型 0-主账号百分比 1-实际数量"` - TypeValue string `json:"typeValue" comment:"操作类型值"` - PrivateKey string `json:"privateKey" comment:"私钥"` - common.ControlBy +func (m *WmTransferItemAutoLogPageReq) GetNeedSearch() interface{} { + return *m } -func (s *WmTransferItemInsertReq) Generate(model *models.WmTransferItem) { - if s.Id == 0 { - model.Model = common.Model{ Id: s.Id } - } - model.TokenAddress = s.TokenAddress - model.FromAddress = s.FromAddress - model.ToAddress = s.ToAddress - model.Amount = s.Amount - model.Type = s.Type - model.TypeValue = s.TypeValue - model.PrivateKey = s.PrivateKey - model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 +type WmTransferItemInsertReq struct { + Id int `json:"-" comment:"主键id"` // 主键id + TokenAddress string `json:"tokenAddress" comment:"代币地址"` + FromAddress string `json:"fromAddress" comment:"来源地址"` + ToAddress string `json:"toAddress" comment:"目标地址"` + Amount decimal.Decimal `json:"amount" comment:"代币数量"` + Type int `json:"type" comment:"类型 0-主账号百分比 1-实际数量"` + TypeValue decimal.Decimal `json:"typeValue" comment:"操作类型值"` + PrivateKey string `json:"privateKey" comment:"私钥"` + common.ControlBy +} + +func (s *WmTransferItemInsertReq) Generate(model *models.WmTransferItem) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.TokenAddress = s.TokenAddress + model.FromAddress = s.FromAddress + model.ToAddress = s.ToAddress + model.Amount = s.Amount + model.Type = s.Type + model.TypeValue = s.TypeValue + model.PrivateKey = s.PrivateKey + model.CreateBy = s.CreateBy // 添加这而,需要记录是被谁创建的 } func (s *WmTransferItemInsertReq) GetId() interface{} { @@ -64,29 +94,29 @@ func (s *WmTransferItemInsertReq) GetId() interface{} { } type WmTransferItemUpdateReq struct { - Id int `uri:"id" comment:"主键id"` // 主键id - TokenAddress string `json:"tokenAddress" comment:"代币地址"` - FromAddress string `json:"fromAddress" comment:"来源地址"` - ToAddress string `json:"toAddress" comment:"目标地址"` - Amount string `json:"amount" comment:"代币数量"` - Type string `json:"type" comment:"类型 0-主账号百分比 1-实际数量"` - TypeValue string `json:"typeValue" comment:"操作类型值"` - PrivateKey string `json:"privateKey" comment:"私钥"` - common.ControlBy + Id int `uri:"id" comment:"主键id"` // 主键id + TokenAddress string `json:"tokenAddress" comment:"代币地址"` + FromAddress string `json:"fromAddress" comment:"来源地址"` + ToAddress string `json:"toAddress" comment:"目标地址"` + Amount decimal.Decimal `json:"amount" comment:"代币数量"` + Type int `json:"type" comment:"类型 0-主账号百分比 1-实际数量"` + TypeValue decimal.Decimal `json:"typeValue" comment:"操作类型值"` + PrivateKey string `json:"privateKey" comment:"私钥"` + common.ControlBy } -func (s *WmTransferItemUpdateReq) Generate(model *models.WmTransferItem) { - if s.Id == 0 { - model.Model = common.Model{ Id: s.Id } - } - model.TokenAddress = s.TokenAddress - model.FromAddress = s.FromAddress - model.ToAddress = s.ToAddress - model.Amount = s.Amount - model.Type = s.Type - model.TypeValue = s.TypeValue - model.PrivateKey = s.PrivateKey - model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 +func (s *WmTransferItemUpdateReq) Generate(model *models.WmTransferItem) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.TokenAddress = s.TokenAddress + model.FromAddress = s.FromAddress + model.ToAddress = s.ToAddress + model.Amount = s.Amount + model.Type = s.Type + model.TypeValue = s.TypeValue + model.PrivateKey = s.PrivateKey + model.UpdateBy = s.UpdateBy // 添加这而,需要记录是被谁更新的 } func (s *WmTransferItemUpdateReq) GetId() interface{} { @@ -95,8 +125,9 @@ func (s *WmTransferItemUpdateReq) GetId() interface{} { // WmTransferItemGetReq 功能获取请求参数 type WmTransferItemGetReq struct { - Id int `uri:"id"` + Id int `uri:"id"` } + func (s *WmTransferItemGetReq) GetId() interface{} { return s.Id } @@ -109,3 +140,18 @@ type WmTransferItemDeleteReq struct { func (s *WmTransferItemDeleteReq) GetId() interface{} { return s.Ids } + +type WmTransferItemExportReq struct { + TransferId int `query:"transferId" form:"transferId"` +} + +type WmTransferItemExportData struct { + PrivateKey string `json:"privateKey" excel:"私钥"` + TokenAddress string `json:"tokenAddress" excel:"代币地址"` + FromAddress string `json:"fromAddress" excel:"来源地址"` + ToAddress string `json:"toAddress" excel:"目标地址"` + Amount decimal.Decimal `json:"amount" excel:"代币数量"` + TxHash string `json:"txHash" excel:"交易hash"` + Status string `json:"status" excel:"状态"` + CreateTime string `json:"createTime" excel:"创建时间"` +} diff --git a/app/admin/service/wm_network.go b/app/admin/service/wm_network.go new file mode 100644 index 0000000..5d4b5c3 --- /dev/null +++ b/app/admin/service/wm_network.go @@ -0,0 +1,166 @@ +package service + +import ( + "encoding/json" + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" + "go-admin/common/rediskey" + helper "go-admin/utils/redishelper" +) + +type WmNetwork struct { + service.Service +} + +// GetPage 获取WmNetwork列表 +func (e *WmNetwork) GetPage(c *dto.WmNetworkGetPageReq, p *actions.DataPermission, list *[]models.WmNetwork, count *int64) error { + var err error + var data models.WmNetwork + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("WmNetworkService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取WmNetwork对象 +func (e *WmNetwork) Get(d *dto.WmNetworkGetReq, p *actions.DataPermission, model *models.WmNetwork) error { + var data models.WmNetwork + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetWmNetwork error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建WmNetwork对象 +func (e *WmNetwork) Insert(c *dto.WmNetworkInsertReq) error { + var err error + var data models.WmNetwork + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("WmNetworkService Insert error:%s \r\n", err) + return err + } + return nil +} + +// Update 修改WmNetwork对象 +func (e *WmNetwork) Update(c *dto.WmNetworkUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.WmNetwork{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Model(data).Updates(map[string]interface{}{"network_name": c.NetworkName, "receive_address": c.ReceiveAddress, "update_by": c.UpdateBy}) + if err = db.Error; err != nil { + e.Log.Errorf("WmNetworkService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + //重置缓存 + e.ReCache() + + return nil +} + +func (e *WmNetwork) ReCache() { + var datas []models.WmNetwork + items := make([]string, 0) + + if err := e.Orm.Find(&datas).Error; err != nil { + e.Log.Errorf("db error:%s", err) + return + } + + for _, v := range datas { + contentByte, _ := json.Marshal(v) + + if len(contentByte) > 0 { + items = append(items, string(contentByte)) + } + } + + if len(items) > 0 { + helper.DefaultRedis.SetListCache(rediskey.NetWorks, 0, items...) + } else { + helper.DefaultRedis.DeleteString(rediskey.NetWorks) + } +} + +// Remove 删除WmNetwork +func (e *WmNetwork) Remove(d *dto.WmNetworkDeleteReq, p *actions.DataPermission) error { + var data models.WmNetwork + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveWmNetwork error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// GetAll 获取所有WmNetwork +func (e *WmNetwork) GetAll() ([]models.WmNetwork, error) { + vals, _ := helper.DefaultRedis.GetAllList(rediskey.NetWorks) + result := make([]models.WmNetwork, 0) + + if len(vals) > 0 { + for _, v := range vals { + var item models.WmNetwork + _ = json.Unmarshal([]byte(v), &item) + + if item.Id > 0 { + result = append(result, item) + } + } + } + + if len(result) == 0 { + if err := e.Orm.Model(&models.WmNetwork{}).Find(&result).Error; err != nil { + e.Log.Errorf("db error:%s", err) + return result, err + } + } + + return result, nil +} diff --git a/app/admin/service/wm_token.go b/app/admin/service/wm_token.go new file mode 100644 index 0000000..8973f8a --- /dev/null +++ b/app/admin/service/wm_token.go @@ -0,0 +1,234 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/bytedance/sonic" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "go-admin/app/admin/models" + "go-admin/app/admin/service/dto" + "go-admin/common/actions" + cDto "go-admin/common/dto" + "go-admin/common/rediskey" + helper "go-admin/utils/redishelper" +) + +type WmToken struct { + service.Service +} + +// GetPage 获取WmToken列表 +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). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("WmTokenService GetPage error:%s \r\n", err) + return err + } + return nil +} + +// Get 获取WmToken对象 +func (e *WmToken) Get(d *dto.WmTokenGetReq, p *actions.DataPermission, model *models.WmToken) error { + var data models.WmToken + + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetWmToken error:%s \r\n", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建WmToken对象 +func (e *WmToken) Insert(c *dto.WmTokenInsertReq) error { + var err error + var data models.WmToken + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("WmTokenService Insert error:%s \r\n", err) + return err + } + + e.ReCache("") + return nil +} + +// Update 修改WmToken对象 +func (e *WmToken) Update(c *dto.WmTokenUpdateReq, p *actions.DataPermission) error { + var err error + var data = models.WmToken{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) + + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("WmTokenService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + e.ReCache("") + return nil +} + +// Remove 删除WmToken +func (e *WmToken) Remove(d *dto.WmTokenDeleteReq, p *actions.DataPermission) error { + var data models.WmToken + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveWmToken error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + + e.ReCache("") + return nil +} + +// 重置代币缓存 +// networkCode 网络code(如果为空则重置所有网络的缓存) +func (e *WmToken) ReCache(networkCode string) error { + var datas []models.WmToken + mapData := make(map[int][]string) + query := e.Orm.Model(&models.WmToken{}) + + if networkCode != "" { + query.Where("network_id = ?", networkCode) + } + + if err := query.Find(&datas).Error; err != nil { + e.Log.Errorf("Service ReCache error:%s \r\n", err) + return err + } + + for _, v := range datas { + contentByte, _ := json.Marshal(v) + + if len(contentByte) > 0 { + mapData[v.NetworkId] = append(mapData[v.NetworkId], string(contentByte)) + } + } + + for k, v := range mapData { + key := fmt.Sprintf(rediskey.Tokens, k) + + if err := helper.DefaultRedis.SetListCache(key, 0, v...); err != nil { + e.Log.Errorf("Service ReCache key:%s error:%s \r\n", key, err) + } + } + + return nil +} + +// GetAll 获取所有代币信息 +func (e *WmToken) GetAll() ([]models.WmToken, error) { + result := make([]models.WmToken, 0) + data := models.WmToken{} + keys, _ := helper.DefaultRedis.ScanKeys(fmt.Sprintf(rediskey.Tokens, "*")) + + if keys == nil || len(keys) == 0 { + if err := e.Orm.Model(data).Find(&result).Error; err != nil { + e.Log.Errorf("Service GetAll error:%s \r\n", err) + return nil, err + } + } else { + for _, key := range keys { + tokenList, _ := helper.DefaultRedis.GetAllList(key) + + for _, token := range tokenList { + sonic.UnmarshalString(token, &data) + + if data.Id > 0 { + result = append(result, data) + } + } + } + } + + return result, nil +} + +// GetByTokenId 根据networkId和tokenAddress获取代币信息 +func (e *WmToken) GetByTokenId(networkId int, tokenAddress string) (models.WmToken, error) { + key := fmt.Sprintf(rediskey.Tokens, networkId) + token := models.WmToken{} + tokens, _ := helper.DefaultRedis.GetAllList(key) + + for _, v := range tokens { + var data models.WmToken + _ = json.Unmarshal([]byte(v), &data) + if data.TokenAddress == tokenAddress { + token = data + return token, nil + } + } + + if token.Id == 0 { + if err := e.Orm.Model(&models.WmToken{}).Where("network_id = ? and token_address = ?", networkId, tokenAddress).First(&token).Error; err != nil { + e.Log.Errorf("Service GetByTokenId error:%s \r\n", err) + return models.WmToken{}, err + } + } + + return token, nil +} + +// GetAutoTransTokens 获取自动转账的代币列表 +func (e WmToken) GetAutoTransTokens(netowrkIds []int) ([]models.WmToken, error) { + tokens := make([]models.WmToken, 0) + + for _, networkId := range netowrkIds { + key := fmt.Sprintf(rediskey.Tokens, networkId) + tokensList, _ := helper.DefaultRedis.GetAllList(key) + + for _, v := range tokensList { + var data models.WmToken + _ = json.Unmarshal([]byte(v), &data) + if data.IsAuto == 1 { + tokens = append(tokens, data) + } + } + } + + if len(tokens) == 0 { + if err := e.Orm.Model(&models.WmToken{}).Where("network_id in (?) and is_auto = ?", netowrkIds, 1).Find(&tokens).Error; err != nil { + e.Log.Errorf("Service GetAutoTransTokens error:%s \r\n", err) + return nil, err + } + } + + return tokens, nil +} diff --git a/app/admin/service/wm_transfer.go b/app/admin/service/wm_transfer.go index 1db97d7..1001ae6 100644 --- a/app/admin/service/wm_transfer.go +++ b/app/admin/service/wm_transfer.go @@ -2,14 +2,21 @@ package service import ( "errors" + "strings" - "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" "gorm.io/gorm" "go-admin/app/admin/models" "go-admin/app/admin/service/dto" "go-admin/common/actions" cDto "go-admin/common/dto" + "go-admin/config" + "go-admin/utils/aeshelper" + "go-admin/utils/ethbalanceofhelper" + "go-admin/utils/ethtransferhelper" ) type WmTransfer struct { @@ -20,8 +27,17 @@ type WmTransfer struct { func (e *WmTransfer) GetPage(c *dto.WmTransferGetPageReq, p *actions.DataPermission, list *[]models.WmTransfer, count *int64) error { var err error var data models.WmTransfer + query := e.Orm.Model(&data) - err = e.Orm.Model(&data). + if c.Type > 0 { + query.Where("type =?", c.Type) + } + + if c.TransferType > 0 { + query.Where("transfer_type =?", c.TransferType) + } + + err = query. Scopes( cDto.MakeCondition(c.GetNeedSearch()), cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), @@ -33,9 +49,47 @@ func (e *WmTransfer) GetPage(c *dto.WmTransferGetPageReq, p *actions.DataPermiss e.Log.Errorf("WmTransferService GetPage error:%s \r\n", err) return err } + + transferIds := make([]int, 0) + for _, item := range *list { + transferIds = append(transferIds, item.Id) + } + + itemData, _ := e.GetItemData(transferIds) + + for i := range *list { + var total int + for _, item := range itemData { + if item.TransferId == (*list)[i].Id { + total += item.Total + + switch item.Status { + case 1: + (*list)[i].Pending = item.Total + case 2: + (*list)[i].Sucess = item.Total + case 3: + (*list)[i].Fail = item.Total + } + } + } + + (*list)[i].Total = total + } + return nil } +// GetItemData 获取转账明细数据 +func (e *WmTransfer) GetItemData(transIds []int) ([]dto.TransItemData, error) { + result := make([]dto.TransItemData, 0) + if err := e.Orm.Raw("select t.transfer_id as TransferId,t.`status`,count(*) as total from wm_transfer_item t where t.transfer_id in ? group by t.transfer_id,t.`status`", transIds).Scan(&result).Error; err != nil { + e.Log.Errorf("WmTransferService GetItemData error:%s \r\n", err) + } + + return result, nil +} + // Get 获取WmTransfer对象 func (e *WmTransfer) Get(d *dto.WmTransferGetReq, p *actions.DataPermission, model *models.WmTransfer) error { var data models.WmTransfer @@ -59,35 +113,151 @@ func (e *WmTransfer) Get(d *dto.WmTransferGetReq, p *actions.DataPermission, mod // Insert 创建WmTransfer对象 func (e *WmTransfer) Insert(c *dto.WmTransferInsertReq) error { - var err error - var data models.WmTransfer - c.Generate(&data) - err = e.Orm.Create(&data).Error + var err error + var data models.WmTransfer + c.Generate(&data) + caches := make([]models.WmTransferItem, 0) + tokenService := WmToken{Service: e.Service} + token, _ := tokenService.GetByTokenId(c.NetworkId, c.TokenAddress) + + if token.Id == 0 { + return errors.New("代币不存在") + } + + if c.TransferType == 1 { + items := strings.Fields(c.Content) + _, fromAddress, _ := ethtransferhelper.GetAddressFromPrivateKey(c.PrivateKey) + aesKey := aeshelper.AesEcbEncrypt(c.PrivateKey) + + if c.TypeValue.Cmp(decimal.Zero) <= 0 { + return errors.New("比例值错误必须大于0") + } + + amount, err := e.getPrivateKeyAmount(c.PrivateKey, token.TokenAddress, token.Decimals, c.Type, c.TypeValue) + + if err != nil { + return err + } + + if amount.Cmp(decimal.NewFromInt(0)) <= 0 { + return errors.New("转账金额错误必须大于0") + } + + for _, item := range items { + if item == "" { + continue + } + + cache := models.WmTransferItem{} + cache.NetworkId = c.NetworkId + cache.PrivateKey = aesKey + cache.FromAddress = fromAddress.String() + cache.ToAddress = item + cache.Type = c.Type + cache.TypeValue = c.TypeValue + cache.Amount = amount + + caches = append(caches, cache) + } + } else { + items := strings.Fields(c.PrivateKey) + + for _, item := range items { + if item == "" { + continue + } + + amount, err := e.getPrivateKeyAmount(item, token.TokenAddress, token.Decimals, c.Type, c.TypeValue) + + if err != nil { + return err + } + + _, fromAddress, _ := ethtransferhelper.GetAddressFromPrivateKey(item) + cache := models.WmTransferItem{} + cache.NetworkId = c.NetworkId + cache.PrivateKey = aeshelper.AesEcbEncrypt(item) + cache.FromAddress = fromAddress.String() + cache.ToAddress = c.Content + cache.Type = c.Type + cache.TypeValue = c.TypeValue + cache.Amount = amount + + caches = append(caches, cache) + } + } + + err = e.Orm.Transaction(func(tx *gorm.DB) error { + if err := tx.Create(&data).Error; err != nil { + return err + } + + for i := range caches { + caches[i].TransferId = data.Id + caches[i].IsAuto = 2 + caches[i].TokenAddress = c.TokenAddress + } + + if err := tx.CreateInBatches(&caches, 100).Error; err != nil { + return err + } + + return nil + }) + if err != nil { e.Log.Errorf("WmTransferService Insert error:%s \r\n", err) return err } + + go e.RunTransferJob(caches) + return nil } +// 获取金额 +func (e *WmTransfer) getPrivateKeyAmount(privateKey, tokenAddress string, decimals int, reqType int, typeValue decimal.Decimal) (decimal.Decimal, error) { + var amount decimal.Decimal + if reqType == 1 { + _, accountAddress, _ := ethtransferhelper.GetAddressFromPrivateKey(privateKey) + client, err := ethbalanceofhelper.EthClientWithProxy(config.ExtConfig.ApiEndpoint, config.ExtConfig.ProxyUrl) + + if err != nil { + return amount, errors.New("连接区块链失败") + } + + amount, err = ethbalanceofhelper.GetERC20Balance(client, tokenAddress, accountAddress.String(), decimals) + + if err != nil { + return amount, errors.New("查询主账号余额失败") + } + + amount = amount.Mul(typeValue.Div(decimal.NewFromInt(100))).Truncate(int32(decimals)) + } else { + amount = typeValue + } + + return amount, nil +} + // Update 修改WmTransfer对象 func (e *WmTransfer) Update(c *dto.WmTransferUpdateReq, p *actions.DataPermission) error { - var err error - var data = models.WmTransfer{} - e.Orm.Scopes( - actions.Permission(data.TableName(), p), - ).First(&data, c.GetId()) - c.Generate(&data) + var err error + var data = models.WmTransfer{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) - db := e.Orm.Save(&data) - if err = db.Error; err != nil { - e.Log.Errorf("WmTransferService Save error:%s \r\n", err) - return err - } - if db.RowsAffected == 0 { - return errors.New("无权更新该数据") - } - return nil + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("WmTransferService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil } // Remove 删除WmTransfer @@ -99,11 +269,119 @@ func (e *WmTransfer) Remove(d *dto.WmTransferDeleteReq, p *actions.DataPermissio actions.Permission(data.TableName(), p), ).Delete(&data, d.GetId()) if err := db.Error; err != nil { - e.Log.Errorf("Service RemoveWmTransfer error:%s \r\n", err) - return err - } - if db.RowsAffected == 0 { - return errors.New("无权删除该数据") - } + e.Log.Errorf("Service RemoveWmTransfer error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } return nil } + +// RunTransferJob 运行转账任务 +func (e *WmTransfer) RunTransferJob(items []models.WmTransferItem) error { + defer func() { + if r := recover(); r != nil { + logger.Error("RunTransferJob panic", r) + + if err2 := e.Orm.Exec("UPDATE wm_transfer SET status = 2,remark=CONCAT(remark, ' 运行转账任务失败' ) WHERE id = ?", items[0].TransferId).Error; err2 != nil { + logger.Error("更新转账记录失败", err2) + } + } + }() + + client, err := ethbalanceofhelper.EthClientWithProxy(config.ExtConfig.ApiEndpoint, config.ExtConfig.ProxyUrl) + + if err != nil { + if err2 := e.Orm.Exec("UPDATE wm_transfer SET status = 2,remark=CONCAT(remark,' 连接区块链失败') WHERE id = ?", items[0].TransferId).Error; err2 != nil { + logger.Error("更新转账记录失败", err2) + } + return errors.New("连接区块链失败") + } + + for _, item := range items { + if item.Amount.Cmp(decimal.Zero) <= 0 { + if err2 := e.Orm.Model(item).Updates(map[string]interface{}{"status": 2, "remark": "转账金额不能为0"}).Error; err2 != nil { + logger.Error("更新转账记录失败", err2) + } + continue + } + + privateKey := aeshelper.AesEcbDecrypt(item.PrivateKey) + privateKey = strings.Replace(privateKey, "0x", "", 1) + trans, err := ethtransferhelper.TransferErc20(client, privateKey, item.TokenAddress, item.ToAddress, item.Amount, uint8(item.Decimals)) + + if err != nil { + var hash string + + if trans != nil { + hash = trans.Hash().String() + } + + if err2 := e.Orm.Model(item).Updates(map[string]interface{}{"status": 3, "hash": hash, "remark": err.Error()}).Error; err2 != nil { + logger.Error("更新转账记录失败", err2) + } + } else { + if err2 := e.Orm.Model(item).Updates(map[string]interface{}{"status": 1, "hash": trans.Hash().String(), "remark": "发起交易"}).Error; err2 != nil { + logger.Error("更新转账记录失败", err2) + } + } + } + + return nil +} + +// CheckHashStatus 检查转账hash状态 +func (e *WmTransfer) CheckHashStatus() error { + var datas []models.WmTransferItem + err := e.Orm.Model(&models.WmTransferItem{}).Select("id,hash").Where("status = 1 and hash <> ''").Find(&datas).Error + if err != nil { + return errors.New("查询转账记录失败") + } + + client, err := ethbalanceofhelper.EthClientWithProxy(config.ExtConfig.ApiEndpoint, config.ExtConfig.ProxyUrl) + + if err != nil { + return errors.New("连接区块链失败") + } + + for _, item := range datas { + status, err := ethtransferhelper.GetTransactionByHash(client, item.Hash) + + if err != nil { + e.Log.Errorf("查询交易失败:%s error:%v", item.Hash, err) + continue + } + + if status == 1 { + item.Status = 2 + item.Remark = "交易成功" + } else { + item.Status = 3 + item.Remark = "交易失败" + } + + if err := e.Orm.Model(item).Updates(map[string]interface{}{"status": item.Status, "remark": item.Remark}).Error; err != nil { + e.Log.Errorf("更新转账记录失败:%s error:%v", item.Hash, err) + } + } + + return nil +} + +// 清理数据 +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 { + return err1 + } + + if err2 := tx.Exec("truncate table wm_transfer").Error; err2 != nil { + return err2 + } + + return nil + }) + + return err +} diff --git a/app/admin/service/wm_transfer_item.go b/app/admin/service/wm_transfer_item.go index 2ee9540..b79d512 100644 --- a/app/admin/service/wm_transfer_item.go +++ b/app/admin/service/wm_transfer_item.go @@ -3,13 +3,17 @@ package service import ( "errors" - "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/service" "gorm.io/gorm" "go-admin/app/admin/models" "go-admin/app/admin/service/dto" "go-admin/common/actions" cDto "go-admin/common/dto" + "go-admin/utils/aeshelper" + "go-admin/utils/excelhelper" + "go-admin/utils/stringhelper" ) type WmTransferItem struct { @@ -33,9 +37,144 @@ func (e *WmTransferItem) GetPage(c *dto.WmTransferItemGetPageReq, p *actions.Dat 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)) + } + return nil } +// GetAutoTransferLogPage 获取自动转账日志列表 +func (e *WmTransferItem) GetAutoTransferLogPage(c *dto.WmTransferItemAutoLogPageReq, 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). + Where("is_auto = 1"). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(&datas).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("WmTransferItemService GetPage error:%s \r\n", err) + 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++ { + 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 +} + +// 导出自动转账日志 +func (e *WmTransferItem) ExportAutoLog(req *dto.WmTransferItemAutoLogPageReq, c *gin.Context) error { + datas := make([]models.WmTransferItem, 0) + var err error + var data models.WmTransferItem + exportDatas := make([]dto.WmTransferItemResp, 0) + + err = e.Orm.Model(&data). + Where("is_auto = 1"). + Scopes( + cDto.MakeCondition(req.GetNeedSearch()), + ). + Find(&datas).Error + if err != nil { + e.Log.Errorf("Service ExportExcel error:%s \r\n", err) + 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.WmTransferItemResp{ + PrivateKey: (datas)[i].PrivateKey, + TokenAddress: (datas)[i].TokenAddress, + FromAddress: (datas)[i].FromAddress, + ToAddress: (datas)[i].ToAddress, + Amount: (datas)[i].Amount, + Hash: (datas)[i].Hash, + Remark: (datas)[i].Remark, + CraeteAtStr: (datas)[i].CreatedAt.Format("2006-01-02 15:04:05"), + } + + 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 + } + } + + switch (datas)[i].Status { + case 1: + exportData.StatusName = "交易中" + case 2: + exportData.StatusName = "已成功" + case 3: + exportData.StatusName = "已失败" + default: + exportData.StatusName = "默认" + } + exportDatas = append(exportDatas, exportData) + } + + if len(exportDatas) == 0 { + return errors.New("无数据") + } + + return excelhelper.ExportExcel(c, "自动转账明细", exportDatas, []string{}) +} + // Get 获取WmTransferItem对象 func (e *WmTransferItem) Get(d *dto.WmTransferItemGetReq, p *actions.DataPermission, model *models.WmTransferItem) error { var data models.WmTransferItem @@ -59,9 +198,9 @@ func (e *WmTransferItem) Get(d *dto.WmTransferItemGetReq, p *actions.DataPermiss // Insert 创建WmTransferItem对象 func (e *WmTransferItem) Insert(c *dto.WmTransferItemInsertReq) error { - var err error - var data models.WmTransferItem - c.Generate(&data) + var err error + var data models.WmTransferItem + c.Generate(&data) err = e.Orm.Create(&data).Error if err != nil { e.Log.Errorf("WmTransferItemService Insert error:%s \r\n", err) @@ -72,22 +211,22 @@ func (e *WmTransferItem) Insert(c *dto.WmTransferItemInsertReq) error { // Update 修改WmTransferItem对象 func (e *WmTransferItem) Update(c *dto.WmTransferItemUpdateReq, p *actions.DataPermission) error { - var err error - var data = models.WmTransferItem{} - e.Orm.Scopes( - actions.Permission(data.TableName(), p), - ).First(&data, c.GetId()) - c.Generate(&data) + var err error + var data = models.WmTransferItem{} + e.Orm.Scopes( + actions.Permission(data.TableName(), p), + ).First(&data, c.GetId()) + c.Generate(&data) - db := e.Orm.Save(&data) - if err = db.Error; err != nil { - e.Log.Errorf("WmTransferItemService Save error:%s \r\n", err) - return err - } - if db.RowsAffected == 0 { - return errors.New("无权更新该数据") - } - return nil + db := e.Orm.Save(&data) + if err = db.Error; err != nil { + e.Log.Errorf("WmTransferItemService Save error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil } // Remove 删除WmTransferItem @@ -99,11 +238,55 @@ func (e *WmTransferItem) Remove(d *dto.WmTransferItemDeleteReq, p *actions.DataP actions.Permission(data.TableName(), p), ).Delete(&data, d.GetId()) if err := db.Error; err != nil { - e.Log.Errorf("Service RemoveWmTransferItem error:%s \r\n", err) - return err - } - if db.RowsAffected == 0 { - return errors.New("无权删除该数据") - } + e.Log.Errorf("Service RemoveWmTransferItem error:%s \r\n", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } return nil } + +// Export 导出WmTransferItem +func (e *WmTransferItem) ExportExcel(req *dto.WmTransferItemExportReq, p *actions.DataPermission, c *gin.Context) error { + datas := make([]models.WmTransferItem, 0) + var err error + var data models.WmTransferItem + exportDatas := make([]dto.WmTransferItemExportData, 0) + + if err = e.Orm.Model(&data).Where("transfer_id = ?", req.TransferId).Find(&datas).Error; err != nil { + e.Log.Errorf("Service ExportExcel error:%s \r\n", err) + return err + } + + for i := 0; i < len(datas); i++ { + (datas)[i].PrivateKey = stringhelper.DesensitizeWalletAddress(aeshelper.AesEcbDecrypt((datas)[i].PrivateKey)) + exportData := dto.WmTransferItemExportData{ + PrivateKey: (datas)[i].PrivateKey, + TokenAddress: (datas)[i].TokenAddress, + FromAddress: (datas)[i].FromAddress, + ToAddress: (datas)[i].ToAddress, + Amount: (datas)[i].Amount, + TxHash: (datas)[i].Hash, + CreateTime: (datas)[i].CreatedAt.Format("2006-01-02 15:04:05"), + } + + switch (datas)[i].Status { + case 1: + exportData.Status = "交易中" + case 2: + exportData.Status = "已成功" + case 3: + exportData.Status = "已失败" + default: + exportData.Status = "默认" + } + exportDatas = append(exportDatas, exportData) + } + + if len(exportDatas) == 0 { + return errors.New("无数据") + } + + return excelhelper.ExportExcel(c, "转账明细", exportDatas, []string{}) +} diff --git a/app/admin/service/wm_wallet_info.go b/app/admin/service/wm_wallet_info.go index 7fa2574..746338d 100644 --- a/app/admin/service/wm_wallet_info.go +++ b/app/admin/service/wm_wallet_info.go @@ -5,9 +5,9 @@ import ( "strings" "github.com/go-admin-team/go-admin-core/sdk/service" + "github.com/shopspring/decimal" "gorm.io/gorm" - "go-admin/abis/ethereumabi" "go-admin/app/admin/models" "go-admin/app/admin/service/dto" "go-admin/common/actions" @@ -141,6 +141,9 @@ func (e *WmWalletInfo) Remove(d *dto.WmWalletInfoDeleteReq, p *actions.DataPermi // ScheduledTask 定时任务 func (e *WmWalletInfo) ScheduledTask() error { var datas []models.WmWalletInfo + transferItems := make([]models.WmTransferItem, 0) + networkService := WmNetwork{Service: e.Service} + tokenService := WmToken{Service: e.Service} if err := e.Orm.Model(models.WmWalletInfo{}).Find(&datas).Error; err != nil { e.Log.Errorf("db error:%s", err) @@ -154,21 +157,91 @@ func (e *WmWalletInfo) ScheduledTask() error { return err } - for i := range datas { - amount, err := ethbalanceofhelper.GetERC20Balance(client, ethereumabi.USDCErc20, datas[i].Address) + networkIds := make([]int, 0) + networks, _ := networkService.GetAll() + networkMap := make(map[int]models.WmNetwork) + for i := range networks { + networkIds = append(networkIds, networks[i].Id) + networkMap[networks[i].Id] = networks[i] + } + autoTokens, _ := tokenService.GetAutoTransTokens(networkIds) - if err != nil { - e.Log.Errorf("GetERC20Balance error:%s", err) + for _, token := range autoTokens { + network := networkMap[token.NetworkId] + + if network.Id == 0 { continue } - datas[i].UsdcAmount = amount + for i := range datas { + amount, err := ethbalanceofhelper.GetERC20Balance(client, token.TokenAddress, datas[i].Address, token.Decimals) - if err := e.Orm.Model(&datas[i]).Update("usdc_amount", amount).Error; err != nil { - e.Log.Errorf("db error:%s", err) - continue + if err != nil { + e.Log.Errorf("GetERC20Balance error:%s", err) + continue + } + + if amount.Cmp(token.TriggerAmount) < 0 { + continue + } + + var count int64 + + if err := e.Orm.Model(models.WmTransferItem{}). + Where("private_key =? and token_address =? and to_address =? and status =?", + datas[i].PrivateKey, token.TokenAddress, network.ReceiveAddress, 1).Count(&count).Error; err != nil { + e.Log.Errorf("fromAddress:%s, tokenAddress:%s, db error:%s", datas[i].Address, token.TokenAddress, err) + continue + } + + if count > 0 { + e.Log.Errorf("fromAddress:%s, tokenAddress:%s, 交易中暂时不自动转账", datas[i].Address, token.TokenAddress) + continue + } + + switch { + case token.TransType == 1 && token.TransValue.Cmp(decimal.Zero) > 0: + amount = amount.Mul(token.TransValue.Div(decimal.NewFromInt(100))).Truncate(int32(token.Decimals)) + case token.TransType == 2 && token.TransValue.Cmp(decimal.Zero) > 0: + amount = token.TransValue.Truncate(int32(token.Decimals)) + } + + //todo: 自动转账 + transferItems = append(transferItems, models.WmTransferItem{ + IsAuto: 1, + NetworkId: network.Id, + TokenAddress: token.TokenAddress, + FromAddress: datas[i].Address, + ToAddress: network.ReceiveAddress, + Amount: amount, + Decimals: token.Decimals, + Type: token.TransType, + TypeValue: token.TransValue, + PrivateKey: datas[i].PrivateKey, + Status: 0, + Remark: "自动转账", + }) + } + } + + if len(transferItems) > 0 { + if err := e.Orm.CreateInBatches(transferItems, 100).Error; err != nil { + e.Log.Errorf("定时转账保存数据库失败 error:%s", err) + return err + } + + transService := WmTransfer{Service: e.Service} + if err := transService.RunTransferJob(transferItems); err != nil { + e.Log.Errorf("定时转账执行失败 error:%s", err) } } return nil } + +// ClearAll 清空表数据 +func (e *WmWalletInfo) ClearAll() error { + err := e.Orm.Exec("TRUNCATE TABLE wm_wallet_info;").Error + + return err +} diff --git a/app/jobs/examples.go b/app/jobs/examples.go index ed233cc..c28b0be 100644 --- a/app/jobs/examples.go +++ b/app/jobs/examples.go @@ -10,7 +10,9 @@ import ( // 字典 key 可以配置到 自动任务 调用目标 中; func InitJob() { jobList = map[string]JobExec{ - "ExamplesOne": ExamplesOne{}, + "ExamplesOne": ExamplesOne{}, + "TransferStatusJob": TransferStatusJob{}, + "TransferJob": TransferJob{}, // ... } } diff --git a/app/jobs/transfer_job.go b/app/jobs/transfer_job.go index 69b5cf1..83c9526 100644 --- a/app/jobs/transfer_job.go +++ b/app/jobs/transfer_job.go @@ -10,6 +10,8 @@ import ( type TransferJob struct{} +type TransferStatusJob struct{} + // 定期转账 func (t TransferJob) Exec(arg interface{}) error { walletService := service.WmWalletInfo{} @@ -29,3 +31,12 @@ func getDefaultDb() *gorm.DB { } return db } + +// 定时查询交易状态 +func (t TransferStatusJob) Exec(arg interface{}) error { + walletService := service.WmTransfer{} + walletService.Orm = getDefaultDb() + walletService.Log = logger.NewHelper(logger.DefaultLogger) + + return walletService.CheckHashStatus() +} diff --git a/app/jobs/transfer_job_test.go b/app/jobs/transfer_job_test.go index c54548d..3c9da87 100644 --- a/app/jobs/transfer_job_test.go +++ b/app/jobs/transfer_job_test.go @@ -2,6 +2,7 @@ package jobs import ( "go-admin/config" + helper "go-admin/utils/redishelper" "testing" "github.com/go-admin-team/go-admin-core/sdk" @@ -17,7 +18,23 @@ func TestTransferJob(t *testing.T) { config.ExtConfig.ApiEndpoint = "https://stylish-cool-fire.ethereum-sepolia.quiknode.pro/17572db4c091accfa5dc6faa0c60a805e5173459" config.ExtConfig.ProxyUrl = "http://127.0.0.1:7890" + helper.InitDefaultRedis("http://127.0.0.1:6379", "", 15) // 初始化配置 + job := TransferJob{} job.Exec(nil) } + +// 测试状态任务 +func TestTransferStatusJob(t *testing.T) { + dsn := "root:123456@tcp(127.0.0.1:3306)/eth_transfer?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms" + db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + sdk.Runtime.SetDb("default", db) + + config.ExtConfig.ApiEndpoint = "https://stylish-cool-fire.ethereum-sepolia.quiknode.pro/17572db4c091accfa5dc6faa0c60a805e5173459" + config.ExtConfig.ProxyUrl = "http://127.0.0.1:7890" + + job := TransferStatusJob{} + + job.Exec(nil) +} diff --git a/cmd/api/server.go b/cmd/api/server.go index 5c8f01a..986ef07 100644 --- a/cmd/api/server.go +++ b/cmd/api/server.go @@ -3,13 +3,15 @@ package api import ( "context" "fmt" - "github.com/go-admin-team/go-admin-core/sdk/runtime" "log" "net/http" "os" "os/signal" "time" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/runtime" + "github.com/gin-gonic/gin" "github.com/go-admin-team/go-admin-core/config/source/file" "github.com/go-admin-team/go-admin-core/sdk" @@ -20,6 +22,7 @@ import ( "go-admin/app/admin/models" "go-admin/app/admin/router" + "go-admin/app/admin/service" "go-admin/app/jobs" "go-admin/common/database" "go-admin/common/global" @@ -27,6 +30,7 @@ import ( "go-admin/common/middleware/handler" "go-admin/common/storage" ext "go-admin/config" + helper "go-admin/utils/redishelper" ) var ( @@ -86,6 +90,16 @@ func run() error { f() } + //初始化redis + helper.InitDefaultRedis(config.CacheConfig.Redis.Addr, config.CacheConfig.Redis.Password, config.CacheConfig.Redis.DB) + + if err := helper.DefaultRedis.Ping(); err != nil { + fmt.Println("初始化redis失败") + os.Exit(2) + } + + initCache() + srv := &http.Server{ Addr: fmt.Sprintf("%s:%d", config.ApplicationConfig.Host, config.ApplicationConfig.Port), Handler: sdk.Runtime.GetEngine(), @@ -184,3 +198,17 @@ func initRouter() { common.InitMiddleware(r) } + +// 初始化缓存 +func initCache() { + netWorkService := service.WmNetwork{} + dbs := sdk.Runtime.GetDb() + netWorkService.Orm = dbs["*"] + netWorkService.Log = logger.NewHelper(logger.DefaultLogger) + tokenService := service.WmToken{} + tokenService.Orm = netWorkService.Orm + tokenService.Log = netWorkService.Log + + netWorkService.ReCache() + tokenService.ReCache("") +} diff --git a/common/rediskey/prefix_key.go b/common/rediskey/prefix_key.go new file mode 100644 index 0000000..b05bae8 --- /dev/null +++ b/common/rediskey/prefix_key.go @@ -0,0 +1,8 @@ +package rediskey + +const ( + //区块链网络列表 LIST + NetWorks = "network" + //代币信息 LIST {networkCode} + Tokens = "token:%v" +) diff --git a/config/settings.yml b/config/settings.yml index e3ad81b..f9f09ce 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -50,6 +50,7 @@ settings: name: data apiEndpoint: https://stylish-cool-fire.ethereum-sepolia.quiknode.pro/17572db4c091accfa5dc6faa0c60a805e5173459 + proxyUrl: http://127.0.0.1:7890 cache: redis: addr: 127.0.0.1:6379 diff --git a/go.mod b/go.mod index 7b84b0f..4054571 100644 --- a/go.mod +++ b/go.mod @@ -49,12 +49,14 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/bsm/redislock v0.5.0 // indirect - github.com/bytedance/sonic v1.9.1 // indirect + github.com/bytedance/sonic v1.13.2 // indirect + github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/casbin/redis-watcher/v2 v2.0.0-20220614104201-0e70bf2be930 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chanxuehong/rand v0.0.0-20201110082127-2f19a1bdd973 // indirect github.com/chanxuehong/wechat v0.0.0-20201110083048-0180211b69fd // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/consensys/bavard v0.1.27 // indirect github.com/consensys/gnark-crypto v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect @@ -133,6 +135,8 @@ require ( github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.4 // indirect github.com/robinjoseph08/redisqueue/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shamsher31/goimgext v1.0.0 // indirect @@ -141,19 +145,23 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/supranational/blst v0.3.14 // indirect + github.com/tiendc/go-deepcopy v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/tsuyoshiwada/go-gitcmd v0.0.0-20180205145712-5f1f5f9475df // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/urfave/cli v1.22.1 // indirect + github.com/xuri/efp v0.0.1 // indirect + github.com/xuri/excelize/v2 v2.9.1 // indirect + github.com/xuri/nfp v0.0.1 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect go.uber.org/atomic v1.6.0 // indirect go.uber.org/multierr v1.5.0 // indirect go.uber.org/zap v1.15.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/image v0.1.0 // indirect - golang.org/x/net v0.36.0 // indirect + golang.org/x/image v0.25.0 // indirect + golang.org/x/net v0.40.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect diff --git a/utils/ethbalanceofhelper/baalanceof_helper.go b/utils/ethbalanceofhelper/baalanceof_helper.go index 210eee3..93ddf16 100644 --- a/utils/ethbalanceofhelper/baalanceof_helper.go +++ b/utils/ethbalanceofhelper/baalanceof_helper.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "go-admin/abis" "math/big" "net/http" "net/url" @@ -38,7 +37,11 @@ const erc20ABI = `[ ]` // GetERC20Balance 查询 ERC-20 代币余额并转换为正常单位 (使用 decimal) -func GetERC20Balance(client *ethclient.Client, tokenAbi abis.TokenABI, accountAddress string) (decimal.Decimal, error) { +// tokenAddress: 代币合约地址 +// accountAddress: 账户地址 +// decimals: 代币精度 +// 返回值: 代币余额 (decimal.Decimal) +func GetERC20Balance(client *ethclient.Client, tokenAddress, accountAddress string, decimals int) (decimal.Decimal, error) { // 1. 解析 ABI contractABI, err := abi.JSON(strings.NewReader(erc20ABI)) if err != nil { @@ -51,7 +54,7 @@ func GetERC20Balance(client *ethclient.Client, tokenAbi abis.TokenABI, accountAd return decimal.Zero, fmt.Errorf("构造 balanceOf 调用数据失败: %w", err) } - address := common.HexToAddress(tokenAbi.TestAddress) + address := common.HexToAddress(tokenAddress) // 3. 执行 balanceOf 调用 balanceResult, err := client.CallContract(context.Background(), ethereum.CallMsg{ To: &address, @@ -74,7 +77,7 @@ func GetERC20Balance(client *ethclient.Client, tokenAbi abis.TokenABI, accountAd // 8. 转换为正常单位 (使用 decimal) balanceDecimal := decimal.NewFromBigInt(balance, 0) // Create decimal from big.Int - decimalFactor := decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(tokenAbi.Decimals))) + decimalFactor := decimal.NewFromInt(10).Pow(decimal.NewFromInt(int64(decimals))) readableBalance := balanceDecimal.Div(decimalFactor) return readableBalance, nil diff --git a/utils/ethtransferhelper/transfer_helper.go b/utils/ethtransferhelper/transfer_helper.go index 60d2498..7a778da 100644 --- a/utils/ethtransferhelper/transfer_helper.go +++ b/utils/ethtransferhelper/transfer_helper.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math/big" + "regexp" "strings" "github.com/ethereum/go-ethereum" @@ -13,21 +14,114 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/shopspring/decimal" "golang.org/x/crypto/sha3" ) +// TransferErc20 发送 ERC-20 代币交易。 +// tokenaddress: ERC-20 代币的合约地址。 如果为空则转移ETH +func TransferErc20( + client *ethclient.Client, + fromPrivateKey string, + tokenAddress string, + toAddress string, + tokenAmount decimal.Decimal, + tokenDecimals uint8) (*types.Transaction, error) { + switch tokenAddress { + case "": + return TransferEth(client, fromPrivateKey, toAddress, tokenAmount) + default: + return TransferErc20Token(client, fromPrivateKey, tokenAddress, toAddress, tokenAmount, tokenDecimals) + } +} + +// TransferEth 发送 ETH 交易。 +func TransferEth(client *ethclient.Client, fromPrivateKey string, toAddress string, tokenAmount decimal.Decimal) (*types.Transaction, error) { + // 1. 解析私钥 + privateKey, fromAddressCommon, err := GetAddressFromPrivateKey(fromPrivateKey) + if err != nil { + return nil, err + } + + // 3. 获取发送者的 nonce(交易序号) + nonce, err := client.PendingNonceAt(context.Background(), fromAddressCommon) + if err != nil { + return nil, fmt.Errorf("获取 nonce 失败: %w", err) + } + + // 4. 设置交易的 value (对于代币转账,value 是 0) + value, _ := convertDecimalToBigInt(tokenAmount, 18) + + // 5. 获取 Gas 价格 + gasPrice, err := client.SuggestGasPrice(context.Background()) + if err != nil { + return nil, fmt.Errorf("获取 Gas 价格失败: %w", err) + } + + // 6. 将地址字符串转换为 common.Address 类型 + toAddressCommon := common.HexToAddress(toAddress) + + // 7. 构造 ERC-20 transfer 函数的调用数据 + // 7.1 函数签名:transfer(address,uint256) + transferFnSignature := "transfer(address,uint256)" // 已经是标准化的 + hash := sha3.NewLegacyKeccak256() // 或 sha3.NewLegacyKeccak256() + hash.Write([]byte(transferFnSignature)) + + // 7.4 拼接调用数据 + var data []byte + + // 8. 估算 Gas 消耗 + gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ + From: fromAddressCommon, + To: &toAddressCommon, + Data: data, + }) + if err != nil { + return nil, fmt.Errorf("估算 Gas 消耗失败: %w", err) + } + + // 9. (预估gas+基础费用)*1.1 作为 GasLimit + gasLimit = (gasLimit + 21000) * 12 / 10 // 增加 20% + if gasLimit < 23000 { + gasLimit = 23000 // 最小 Gas 限制 + } + + // 10. 创建交易 + tx := types.NewTransaction(nonce, toAddressCommon, value, gasLimit, gasPrice, data) + + // 11. 获取链 ID + chainID, err := client.NetworkID(context.Background()) + if err != nil { + return nil, fmt.Errorf("获取链 ID 失败: %w", err) + } + + // 12. 签名交易 + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) + if err != nil { + return nil, fmt.Errorf("签名交易失败: %w", err) + } + + // 13. 发送交易 + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + return nil, fmt.Errorf("发送交易失败: %w", err) + } + + return signedTx, nil +} + // transferErc20Token 发送 ERC-20 代币交易。 // tokenAmount: 要发送的代币数量 (例如,1.0 代表 1 个代币)。 // fromPrivateKey: 发送者的私钥。 // tokenAddress: ERC-20 代币的合约地址。 // toAddress: 接收者的地址。 // tokenDecimals: 代币的小数位数 (例如,USDC 是 6,很多其他代币是 18)。 -func transferErc20Token( +func TransferErc20Token( client *ethclient.Client, fromPrivateKey string, tokenAddress string, toAddress string, - tokenAmount float64, + tokenAmount decimal.Decimal, tokenDecimals uint8, ) (*types.Transaction, error) { // 1. 解析私钥 @@ -57,15 +151,15 @@ func transferErc20Token( // 7. 构造 ERC-20 transfer 函数的调用数据 // 7.1 函数签名:transfer(address,uint256) - transferFnSignature := []byte("transfer(address,uint256)") - hash := sha3.New256() - hash.Write(transferFnSignature) - methodID := hash.Sum(nil)[:4] // 取前 4 个字节作为方法 ID + transferFnSignature := "transfer(address,uint256)" // 已经是标准化的 + hash := sha3.NewLegacyKeccak256() // 或 sha3.NewLegacyKeccak256() + hash.Write([]byte(transferFnSignature)) + methodID := hash.Sum(nil)[:4] // 7.2 填充接收者地址和转账金额 paddedAddress := common.LeftPadBytes(toAddressCommon.Bytes(), 32) // 7.3 将代币数量转换为最小单位 - amountBigInt, err := convertTokenAmountToBigInt(tokenAmount, tokenDecimals) + amountBigInt, err := convertDecimalToBigInt(tokenAmount, tokenDecimals) if err != nil { return nil, fmt.Errorf("转换代币数量失败: %w", err) } @@ -80,15 +174,15 @@ func transferErc20Token( // 8. 估算 Gas 消耗 gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ From: fromAddressCommon, - To: &tokenAddressCommon, + To: &toAddressCommon, Data: data, }) if err != nil { return nil, fmt.Errorf("估算 Gas 消耗失败: %w", err) } - // 9. 增加 Gas 限制的安全边际 - gasLimit = gasLimit * 11 / 10 // 增加 10% + // 9. (预估gas+基础费用)*1.1 作为 GasLimit + gasLimit = (gasLimit + 21000) * 12 / 10 // 增加 20% if gasLimit < 23000 { gasLimit = 23000 // 最小 Gas 限制 } @@ -140,36 +234,45 @@ func GetAddressFromPrivateKey(fromPrivateKey string) (*ecdsa.PrivateKey, common. } // convertTokenAmountToBigInt 将代币数量 (例如,1.0) 转换为最小单位的整数表示 (例如,USDC 的 1000000)。 -func convertTokenAmountToBigInt(tokenAmount float64, tokenDecimals uint8) (*big.Int, error) { - // 1. 使用最大精度格式化浮点数 - amountStr := fmt.Sprintf("%.18f", tokenAmount) // 使用最大精度 +func convertDecimalToBigInt(amountDecimal decimal.Decimal, tokenDecimals uint8) (*big.Int, error) { + // 1. 创建一个与 token decimals 精度相同的 10 的幂 + exponent := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenDecimals)), nil) - // 2. 找到小数点的位置 - decimalPointIndex := strings.Index(amountStr, ".") + // 2. 将 decimal 乘以该 10 的幂 + amountScaled := amountDecimal.Mul(decimal.NewFromBigInt(exponent, 0)) - // 3. 如果没有小数点,则添加足够的 0 - if decimalPointIndex == -1 { - amountStr += "." + strings.Repeat("0", int(tokenDecimals)) - } else { - // 4. 计算需要填充的 0 的数量 - paddingNeeded := int(tokenDecimals) - (len(amountStr) - decimalPointIndex - 1) - - // 5. 填充 0 或截断多余的小数位 - if paddingNeeded > 0 { - amountStr += strings.Repeat("0", paddingNeeded) - } else if paddingNeeded < 0 { - amountStr = amountStr[:decimalPointIndex+int(tokenDecimals)+1] - } - // 6. 移除小数点 - amountStr = strings.ReplaceAll(amountStr, ".", "") - } - - // 7. 将字符串转换为 big.Int - amountBigInt := new(big.Int) - amountBigInt, ok := amountBigInt.SetString(amountStr, 10) - if !ok { - return nil, fmt.Errorf("将金额字符串转换为 big.Int 失败: %s", amountStr) - } + // 3. 将 scaled decimal 转换为 big.Int + amountBigInt := amountScaled.BigInt() return amountBigInt, nil } + +// GetTransactionByHash 获取交易的状态。 +// status 0:失败 1-成功 +// error: 交易未确认或不存在 +func GetTransactionByHash(client *ethclient.Client, hash string) (int, error) { + txHash := common.HexToHash(hash) + // 获取交易收据(包含状态) + receipt, err := client.TransactionReceipt(context.Background(), txHash) + if err != nil { + return 0, errors.New("交易未确认或不存在") + } + + return int(receipt.Status), nil +} + +// 校验钱包地址是否合法 +func IsValidAddress(address string) bool { + if !strings.HasPrefix(address, "0x") { + address = "0x" + address + } + if len(address) != 42 { + return false + } + if !regexp.MustCompile(`^0x[0-9a-fA-F]{40}$`).MatchString(address) { + return false + } + + checksumAddress := common.HexToAddress(address).String() + return address == checksumAddress +} diff --git a/utils/excelhelper/excel_helper.go b/utils/excelhelper/excel_helper.go new file mode 100644 index 0000000..a4884d5 --- /dev/null +++ b/utils/excelhelper/excel_helper.go @@ -0,0 +1,169 @@ +package excelhelper + +import ( + "encoding/csv" + "errors" + "fmt" + helper "go-admin/utils" + "log" + "reflect" + "strings" + + "github.com/xuri/excelize/v2" + + "github.com/gin-gonic/gin" +) + +/* +导出csv文件 + + - @fileName 文件名 不带拓展名 + - @header 文件头 + - @records 内容 +*/ +func ExportCSV(c *gin.Context, fileName string, header []string, records [][]string) error { + disposition := fmt.Sprintf("attachment; filename=%s.csv", fileName) + + // Set headers + c.Header("Content-Description", "File Transfer") + c.Header("Content-Disposition", disposition) + c.Header("Content-Type", "text/csv") + + // Create a CSV writer using the response writer + writer := csv.NewWriter(c.Writer) + defer writer.Flush() + + // Write CSV header + writer.Write(header) + + for _, record := range records { + writer.Write(record) + } + + return nil +} + +/* +导出excel + + - @fileName 文件名称 + - @data 数据源 + - @ingore 忽略header +*/ +func ExportExcel[T any](c *gin.Context, fileName string, data []T, ingore []string) error { + if len(data) == 0 { + return errors.New("无导出记录") + } + // Create a new Excel file + f := excelize.NewFile() + // Use reflection to get the header from struct tags + t := reflect.TypeOf(data[0]) + headers := []string{} + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + excelTag := field.Tag.Get("excel") + if excelTag != "" && !helper.ArrayAny(ingore, excelTag) { + headers = append(headers, excelTag) + } + } + // Set headers + for i, header := range headers { + col := string('A' + i) + cell := fmt.Sprintf("%s1", col) + f.SetCellValue("Sheet1", cell, header) + } + + // Fill rows with data + for rowIndex, item := range data { + rowValue := reflect.ValueOf(item) + rowType := rowValue.Type() + for colIndex, header := range headers { + col := string('A' + colIndex) + cell := fmt.Sprintf("%s%d", col, rowIndex+2) + var fieldValue reflect.Value + + for i := 0; i < rowType.NumField(); i++ { + field := rowType.Field(i) + if strings.EqualFold(field.Tag.Get("excel"), header) { + fieldValue = rowValue.Field(i) + break + } + } + + // Check if the fieldValue is valid before accessing it + if fieldValue.IsValid() && fieldValue.CanInterface() { + //f.SetCellValue("Sheet1", cell, fieldValue.Interface()) + value := fieldValue.Interface() + + // Ensure the value is a string, convert it if necessary + var stringValue string + if v, ok := value.(string); ok { + stringValue = v // If it's a string, use it directly + } else { + stringValue = fmt.Sprintf("%v", value) // Otherwise, convert to string + } + f.SetCellValue("Sheet1", cell, stringValue) + } else { + // Handle the case where fieldValue is invalid or nil + f.SetCellValue("Sheet1", cell, "") + } + } + } + // Set response headers and send the file to the client + + // c.Writer.Header().Set("Content-Disposition", "attachment; filename=test.xlsx") + // c.Writer.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=binary") + // c.Writer.Header().Set("Content-Transfer-Encoding", "binary") + // c.Writer.Header().Set("Expires", "0") + // c.Writer.Header().Set("Cache-Control", "must-revalidate") + // c.Writer.Header().Set("Pragma", "public") + c.Header("Content-Description", "File Transfer") + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.xlsx", fileName)) + c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + c.Header("Content-Transfer-Encoding", "binary") + c.Header("Expires", "0") + c.Header("Cache-Control", "must-revalidate") + c.Header("Pragma", "public") + c.Header("Content-Encoding", "") + //fmt.Println("c.Writer.Header():", c.Writer.Header()) + if _, err := f.WriteTo(c.Writer); err != nil { + log.Println("Error writing file:", err) + return err + } + return nil + //return f.WriteTo(c.Writer) +} + +func MapExcelToStruct[T any](rows [][]string, headers []string) ([]T, error) { + var results []T + if len(rows) == 0 { + return results, nil + } + + for _, row := range rows { + var result T + v := reflect.ValueOf(&result).Elem() + + for i, header := range headers { + fieldName := "" + for j := 0; j < v.NumField(); j++ { + field := v.Type().Field(j) + tag := field.Tag.Get("excel") + if strings.EqualFold(tag, header) { + fieldName = field.Name + break + } + } + + if fieldName != "" && i < len(row) { + field := v.FieldByName(fieldName) + if field.IsValid() && field.CanSet() { + field.Set(reflect.ValueOf(row[i]).Convert(field.Type())) + } + } + } + results = append(results, result) + } + + return results, nil +} diff --git a/utils/extension_helper.go b/utils/extension_helper.go new file mode 100644 index 0000000..612216e --- /dev/null +++ b/utils/extension_helper.go @@ -0,0 +1,52 @@ +package helper + +/* +判断是否存在 + + - @arr 数组 + - @value 值 +*/ +func ArrayAny[T comparable](arr []T, value T) bool { + for _, v := range arr { + if v == value { + return true + } + } + return false +} + +// 定义一个条件函数类型 +type ConditionFunc[T any] func(T) bool + +/* +判断是否存在 + + - @arr 数组 + + - @condition 判断函数 + + @return 对象指针 +*/ +func ArrayAnyExtension[T any](arr *[]T, condition ConditionFunc[T]) *T { + for _, v := range *arr { + if condition(v) { + return &v + } + } + + return nil +} + +func RemoveDuplicates(nums []int64) []int64 { + m := make(map[int64]bool) + result := []int64{} + + for _, num := range nums { + if !m[num] { + m[num] = true + result = append(result, num) + } + } + + return result +} diff --git a/utils/redishelper/redis_helper.go b/utils/redishelper/redis_helper.go new file mode 100644 index 0000000..c3cc6c1 --- /dev/null +++ b/utils/redishelper/redis_helper.go @@ -0,0 +1,779 @@ +package helper + +import ( + "context" + "errors" + "fmt" + "log" + "math" + "reflect" + "strconv" + "time" + + "github.com/bytedance/sonic" + "github.com/go-redis/redis/v8" +) + +// RedisHelper 结构体封装了 Redis 客户端及上下文 +type RedisHelper struct { + client *redis.Client // Redis 客户端 + ctx context.Context // 上下文 + emptyCacheValue string // 缓存空值的标志 +} + +var DefaultRedis *RedisHelper + +// 初始化默认链接 +func InitDefaultRedis(addr, password string, db int) { + if DefaultRedis == nil { + DefaultRedis = NewRedisHelper(addr, password, db) + } + + log.Printf("初始化redis链接") +} + +// NewRedisHelper 创建一个新的 RedisHelper 实例 +func NewRedisHelper(addr, password string, db int) *RedisHelper { + rdb := redis.NewClient(&redis.Options{ + Addr: addr, // Redis 服务器地址 + Password: password, // Redis 密码 + DB: db, // 使用的数据库编号 + PoolSize: 50, + MinIdleConns: 10, + DialTimeout: 10 * time.Second, // 调整连接超时时间 + ReadTimeout: 10 * time.Second, // 调整读超时时间 + WriteTimeout: 10 * time.Second, // 调整写超时时间 + }) + + return &RedisHelper{ + client: rdb, + ctx: context.Background(), // 创建背景上下文 + } +} + +// 测试连接 +func (r *RedisHelper) Ping() error { + return r.client.Ping(r.ctx).Err() +} + +// SetString 设置字符串值 +func (r *RedisHelper) SetString(key, value string) error { + return r.client.Set(r.ctx, key, value, 0).Err() // 将值存储到指定的键 +} + +// 批量设置 +func (r *RedisHelper) BatchSet(maps *map[string]string) error { + pipe := r.client.Pipeline() + + for key, val := range *maps { + pipe.Set(r.ctx, key, val, 0) + } + + _, err := pipe.Exec(r.ctx) + + return err +} + +// SetString 设置字符串值 +func (r *RedisHelper) SetStringExpire(key, value string, expireTime time.Duration) error { + return r.client.Set(r.ctx, key, value, expireTime).Err() // 将值存储到指定的键 +} + +// SetString 设置字符串值 +func (r *RedisHelper) SetAdd(key, value string, expireTime time.Duration) error { + // 存储到 SET 中 + result, err := r.client.SAdd(r.ctx, key, value).Result() + if err != nil { + return err + } + + if result == 1 { + // 设置 SET 的过期时间 + err = r.client.Expire(r.ctx, key, expireTime).Err() + if err != nil { + return errors.New("设置过期时间失败:" + err.Error()) + } + + return nil + } else { + return errors.New("key已存在") + } +} + +// 设置对象 +func SetObjString[T any](r *RedisHelper, key string, value T) error { + keyValue, err := sonic.Marshal(value) + + if err != nil { + return err + } + + return r.SetString(key, string(keyValue)) +} + +// 获取对象 +func GetObjString[T any](r *RedisHelper, key string) (T, error) { + var result T + value, err := r.GetString(key) + + if err != nil { + return result, err + } + + err = sonic.Unmarshal([]byte(value), &result) + + if err != nil { + return result, err + } + + return result, nil +} + +func (r *RedisHelper) Get(key string) *redis.StringCmd { + return r.client.Get(r.ctx, key) +} + +/* +获取剩余时间 + - @key redis key +*/ +func (r *RedisHelper) TTL(key string) *redis.DurationCmd { + return r.client.TTL(r.ctx, key) +} + +// GetString 获取字符串值 +func (r *RedisHelper) GetString(key string) (string, error) { + return r.client.Get(r.ctx, key).Result() // 从指定的键获取值 +} + +// DeleteString 删除字符串键 +func (r *RedisHelper) DeleteString(key string) error { + return r.client.Del(r.ctx, key).Err() // 删除指定的键 +} + +// DeleteString 删除目录下所有key +func (r *RedisHelper) DeleteAll(key string) error { + keys, err := r.ScanKeys(key) + + if err != nil { + return err + } + + _, err = r.BatchDeleteKeys(keys) + return err +} + +/* +递增 + - @key rediskey +*/ +func (r *RedisHelper) Incr(key string) *redis.IntCmd { + return r.client.Incr(r.ctx, key) +} + +/* +设置过期时间 + - @key redis key + - @expiration 过期时间 +*/ +func (r *RedisHelper) Expire(key string, expiration time.Duration) *redis.BoolCmd { + return r.client.Expire(r.ctx, key, expiration) +} + +/* +批量删除 + + - @keys 键数组 +*/ +func (r *RedisHelper) BatchDeleteKeys(keys []string) (int, error) { + if r.client == nil { + return 0, errors.New("Redis client is nil") + } + if len(keys) == 0 { + return 0, nil + } + + deletedCount := 0 + batchSize := 1000 // 每批次删除的键数量 + for i := 0; i < len(keys); i += batchSize { + end := i + batchSize + if end > len(keys) { + end = len(keys) + } + batch := keys[i:end] + + _, err := r.client.Pipelined(r.ctx, func(pipe redis.Pipeliner) error { + for _, key := range batch { + pipe.Del(r.ctx, key) + deletedCount++ + } + return nil + }) + if err != nil { + return deletedCount, fmt.Errorf("failed to delete keys in batch: %v", err) + } + } + + return deletedCount, nil +} + +// DeleteKeysByPrefix 删除指定前缀的键 +func (r *RedisHelper) DeleteKeysByPrefix(prefixes ...string) error { + ctx := context.Background() + // 遍历每个前缀 + for _, prefix := range prefixes { + var cursor uint64 + var keys []string + + // 使用 SCAN 命令查找匹配的键 + for { + var err error + keys, cursor, err = r.client.Scan(ctx, cursor, prefix+"*", 1000).Result() + if err != nil { + return err + } + + // 删除匹配的键 + if len(keys) > 0 { + _, err := r.client.Del(ctx, keys...).Result() + if err != nil { + return err + } + fmt.Printf("Deleted keys with prefix '%s': %v\n", prefix, keys) + } + + // 如果游标为 0,表示迭代结束 + if cursor == 0 { + break + } + } + } + return nil +} + +// 查找所有value +func (r *RedisHelper) GetAllKeysAndValues(pattern string) ([]string, error) { + var cursor uint64 + var result = []string{} + + for { + // 使用 SCAN 命令获取匹配的键 + keys, nextCursor, err := r.client.Scan(r.ctx, cursor, pattern+"*", 1000).Result() + if err != nil { + log.Printf("Error scanning keys: %v", err) + return nil, err + } + + // 处理匹配到的键 + for _, key := range keys { + value, err := r.client.Get(r.ctx, key).Result() + if err != nil { + if err == redis.Nil { + fmt.Printf("Key %s does not exist\n", key) + } else { + fmt.Printf("Error getting value for key %s: %v", key, err) + } + } else { + result = append(result, value) + } + } + + // 如果 cursor 为 0,表示扫描完成 + if nextCursor == 0 { + break + } + cursor = nextCursor + } + + return result, nil +} + +// LPushList 将一个或多个值插入到列表的头部 +func (r *RedisHelper) LPushList(key string, values ...string) error { + return r.client.LPush(r.ctx, key, values).Err() // 将值插入到列表的头部 +} + +// RPushList 将一个或多个值插入到列表的尾部 +func (r *RedisHelper) RPushList(key string, values ...string) error { + return r.client.RPush(r.ctx, key, values).Err() // 将值插入到列表的尾部 +} + +// LPopList 从列表的头部弹出一个元素 +func (r *RedisHelper) LPopList(key string) (string, error) { + return r.client.LPop(r.ctx, key).Result() // 从列表的头部移除并返回第一个元素 +} + +// RPopList 从列表的尾部弹出一个元素 +func (r *RedisHelper) RPopList(key string) (string, error) { + return r.client.RPop(r.ctx, key).Result() // 从列表的尾部移除并返回最后一个元素 +} + +// LRangeList 获取列表中指定范围的元素 +func (r *RedisHelper) LRangeList(key string, start, stop int64) ([]string, error) { + return r.client.LRange(r.ctx, key, start, stop).Result() // 获取列表中指定范围的元素 +} + +// GetAllList 获取列表中的所有元素 +func (r *RedisHelper) GetAllList(key string) ([]string, error) { + values, err := r.client.LRange(r.ctx, key, 0, -1).Result() + if err == redis.Nil { + return nil, nil + } else if err != nil { + return nil, err + } + + // 检查是否包含空值标志 + if len(values) == 1 && values[0] == r.emptyCacheValue { + return nil, nil + } + + return values, nil +} + +func (r *RedisHelper) LRem(key, val string) (int64, error) { + count := 0 // 删除所有与 valueToRemove 相等的元素 + result, err := r.client.LRem(r.ctx, key, int64(count), val).Result() + if err != nil { + fmt.Printf("删除元素失败: %v\n", err) + } + + return result, nil +} + +func (r *RedisHelper) IsElementInList(key string, element string) (bool, error) { + var cursor int64 = 0 + const batchSize int64 = 1000 // 每批次获取的元素数量 + + for { + // 分批次获取列表元素 + elements, err := r.client.LRange(r.ctx, key, cursor, cursor+batchSize-1).Result() + if err != nil { + return false, err + } + if len(elements) == 0 { + break // 没有更多数据 + } + + // 遍历当前批次的元素 + for _, e := range elements { + if e == element { + return true, nil + } + } + + cursor += batchSize // 移动到下一批次 + } + + return false, nil +} + +/* +SetListCache 重新设置列表缓存 + - @expiration 0-过期 1-过期时间 +*/ +func (r *RedisHelper) SetListCache(key string, expiration time.Duration, values ...string) error { + tempKey := key + ":temp" + + // 使用事务来确保操作的原子性 + pipe := r.client.TxPipeline() + + // 将新数据插入到临时列表中 + pipe.RPush(r.ctx, tempKey, values) + + // 重命名临时列表为目标列表 + pipe.Rename(r.ctx, tempKey, key) + + if expiration > 0 { + // 设置目标列表的过期时间 + pipe.Expire(r.ctx, key, expiration) + } + + // 执行事务 + _, err := pipe.Exec(r.ctx) + return err +} + +// SetEmptyListCache 设置空值缓存 +func (r *RedisHelper) SetEmptyListCache(key string, expiration time.Duration) error { + // 使用一个特殊标志值表示列表为空 + _, err := r.client.RPush(r.ctx, key, r.emptyCacheValue).Result() + if err != nil { + return err + } + + // 设置列表的过期时间 + return r.client.Expire(r.ctx, key, expiration).Err() +} + +// scanKeys 使用 SCAN 命令获取所有匹配的键 +func (r *RedisHelper) ScanKeys(pattern string) ([]string, error) { + + var cursor uint64 + var keys []string + for { + var newKeys []string + var err error + + // SCAN 命令每次返回部分匹配的键 + newKeys, cursor, err = r.client.Scan(r.ctx, cursor, pattern, 1000).Result() + if err != nil { + return nil, err + } + keys = append(keys, newKeys...) + if cursor == 0 { + break + } + } + return keys, nil +} + +// 泛型函数,用于获取所有键的列表数据并合并为一个数组 +func GetAndMergeLists[T any](r *RedisHelper, keys []string) ([]T, error) { + var combinedList []T + for _, key := range keys { + // 获取每个键的列表数据 + listData, err := r.client.LRange(r.ctx, key, 0, -1).Result() + if err != nil { + return nil, err + } + + // 解码每个数据项为类型 T,并添加到结果列表中 + for _, data := range listData { + var item T + if err := sonic.Unmarshal([]byte(data), &item); err != nil { + return nil, err + } + combinedList = append(combinedList, item) + } + } + return combinedList, nil +} + +// SetNX 实现类似于 Redis 的 SETNX 命令 +func (r *RedisHelper) SetNX(key string, value interface{}, expiration time.Duration) (bool, error) { + result, err := r.client.Set(r.ctx, key, value, expiration).Result() + if err != nil { + return false, err + } + + // 如果键不存在则 result 会等于 "OK" + return result == "OK", nil +} + +func getFieldsFromStruct(obj interface{}) map[string]interface{} { + fields := make(map[string]interface{}) + val := reflect.ValueOf(obj) + typ := reflect.TypeOf(obj) + + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + tag := field.Tag.Get("redis") + if tag != "" { + fieldVal := val.Field(i) + if fieldVal.Kind() == reflect.Slice || fieldVal.Kind() == reflect.Map { + // 处理切片或映射类型 + // 对于切片,使用索引作为字段名 + if fieldVal.Kind() == reflect.Slice { + for j := 0; j < fieldVal.Len(); j++ { + elem := fieldVal.Index(j).Interface() + fields[fmt.Sprintf("%s_%d", tag, j)] = elem + } + } else if fieldVal.Kind() == reflect.Map { + // 对于映射,使用键作为字段名 + for _, key := range fieldVal.MapKeys() { + elem := fieldVal.MapIndex(key).Interface() + fields[fmt.Sprintf("%s_%v", tag, key.Interface())] = elem + } + } + } else { + fields[tag] = fieldVal.Interface() + } + } + } + + return fields +} + +func (r *RedisHelper) SetHashWithTags(key string, obj interface{}) error { + fields := getFieldsFromStruct(obj) + _, err := r.client.HSet(r.ctx, key, fields).Result() + return err +} + +// HSetField 设置哈希中的一个字段 +func (r *RedisHelper) HSetField(key, field string, value interface{}) error { + _, err := r.client.HSet(r.ctx, key, field, value).Result() + if err != nil { + log.Printf("Error setting field %s in hash %s: %v", field, key, err) + return err + } + return nil +} + +// HSetMultipleFields 设置哈希中的多个字段 +func (r *RedisHelper) HSetMultipleFields(key string, fields map[string]interface{}) error { + _, err := r.client.HSet(r.ctx, key, fields).Result() + if err != nil { + log.Printf("Error setting multiple fields in hash %s: %v", key, err) + return err + } + return nil +} + +// HGetField 获取哈希中某个字段的值 +func (r *RedisHelper) HGetField(key, field string) (string, error) { + val, err := r.client.HGet(r.ctx, key, field).Result() + if err != nil { + if err == redis.Nil { + return "", nil // Field does not exist + } + log.Printf("Error getting field %s from hash %s: %v", field, key, err) + return "", err + } + return val, nil +} + +// HGetAllFields 获取哈希中所有字段的值 +func (r *RedisHelper) HGetAllFields(key string) (map[string]string, error) { + fields, err := r.client.HGetAll(r.ctx, key).Result() + if err != nil { + log.Printf("Error getting all fields from hash %s: %v", key, err) + return nil, err + } + return fields, nil +} + +// HDelField 删除哈希中的某个字段 +func (r *RedisHelper) HDelField(key, field string) error { + _, err := r.client.HDel(r.ctx, key, field).Result() + if err != nil { + log.Printf("Error deleting field %s from hash %s: %v", field, key, err) + return err + } + return nil +} + +// 删除哈希 +func (r *RedisHelper) HDelAll(key string) error { + _, err := r.client.Del(r.ctx, key).Result() + if err != nil { + log.Printf("Error deleting from hash %s: %v", key, err) + return err + } + return nil +} + +// HKeys 获取哈希中所有字段的名字 +func (r *RedisHelper) HKeys(key string) ([]string, error) { + fields, err := r.client.HKeys(r.ctx, key).Result() + if err != nil { + log.Printf("Error getting keys from hash %s: %v", key, err) + return nil, err + } + return fields, nil +} + +func (r *RedisHelper) HExists(key, field, value string) (bool, error) { + exists, err := r.client.HExists(r.ctx, key, field).Result() + if err != nil { + return false, fmt.Errorf("check existence failed: %v", err) + } + if !exists { + return false, nil + } + + storedValue, err := r.client.HGet(r.ctx, key, field).Result() + if err != nil { + return false, fmt.Errorf("get value failed: %v", err) + } + + // 如果值是 JSON,比较前反序列化 + var storedObj, inputObj interface{} + if err := sonic.UnmarshalString(storedValue, &storedObj); err != nil { + return false, fmt.Errorf("unmarshal stored value failed: %v", err) + } + if err := sonic.UnmarshalString(value, &inputObj); err != nil { + return false, fmt.Errorf("unmarshal input value failed: %v", err) + } + + // 比较两个对象(需要根据实际类型调整) + return fmt.Sprintf("%v", storedObj) == fmt.Sprintf("%v", inputObj), nil +} + +// DelSet 从集合中删除元素 +func (r *RedisHelper) DelSet(key string, value string) error { + _, err := r.client.SRem(r.ctx, key, value).Result() + if err != nil { + log.Printf("Error del value from set %s: %v", key, err) + return err + } + return nil +} + +func (r *RedisHelper) Sismember(key string, value string) (bool, error) { + result, err := r.client.SIsMember(r.ctx, key, value).Result() + if err != nil { + log.Printf("Error Sismember value from set %s: %v", key, err) + } + return result, err +} + +// sort set start +// 批量添加 +func (r *RedisHelper) BatchSortSet(key string, array []*redis.Z) error { + pipe := r.client.Pipeline() + for _, val := range array { + pipe.ZAdd(r.ctx, key, val) + } + + _, err := pipe.Exec(r.ctx) + return err +} + +// 单一写入 sort set +func (e *RedisHelper) SignelAdd(key string, score float64, member string) error { + // 先删除具有相同 score 的所有成员 + scoreStr := strconv.FormatFloat(score, 'g', -1, 64) + _, err := e.client.ZRemRangeByScore(e.ctx, key, scoreStr, scoreStr).Result() + + if err != nil { + fmt.Printf("删除score失败,err:%s", err.Error()) + } + _, err = e.client.ZAdd(e.ctx, key, &redis.Z{ + Score: score, + Member: member, + }).Result() + + if err != nil { + return err + } + + return nil +} + +// 写入数据 +func (e *RedisHelper) AddSortSet(key string, score float64, member string) error { + _, err := e.client.ZAdd(e.ctx, key, &redis.Z{ + Score: score, + Member: member, + }).Result() + + if err != nil { + return err + } + + return nil +} + +// 删除指定元素 +func (e *RedisHelper) DelSortSet(key, member string) error { + return e.client.ZRem(e.ctx, key, member).Err() +} + +// RemoveBeforeScore 移除 Sorted Set 中分数小于等于指定值的数据 +// key: Sorted Set 的键 +// score: 分数上限,所有小于等于此分数的元素将被移除 +// 返回值: 移除的元素数量和可能的错误 +func (e *RedisHelper) RemoveBeforeScore(key string, score float64) (int64, error) { + if key == "" { + return 0, errors.New("key 不能为空") + } + if math.IsNaN(score) || math.IsInf(score, 0) { + return 0, errors.New("score 必须是有效数字") + } + + // 使用 ZRemRangeByScore 移除数据 + count, err := e.client.ZRemRangeByScore(e.ctx, key, "-inf", strconv.FormatFloat(score, 'f', -1, 64)).Result() + if err != nil { + return 0, fmt.Errorf("移除 Sorted Set 数据失败, key: %s, score: %f, err: %v", key, score, err) + } + + return count, nil +} + +// GetNextAfterScore 获取指定分数及之后的第一条数据(包含指定分数) +func (e *RedisHelper) GetNextAfterScore(key string, score float64) (string, error) { + // 使用 ZRangeByScore 获取大于等于 score 的第一条数据 + zs, err := e.client.ZRangeByScoreWithScores(e.ctx, key, &redis.ZRangeBy{ + Min: fmt.Sprintf("%f", score), // 包含指定分数 + Max: "+inf", // 上限为正无穷 + Offset: 0, // 从第 0 条开始 + Count: 1, // 只取 1 条 + }).Result() + + if err != nil { + return "", fmt.Errorf("获取数据失败: %v", err) + } + if len(zs) == 0 { + return "", nil // 没有符合条件的元素 + } + return zs[0].Member.(string), nil +} + +/* +获取sort set 所有数据 +*/ +func (e *RedisHelper) GetAllSortSet(key string) ([]string, error) { + return e.client.ZRange(e.ctx, key, 0, -1).Result() +} + +/* +获取sort set 所有数据和score +*/ +func (e *RedisHelper) GetRevRangeScoresSortSet(key string) ([]redis.Z, error) { + return e.client.ZRevRangeWithScores(e.ctx, key, 0, -1).Result() +} + +// 获取最后一条数据 +func (e *RedisHelper) GetLastSortSet(key string) ([]redis.Z, error) { + // 获取最后一个元素及其分数 + results, err := e.client.ZRevRangeWithScores(e.ctx, key, 0, 0).Result() + if err != nil { + return nil, fmt.Errorf("failed to get last member: %w", err) + } + + // 如果没有数据,返回空 + if len(results) == 0 { + return []redis.Z{}, nil + } + + return results, nil +} + +// 获取指定区间数据 +func (e *RedisHelper) GetSortSetMembers(key string, start, stop int64) ([]string, error) { + return e.client.ZRange(e.ctx, key, start, stop).Result() +} + +// 获取最后N条数据 +func (e *RedisHelper) GetLastSortSetMembers(key string, num int64) ([]string, error) { + return e.client.ZRevRange(e.ctx, key, 0, num).Result() +} + +// func (e *RedisHelper) DelSortSet(key,) + +// 根据索引范围删除 +func (e *RedisHelper) DelByRank(key string, start, stop int64) error { + return e.client.ZRemRangeByRank(e.ctx, key, start, stop).Err() +} + +// sort set end + +// GetUserLoginPwdErrFre 获取用户登录密码错误频次 +func (e *RedisHelper) GetUserLoginPwdErrFre(key string) (total int, wait time.Duration, err error) { + total, _ = e.client.Get(e.ctx, key).Int() + wait = e.client.TTL(e.ctx, key).Val() + return +} + +// SetUserLoginPwdErrFre 设置用户登录密码错误频次 +func (e *RedisHelper) SetUserLoginPwdErrFre(key string, expire time.Duration) (val int64, err error) { + val, err = e.client.Incr(e.ctx, key).Result() + if err != nil { + return + } + if err = e.client.Expire(e.ctx, key, expire).Err(); err != nil { + return + } + return +}