This commit is contained in:
2025-02-06 11:14:33 +08:00
commit 07847a2d9e
535 changed files with 65131 additions and 0 deletions

49
common/actions/create.go Normal file
View File

@ -0,0 +1,49 @@
package actions
import (
"net/http"
"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"
"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/common/dto"
"go-admin/common/models"
)
// CreateAction 通用新增动作
func CreateAction(control dto.Control) gin.HandlerFunc {
return func(c *gin.Context) {
log := api.GetRequestLogger(c)
db, err := pkg.GetOrm(c)
if err != nil {
log.Error(err)
return
}
//新增操作
req := control.Generate()
err = req.Bind(c)
if err != nil {
response.Error(c, http.StatusUnprocessableEntity, err, err.Error())
return
}
var object models.ActiveRecord
object, err = req.GenerateM()
if err != nil {
response.Error(c, 500, err, "模型生成失败")
return
}
object.SetCreateBy(user.GetUserId(c))
err = db.WithContext(c).Create(object).Error
if err != nil {
log.Errorf("Create error: %s", err)
response.Error(c, 500, err, "创建失败")
return
}
response.OK(c, object.GetId(), "创建成功")
c.Next()
}
}

61
common/actions/delete.go Normal file
View File

@ -0,0 +1,61 @@
package actions
import (
"net/http"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"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/common/dto"
"go-admin/common/models"
)
// DeleteAction 通用删除动作
func DeleteAction(control dto.Control) gin.HandlerFunc {
return func(c *gin.Context) {
db, err := pkg.GetOrm(c)
if err != nil {
log.Error(err)
return
}
msgID := pkg.GenerateMsgIDFromContext(c)
//删除操作
req := control.Generate()
err = req.Bind(c)
if err != nil {
log.Errorf("MsgID[%s] Bind error: %s", msgID, err)
response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败")
return
}
var object models.ActiveRecord
object, err = req.GenerateM()
if err != nil {
response.Error(c, 500, err, "模型生成失败")
return
}
object.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := GetPermissionFromContext(c)
db = db.WithContext(c).Scopes(
Permission(object.TableName(), p),
).Where(req.GetId()).Delete(object)
if err = db.Error; err != nil {
log.Errorf("MsgID[%s] Delete error: %s", msgID, err)
response.Error(c, 500, err, "删除失败")
return
}
if db.RowsAffected == 0 {
response.Error(c, http.StatusForbidden, nil, "无权删除该数据")
return
}
response.OK(c, object.GetId(), "删除成功")
c.Next()
}
}

58
common/actions/index.go Normal file
View File

@ -0,0 +1,58 @@
package actions
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"gorm.io/gorm"
"go-admin/common/dto"
"go-admin/common/models"
)
// IndexAction 通用查询动作
func IndexAction(m models.ActiveRecord, d dto.Index, f func() interface{}) gin.HandlerFunc {
return func(c *gin.Context) {
db, err := pkg.GetOrm(c)
if err != nil {
log.Error(err)
return
}
msgID := pkg.GenerateMsgIDFromContext(c)
list := f()
object := m.Generate()
req := d.Generate()
var count int64
//查询列表
err = req.Bind(c)
if err != nil {
response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败")
return
}
//数据权限检查
p := GetPermissionFromContext(c)
err = db.WithContext(c).Model(object).
Scopes(
dto.MakeCondition(req.GetNeedSearch()),
dto.Paginate(req.GetPageSize(), req.GetPageIndex()),
Permission(object.TableName(), p),
).
Find(list).Limit(-1).Offset(-1).
Count(&count).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
log.Errorf("MsgID[%s] Index error: %s", msgID, err)
response.Error(c, 500, err, "查询失败")
return
}
response.PageOK(c, list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功")
c.Next()
}
}

View File

@ -0,0 +1,96 @@
package actions
import (
"errors"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user"
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"gorm.io/gorm"
)
type DataPermission struct {
DataScope string
UserId int
DeptId int
RoleId int
}
func PermissionAction() gin.HandlerFunc {
return func(c *gin.Context) {
db, err := pkg.GetOrm(c)
if err != nil {
log.Error(err)
return
}
msgID := pkg.GenerateMsgIDFromContext(c)
var p = new(DataPermission)
if userId := user.GetUserIdStr(c); userId != "" {
p, err = newDataPermission(db, userId)
if err != nil {
log.Errorf("MsgID[%s] PermissionAction error: %s", msgID, err)
response.Error(c, 500, err, "权限范围鉴定错误")
c.Abort()
return
}
}
c.Set(PermissionKey, p)
c.Next()
}
}
func newDataPermission(tx *gorm.DB, userId interface{}) (*DataPermission, error) {
var err error
p := &DataPermission{}
err = tx.Table("sys_user").
Select("sys_user.user_id", "sys_role.role_id", "sys_user.dept_id", "sys_role.data_scope").
Joins("left join sys_role on sys_role.role_id = sys_user.role_id").
Where("sys_user.user_id = ?", userId).
Scan(p).Error
if err != nil {
err = errors.New("获取用户数据出错 msg:" + err.Error())
return nil, err
}
return p, nil
}
func Permission(tableName string, p *DataPermission) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if !config.ApplicationConfig.EnableDP {
return db
}
switch p.DataScope {
case "2":
return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", p.RoleId)
case "3":
return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", p.DeptId)
case "4":
return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%/"+pkg.IntToString(p.DeptId)+"/%")
case "5":
return db.Where(tableName+".create_by = ?", p.UserId)
default:
return db
}
}
}
func getPermissionFromContext(c *gin.Context) *DataPermission {
p := new(DataPermission)
if pm, ok := c.Get(PermissionKey); ok {
switch pm.(type) {
case *DataPermission:
p = pm.(*DataPermission)
}
}
return p
}
// GetPermissionFromContext 提供非action写法数据范围约束
func GetPermissionFromContext(c *gin.Context) *DataPermission {
return getPermissionFromContext(c)
}

5
common/actions/type.go Normal file
View File

@ -0,0 +1,5 @@
package actions
const (
PermissionKey = "dataPermission"
)

59
common/actions/update.go Normal file
View File

@ -0,0 +1,59 @@
package actions
import (
"net/http"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"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/common/dto"
"go-admin/common/models"
)
// UpdateAction 通用更新动作
func UpdateAction(control dto.Control) gin.HandlerFunc {
return func(c *gin.Context) {
db, err := pkg.GetOrm(c)
if err != nil {
log.Error(err)
return
}
msgID := pkg.GenerateMsgIDFromContext(c)
req := control.Generate()
//更新操作
err = req.Bind(c)
if err != nil {
response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败")
return
}
var object models.ActiveRecord
object, err = req.GenerateM()
if err != nil {
response.Error(c, 500, err, "模型生成失败")
return
}
object.SetUpdateBy(user.GetUserId(c))
//数据权限检查
p := GetPermissionFromContext(c)
db = db.WithContext(c).Scopes(
Permission(object.TableName(), p),
).Where(req.GetId()).Updates(object)
if err = db.Error; err != nil {
log.Errorf("MsgID[%s] Update error: %s", msgID, err)
response.Error(c, 500, err, "更新失败")
return
}
if db.RowsAffected == 0 {
response.Error(c, http.StatusForbidden, nil, "无权更新该数据")
return
}
response.OK(c, object.GetId(), "更新成功")
c.Next()
}
}

67
common/actions/view.go Normal file
View File

@ -0,0 +1,67 @@
package actions
import (
"errors"
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"net/http"
"github.com/gin-gonic/gin"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"gorm.io/gorm"
"go-admin/common/dto"
"go-admin/common/models"
)
// ViewAction 通用详情动作
func ViewAction(control dto.Control, f func() interface{}) gin.HandlerFunc {
return func(c *gin.Context) {
db, err := pkg.GetOrm(c)
if err != nil {
log.Error(err)
return
}
msgID := pkg.GenerateMsgIDFromContext(c)
//查看详情
req := control.Generate()
err = req.Bind(c)
if err != nil {
response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败")
return
}
var object models.ActiveRecord
object, err = req.GenerateM()
if err != nil {
response.Error(c, 500, err, "模型生成失败")
return
}
var rsp interface{}
if f != nil {
rsp = f()
} else {
rsp, _ = req.GenerateM()
}
//数据权限检查
p := GetPermissionFromContext(c)
err = db.Model(object).WithContext(c).Scopes(
Permission(object.TableName(), p),
).Where(req.GetId()).First(rsp).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
response.Error(c, http.StatusNotFound, nil, "查看对象不存在或无权查看")
return
}
if err != nil {
log.Errorf("MsgID[%s] View error: %s", msgID, err)
response.Error(c, 500, err, "查看失败")
return
}
response.OK(c, rsp, "查询成功")
c.Next()
}
}

135
common/apis/api.go Normal file
View File

@ -0,0 +1,135 @@
package apis
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk/api"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"github.com/go-admin-team/go-admin-core/sdk/pkg/response"
"gorm.io/gorm"
"go-admin/common/service"
)
type Api struct {
Context *gin.Context
Logger *logger.Helper
Orm *gorm.DB
Errors error
}
func (e *Api) AddError(err error) {
if e.Errors == nil {
e.Errors = err
} else if err != nil {
e.Logger.Error(err)
e.Errors = fmt.Errorf("%v; %w", e.Errors, err)
}
}
// MakeContext 设置http上下文
func (e *Api) MakeContext(c *gin.Context) *Api {
e.Context = c
e.Logger = api.GetRequestLogger(c)
return e
}
// GetLogger 获取上下文提供的日志
func (e *Api) GetLogger() *logger.Helper {
return api.GetRequestLogger(e.Context)
}
func (e *Api) Bind(d interface{}, bindings ...binding.Binding) *Api {
var err error
if len(bindings) == 0 {
bindings = append(bindings, binding.JSON, nil)
}
for i := range bindings {
switch bindings[i] {
case binding.JSON:
err = e.Context.ShouldBindWith(d, binding.JSON)
case binding.XML:
err = e.Context.ShouldBindWith(d, binding.XML)
case binding.Form:
err = e.Context.ShouldBindWith(d, binding.Form)
case binding.Query:
err = e.Context.ShouldBindWith(d, binding.Query)
case binding.FormPost:
err = e.Context.ShouldBindWith(d, binding.FormPost)
case binding.FormMultipart:
err = e.Context.ShouldBindWith(d, binding.FormMultipart)
case binding.ProtoBuf:
err = e.Context.ShouldBindWith(d, binding.ProtoBuf)
case binding.MsgPack:
err = e.Context.ShouldBindWith(d, binding.MsgPack)
case binding.YAML:
err = e.Context.ShouldBindWith(d, binding.YAML)
case binding.Header:
err = e.Context.ShouldBindWith(d, binding.Header)
default:
err = e.Context.ShouldBindUri(d)
}
if err != nil {
e.AddError(err)
}
}
return e
}
// GetOrm 获取Orm DB
func (e *Api) GetOrm() (*gorm.DB, error) {
db, err := pkg.GetOrm(e.Context)
if err != nil {
e.Error(500, err, "数据库连接获取失败")
return nil, err
}
return db, nil
}
// MakeOrm 设置Orm DB
func (e *Api) MakeOrm() *Api {
var err error
if e.Logger == nil {
err = errors.New("at MakeOrm logger is nil")
//e.Logger.Error(500, err, "at MakeOrm logger is nil")
e.AddError(err)
return e
}
db, err := pkg.GetOrm(e.Context)
if err != nil {
e.Logger.Error(500, err, "数据库连接获取失败")
e.AddError(err)
}
e.Orm = db
return e
}
func (e *Api) MakeService(c *service.Service) *Api {
c.Log = e.Logger
c.Orm = e.Orm
return e
}
// Error 通常错误数据处理
func (e *Api) Error(code int, err error, msg string) {
response.Error(e.Context, code, err, msg)
}
// OK 通常成功数据处理
func (e *Api) OK(data interface{}, msg string) {
response.OK(e.Context, data, msg)
}
// PageOK 分页数据处理
func (e *Api) PageOK(result interface{}, count int, pageIndex int, pageSize int, msg string) {
response.PageOK(e.Context, result, count, pageIndex, pageSize, msg)
}
// Custom 兼容函数
func (e *Api) Custom(data gin.H) {
response.Custum(e.Context, data)
}

View File

@ -0,0 +1,7 @@
package ad_userinfo_transferopen
// 用户是否开启内部转账功能
const (
Close = 1 // 关闭
Open = 3 // 开启
)

View File

@ -0,0 +1,6 @@
package agent_account_source
const (
UserRegister = 1 // 用户注册
SystemRegister = 3 // 系统添加
)

View File

@ -0,0 +1,7 @@
package agent_account_status
const (
Wait = 1 // 待审核
Audited = 3 // 已通过
Reject = 5 // 已拒绝
)

View File

@ -0,0 +1,6 @@
package agent_reward_category
const (
Straight = 1 // 直客佣金
Agent = 3 // 代理佣金
)

View File

@ -0,0 +1,96 @@
package businesstype
// BusinessType 操作业务
type BusinessType int
const (
// 注册登录业务
Other BusinessType = 0 // 其他
Register BusinessType = 1 // 注册
Login BusinessType = 2 // 登入
ResetPass BusinessType = 3 // 找回密码
ChangePassword BusinessType = 4 // 更改密码
ScanLogin BusinessType = 14 // 扫码登入
// 身份验证专用业务类型(请勿占用)
OpenPhoneAuth BusinessType = 11
ChangePhoneAuth BusinessType = 21
ClosePhoneAuth BusinessType = 31
OpenEmailAuth BusinessType = 12
ChangeEmailAuth BusinessType = 22
CloseEmailAuth BusinessType = 32
OpenGoogleAuth BusinessType = 13
ChangeGoogleAuth BusinessType = 23
CloseGoogleAuth BusinessType = 33
//提现验证使用
WithdrawAuth BusinessType = 201
//userkey验证
UserKeyAuth BusinessType = 202
//地址浦
AddAddress BusinessType = 203
// 以下请从 100 开始定义
OtcCaptcha BusinessType = 100 // OTC 验证码
AddPayAccount BusinessType = 101 // 新增收款账号
EditPayAccount BusinessType = 102 // 编辑收款账号
OTCSellerConfirmPay BusinessType = 103 // OTC 卖家已确认收款
OTCSellerAppeal BusinessType = 104 // OTC 买家申诉
OTCBuyerPaid BusinessType = 105 // OTC 买家已付款
OTCBuyerAppeal BusinessType = 106 // OTC 买家申诉
AgentEmailCode BusinessType = 107 // 代理商找回密码验证码
)
// 业务类型中文映射
var BusinessTypeMapCN = map[BusinessType]string{
Other: "其它",
Register: "注册",
Login: "登录",
ResetPass: "重置密码",
ChangePassword: "修改登录密码",
OpenPhoneAuth: "开启手机验证",
ChangePhoneAuth: "更改手机验证",
ClosePhoneAuth: "关闭手机验证",
OpenEmailAuth: "开启邮箱验证",
ChangeEmailAuth: "更改邮箱验证",
CloseEmailAuth: "关闭邮箱验证",
OpenGoogleAuth: "开启谷歌验证",
ChangeGoogleAuth: "更改谷歌验证",
CloseGoogleAuth: "关闭谷歌验证",
WithdrawAuth: "提现验证",
UserKeyAuth: "api验证",
AddAddress: "新增地址浦验证",
OtcCaptcha: "OTC验证码",
AddPayAccount: "新增收款账号",
EditPayAccount: "编辑收款账号",
}
// 业务类型英文映射
var BusinessTypeMapEN = map[BusinessType]string{
Other: "Other",
Register: "Register",
Login: "Login",
ResetPass: "Reset Password",
ChangePassword: "Change Login Password",
OpenPhoneAuth: "Enable mobile phone verification",
ChangePhoneAuth: "Change phone verification",
ClosePhoneAuth: "Turn off phone verification",
OpenEmailAuth: "Enable mailbox verification",
ChangeEmailAuth: "Change mailbox validation",
CloseEmailAuth: "Turn off mailbox verification",
OpenGoogleAuth: "Turn on Google Authentication",
ChangeGoogleAuth: "Change Google Authentication",
CloseGoogleAuth: "Turn off Google Authentication",
WithdrawAuth: "Withdraw Auth",
UserKeyAuth: "UserKey Auth",
AddAddress: "AddAddress Auth",
OtcCaptcha: "otc phone verification",
AddPayAccount: "otc add payaccount",
EditPayAccount: "otc edit payaccount",
}

View File

@ -0,0 +1,8 @@
package cointype
// cointype
const (
BTC int = 1 // BTC
USDT int = 2 // USDT
USD int = 3 // USD
)

View File

@ -0,0 +1,10 @@
package culatortype
// CulatorType 计算器
const (
Income int = 1 // 收益
TargetPrice int = 2 // 目标价格
FlatPrice int = 3 // 强平价格
OpenEable int = 4 // 可开
OpenPrice int = 5 // 开仓价格
)

View File

@ -0,0 +1,39 @@
package dealtype
// DealType 资金小类型1买单2卖单3手续费4划转(废弃)5已实现盈亏,6爆仓精算,7资金费用 13内部转入 14 内部转出 101现货划转合约 102 合约划转现货 103 现货转法币 104 法币转现货 105 币本位划转现货 106 现货划转到币本位
// 说明: 13-106之间不能插入其他数值
const (
Buy int = 1 // 1买单
Sell int = 2 // 2卖单
BrokerAge int = 3 // 手续费
Transfer int = 4 // 划转(废弃)
Profited int = 5 // 已实现盈亏
LossFee int = 6 // 爆仓精算
FundFee int = 7 // 资金费用
InTransfer int = 13 // 内部转入
OutTransfer int = 14 // 内部转出
VtsToFut int = 101 // 现货划转合约
FutToVts int = 102 // 合约转现货
VtsToOtc int = 103 // 现货转法币
OtcToVts int = 104 // 法币转现货
FutCoinToVts int = 105 // 币本位划转现货
VtsToFutCoin int = 106 // 现货划转到币本位
)
var Types = map[int]string{
Buy: "买单",
Sell: "卖单",
BrokerAge: "手续费",
Transfer: "划转",
Profited: "已实现盈亏",
LossFee: "爆仓精算",
FundFee: "资金费用",
InTransfer: "内部转入",
OutTransfer: "内部转出",
VtsToFut: "现货划转合约",
FutToVts: "合约转现货",
VtsToOtc: "现货转法币",
OtcToVts: "法币转现货",
FutCoinToVts: "币本位划转现货",
VtsToFutCoin: "现货划转到币本位",
}

View File

@ -0,0 +1,7 @@
package enable
// 是否启用(通用)
const (
Disable = 1 // 禁用
Enable = 3 // 启用
)

View File

@ -0,0 +1,6 @@
package fut_coin_type
const (
USDT = 1 // U本位
COIN = 2 // 币本位
)

View File

@ -0,0 +1,7 @@
package grid_buytype
// 交易网格买卖类型
const (
Buy = 1 // 买
Sell = 2 // 卖
)

View File

@ -0,0 +1,7 @@
package grid_createtype
// 交易网格创建方式
const (
OneCreation = 1 // 一键创建
ManualCreation = 2 // 手动创建
)

View File

@ -0,0 +1,8 @@
package grid_dealtype
// 交易网格资金流水类型
const (
Occupy = 1 // 策略占用
Release = 2 // 策略释放
Profit = 3 // 策略利润
)

View File

@ -0,0 +1,7 @@
package grid_netmode
// 现货交易网格模式
const (
EqualRatio = 1 // 等比
EqualDifference = 2 // 等差
)

View File

@ -0,0 +1,9 @@
package grid_spotorder_status
// 现货交易网格委托单状态
const (
UnDeal = 1 // 未成交
Partial = 2 // 部分成交
Complete = 3 // 完成成交
Cancel = 4 // 取消
)

View File

@ -0,0 +1,13 @@
package grid_spotpolicy_state
// 现货交易网格状态
const (
Wait = 1 // 等待触发
Starting = 2 // 正在启动
Start = 3 // 启动完成
Stopping = 4 // 正在停止
ManualStop = 5 // 手动停止
ProfitStop = 6 // 止盈停止
LossStop = 7 // 止损停止
SignalStop = 8 // 信号触发停止
)

View File

@ -0,0 +1,7 @@
package grid_triggertype
// 交易网格触发条件
const (
Immediately = 1 // 立即触发
Price = 2 // 价格触发
)

View File

@ -0,0 +1,9 @@
package holddaylog_chart_daytype
// 1==24小时 3==7天 5==30天 7=3个月
const (
Hour_24 = 1 // 24小时
Day_7 = 3 // 7天
Day_30 = 5 // 30天
Month_3 = 7 // 3个月
)

View File

@ -0,0 +1,54 @@
package kyctype
// IdCardLevel 身份等级
type IdCardLevel int
const (
// Junior 初级认证
Junior IdCardLevel = 1
// Middle 中级认证
Middle IdCardLevel = 2
// Senior 高级认证
Senior IdCardLevel = 3
)
// IdCardState 审核状态
type IdCardState int
const (
// UnAuth 待审核
UnAuth IdCardState = 1
// AuthFailed 审核失败
AuthFailed IdCardState = 2
// AuthSuccess 审核完成
AuthSuccess IdCardState = 3
)
// IdCardType 审核类型
type IdCardType int
const (
// IdCard 身份证
IdCard IdCardType = 1
// Passport 护照
Passport IdCardType = 2
// Drive 驾照
Drive IdCardType = 3
)
// VerifyType 审核类型
type VerifyType int
const (
// Third 第三方审核
Third VerifyType = 1
// System 系统审核
System VerifyType = 2
)

View File

@ -0,0 +1,13 @@
package enum
// 语言
var LanguageMap = map[string]string{
"en": "英语",
"jp": "日本语",
"kr": "韩语",
"my": "马来西亚语",
"th": "泰国语",
"vn": "越南语",
"zh-CN": "简体中文",
"zh-HK": "繁体中文",
}

View File

@ -0,0 +1,24 @@
package logtype
// LogType 日志类型
type LogType int
const (
Min LogType = iota // 最小值
Login // 登入
Logout // 登出
Create // 新增
Update // 编辑
Delete // 删除
ChangePwd // 修改密码
Max // 最大值
)
var LogTypeMap = map[LogType]string{
Login: "登入",
Logout: "登出",
Create: "新增",
Update: "编辑",
Delete: "删除",
ChangePwd: "修改密码",
}

View File

@ -0,0 +1,7 @@
package longshorttype
// longshorttype
const (
Long = 1 // 做多
Short = 2 // 做空
)

View File

@ -0,0 +1,10 @@
package ordertype
//订单类型1限价2限价止盈止损3市价,4止盈止损市价单,5系统强平委托
const (
Limit = 1 //限价单,下单类型
StopLimit = 2 //止盈止损限价单,下单类型
Market = 3 //市价单,下单类型
StopMarket = 4 //止盈止损市价单,下单类型
Force = 5 //系统强平委托
)

View File

@ -0,0 +1,8 @@
package otc_advaduitstatus
// otc广告审核状态
const (
Pending = 1 // 待审核
Pass = 3 // 审核通过
Reject = 5 // 审核拒绝
)

View File

@ -0,0 +1,7 @@
package otc_advertisetype
// 广告类型
const (
Buy = 1 // 购买
Sell = 3 // 出售
)

View File

@ -0,0 +1,7 @@
package otc_advstatus
// otc广告状态
const (
Offline = 1 // 下线
Online = 3 // 上线
)

View File

@ -0,0 +1,10 @@
package otc_appealprogress
//申诉进程
const (
WaitDefendantProcess = 1 // 等待被诉方处理
WaitAppealProcess = 3 // 等待申诉方处理
Revoked = 5 // 已撤销
WaitCustomer = 7 // 等待客服处理
Arbitrated = 9 // 已仲裁
)

View File

@ -0,0 +1,19 @@
package otc_appealreason
// 申诉原因
const (
Paid = 1 // 我已付款,卖家未放行
PayMore = 3 // 我向卖家多转了钱
NotPay = 5 // 我没有收到买家付款
MoneyErr = 7 // 买家付款金额不对
Other = 9 // 其他
)
var Types = map[int]string{
Paid: "我已付款,卖家未放行",
PayMore: "我向卖家多转了钱",
NotPay: "我没有收到买家付款",
MoneyErr: "买家付款金额不对",
Other: "其他",
}

View File

@ -0,0 +1,8 @@
package otc_appealstatus
// otc仲裁状态
const (
Not = 1 // 未仲裁
BuyerWin = 3 // 买家胜,放行
SellerWin = 5 // 卖家胜,取消订单
)

View File

@ -0,0 +1,6 @@
package otc_auditlevel
// 商家认证等级
const (
Certified = 3 // 认证商家
)

View File

@ -0,0 +1,8 @@
package otc_chatlogsourcetype
// 消息来源
const (
Android = 1
Ios = 3
Pc = 5
)

View File

@ -0,0 +1,7 @@
package otc_chatlogstatus
// 聊天发送状态
const (
Fail = 1 // 发送失败
Success = 3 // 发送成功
)

View File

@ -0,0 +1,8 @@
package otc_chattype
// 通讯软件类型
const (
Telegram = 1
Wechat = 3
QQ = 5
)

View File

@ -0,0 +1,7 @@
package otc_complaint
// otc申诉方
const (
Buyer = 1 // 买方
Seller = 3 // 卖方
)

View File

@ -0,0 +1,8 @@
package otc_consult
// otc协商结果
const (
Not = 1 // 未协商
Unable = 3 // 无法协商
Negotiated = 5 // 协商解决
)

View File

@ -0,0 +1,20 @@
package otc_holdlogdealtype
// otc 资金类型
const (
Buy = 1 // 买入
Sell = 3 // 卖出
Frozen = 7 // 冻结
AppendBond = 9 // 追加保证金
UnFreeze = 11 // 解冻
Deduct = 13 // 扣除保证金
VtsToOtc = 103 // 现货转法币
OtcToVts = 104 // 法币转现货
Buckle = 105 // 划扣
SaleAdviserAdd = 201 // 出售广告添加
SaleAdviserEdit = 203 // 出售广告编辑
MerchantUnVerify = 205 // 商家解除认证 保证金将解除冻结
MerchantAdd = 207 // 法币商家新增
MerchantEdit = 209 // 法币商家编辑
MerchantFrozenBond = 210 // 法币商家冻结保证金
)

View File

@ -0,0 +1,7 @@
package otc_holdstatus
// otc 账户状态
const (
Frozen = 1 // 冻结
Normal = 3 // 正常
)

View File

@ -0,0 +1,8 @@
package otc_linktype
// 紧急联系人类型
const (
Family = 1 // 家人
Friend = 3 // 朋友
Colleague = 5 // 同事
)

View File

@ -0,0 +1,7 @@
package otc_merchantout_status
const (
Wait = 1 // 待处理
Passed = 3 // 已通过
Rejected = 5 // 不通过
)

View File

@ -0,0 +1,11 @@
package otc_merchantstatus
// otc商家状态1 申请中3 不通过5 正常7 已禁用9 已解除认证
const (
Applying = 1 // 申请中
Fail = 3 // 不通过
Normal = 5 // 正常
Disabled = 7 // 已禁用
Revoked = 9 // 已解除认证
BondNoEnough = 11 // 保证金不足
)

View File

@ -0,0 +1,7 @@
package otc_msgtype
// otc 聊天消息类型
const (
Text = 1 // 文本
Image = 3 // 图片
)

View File

@ -0,0 +1,9 @@
package otc_orderstatus
const (
UnPay = 1 // 未付款
WaitMoney = 3 // 待放币
Complete = 5 // 已完成
Complain = 7 // 申诉中
Cancel = 9 // 已取消
)

View File

@ -0,0 +1,7 @@
package otc_ordertype
// 订单方向
const (
Buy = 1 // 买
Sell = 3 // 卖
)

View File

@ -0,0 +1,7 @@
package otc_pricetype
//定价方式
const (
Fix = 1 // 固定价格
Float = 3 // 浮动价格
)

View File

@ -0,0 +1,10 @@
package otc_progress
// otc申诉进程
const (
WaitSellerHandle = 1 // 等待卖家处理
WaitBuyerHandle = 3 // 等待买家处理
Revoked = 5 // 已撤销申诉
WaitCustomer = 7 // 等待客服处理
Arbitrated = 9 // 已仲裁
)

View File

@ -0,0 +1,7 @@
package otc_rateway
//浮动方式
const (
Up = 1 // 上浮
Down = 3 // 下浮
)

View File

@ -0,0 +1,7 @@
package otc_tradetype
// 交易区
const (
Fast = 1 // 快捷区
Self = 3 // 自选区
)

View File

@ -0,0 +1,7 @@
package showhide
// 是否隐藏账号(通用)
const (
Hide = 1 // 隐藏
Show = 3 // 显示
)

View File

@ -0,0 +1,25 @@
package smstemplate
// 短信模板 Key 命名规则SmsTemplate_BusinessType_Language
const (
// 通用业务模板 - 中文
SmsTemplate_1_CN = "您正在进行【${business}】,验证码:${code}5分钟内有效请勿泄露给任何人。如非本人操作请立即联系官方客服。"
// 通用业务模板 - 英文
SmsTemplate_1_EN = "Are you doing [${business}] Verification code: ${code}, valid within 5 minutes, please do not disclose it to anyone."
// 卖家已确认收款_CN
SmsTemplate_2_CN = "订单号 ${ordersn},卖家已确认收款,订单已完成,请前往账户查收资产。"
// 卖家已确认收款_EN
SmsTemplate_2_EN = "Order number ${ordersn}, the seller has confirmed the payment, the order has been completed, please go to the account to check the assets."
// 卖家申诉_CN
SmsTemplate_3_CN = "订单号 ${ordersn},卖家已发起申诉,请尽快前往处理该订单申诉。"
// 卖家申诉_EN
SmsTemplate_3_EN = "Order number ${ordersn}, the seller has initiated an appeal, please go to the appeal as soon as possible."
// 买家已付款_CN
SmsTemplate_4_CN = "订单号 ${ordersn},买家已付款,请查收款项进行放行。"
// 买家已付款_EN
SmsTemplate_4_EN = "Order number ${ordersn}, the buyer has paid, please check the payment for release."
// 买家申诉_CN
SmsTemplate_5_CN = "订单号 ${ordersn},买家已发起申诉,请尽快处理该订单申诉。"
// 买家申诉_EN
SmsTemplate_5_EN = "Order number ${ordersn}, the buyer has initiated an appeal, please handle the order appeal as soon as possible."
)

View File

@ -0,0 +1,10 @@
package transfertype
const (
Vts2Fut = 1 // 现货划转合约
Fut2Vts = 2 // 合约划转现货
Vts2Otc = 3 // 现货划转Otc
Otc2Vts = 4 // Otc划转现货
FutCoin2Vts = 5 // 币本位划转现货
Vts2FutCoin = 6 // 现货划转到币本位
)

View File

@ -0,0 +1,7 @@
package usefree
// usefree
const (
Available = 1 // 可用
Frozen = 2 // 冻结
)

View File

@ -0,0 +1,8 @@
package vts_alertset_frequency
// frequency 提醒频率
const (
OnlyOnce = 1 // 仅提醒一次
OnceDay = 2 // 每日提醒一次
Always = 3 // 持续提醒(每当价格达到该值,则提醒一次)
)

View File

@ -0,0 +1,7 @@
package vts_alertset_state
// state 设置状态
const (
Normal = 1 // 正常
Stop = 2 // 停止(比如频率是只提醒一次提醒一次完就修改为2)
)

View File

@ -0,0 +1,11 @@
package vts_alertset_type
// alerttype 预警类型
const (
RisesAbove = 1 //1 价格涨到
DropTo = 2 //2 价格跌到
ChangeOver = 3 //3 涨幅达到
ChangeUnder = 4 //4 跌幅达到
H24ChangeOver = 5 //5 h24小时涨幅
H24ChangeUnder = 6 //6 h24小时跌幅
)

View File

@ -0,0 +1,43 @@
package warehousetype
// WareHouse
const (
Isolated = 1 // 逐仓
Cross = 2 // 全仓
)
// DirectHouse 持仓模式
const (
OneWayMode = 1 // 单向持仓
HedgeMode = 2 // 双向持仓
)
// 下单持仓类型 1开多 2平多 3开空 4平空
const (
BuyLong = iota + 1 // 开多
SellLog // 平多
SellShort // 开空
BuyShort // 平空
)
const (
BOTH = "both"
LONG = "long"
SHORT = "short"
)
var (
PosSide = []string{BOTH, LONG, SHORT} // 持仓方向集合
)
const (
Buy = 1 // 下单方向买
Sell = 2 // 下单方向卖
)
const (
PositionTypeOpen = 1 // 开仓
PositionTypeCloseOpen = 2 // 反向开仓
PositionTypeClose = 3 // 平仓
PositionTypeForce = 4 // 系统强平
PositionTypeSysClose = 5 // 系统平仓
)

View File

@ -0,0 +1,7 @@
package yesno
// 是否(通用)
const (
No = 1 // 否
Yes = 3 // 是
)

View File

@ -0,0 +1,62 @@
package rediskey
const (
IPPositionCache = "_IPPositionCache" // IP 归属地缓存
AppLoginUserToken = "_AppLoginUserToken_%d" // App登录用户的Token {uid}
AgentLoginUserToken = "_AgentLoginUserToken_%d" // PC端代理商登录用户的Token
AgentEmailCode = "_AgentEmailCode_%d"
AdminLoginUserToken = "_AdminLoginUserToken_%d" // 后台登录用户的Token {uid}
PCLoginUserToken = "_PCLoginUserToken_%d" // PC端登录token
UserLoginPwdErrFre = "_UserLoginPwdErrFre_%d" // 用户登录密码错误次数 {uid}
UserCaptchaSendFre = "_UserCaptchaSendFre_%v_%d" // 用户验证码发送频次 {uid|ip}_{business}
UserLoginWsClient = "_UserLoginWsClient" // websocket连接的客户端
ScanLoginSecret = "_ScanLoginSecret_%v" // 扫码登录秘钥
StatusCodeLanguage = "_StatusCodeLanguage_%v" // 状态码语言包_en
PCRegisterEmail = "_PCRegister_%v" // 用户注册时邮箱key
PCRegisterMobile = "_PCRegisterMobile_%v" // 用户注册时手机key
SpotSymbolTicker = "_SpotSymbolTicker_" // 现货交易对行情
FutSymbolTicker = "_FutSymbolTicker_" // 合约交易对行情
PreOrderScriptList = "_ProOrderScriptList_" // 脚本执行list
PreSpotOrderList = "_PreSpotOrderList_:%s" // 待触发的现货订单集合{交易所类型 exchange_type}
PreFutOrderList = "_PreFutOrderList_:%s" // 待触发的订单集合 {交易所类型 exchange_type}
API_USER = "api_user:%v" // api用户
SystemSetting = "system_setting" //系统设置
ApiGroup = "api_group:%v" //api用户组 {id}
ApiGroupAll = "api_group:"
ApiUserActiveList = "api_user_active_list" //已启用待连接websocket的api
ApiUserDeleteList = "api_user_delete_list" //已删除待删除的api
FutStopTrigger = "fut_trigger_stop_lock:%v_%s" //合约止损触发锁
SpotStopTrigger = "spot_trigger_stop_lock:%v_%s" //现货止损触发锁
SpotAddPositionTrigger = "spot_addposition_trigger:%v_%s" //现货加仓触发 {apiuserid|symbol}
FutAddPositionTrigger = "fut_addposition_trigger:%v_%s" //合约加仓触发 {apiuserid|symbol}
SpotTrigger = "spot_trigger_lock:%v_%s" //现货触发 {apiuserid|symbol}
FutTrigger = "fut_trigger_lock:%v_%s" //合约触发 {apiuserid|symbol}
SpotCallBack = "spot_callback:%s" //现货回调 {ordersn}
FutCallBack = "fut_callback:%s" //合约回调 {ordersn}
//需要清理键值---------BEGIN---------------
UserHolding = "api_user_hold:%v" //用户持仓币种 {主单apiid} 不是交易对
SpotHedgeClosePosition = "spot_hedge_close_position:%v_%s" //现货对冲平仓 {mainorderid|symbol}
FuturesHedgeClosePosition = "futures_hedge_close_position:%v_%s" //合约对冲平仓 {mainorderid|symbol}
SpotStopLossList = "spot_stoploss_list" //现货止损待触发列表
FuturesStopLossList = "futures_stoploss_list" //合约止损待触发列表
SpotAddPositionList = "spot_add_position_list" //现货加仓待触发
FuturesAddPositionList = "futures_add_position_list" //合约加仓待触发
StoplossMarkt = "stop_loss_markt" //对冲保险单(市价) 对冲单成交之后、清除
HoldeA = "holde_a:%v" //持仓A {主单id}
HoldeB = "holde_b:%v" //持仓B {主单id}
HedgeClosePosition = "hedge_close_position:%v" //对冲平仓记录 {主单id}
//需要清理键值---------END-----------------
)

View File

@ -0,0 +1,28 @@
package userlockkey
import (
"strconv"
)
var (
preOrder = "Order-" //分布式锁现货委托单+划转前缀
preFutOrder = "futOrder-" //分布式锁u本位--合约委托单+划转前缀
preFutCoinOrder = "futCoinOrder-" //分布式锁币本位--合约委托单+划转前缀
)
// GetUserLockKey 分布式锁现货 用户 key
func GetUserLockKey(userId int) string {
return preOrder + strconv.Itoa(userId)
}
// GetUserFutLockKey u本位--分布式锁合约 用户 key
func GetUserFutLockKey(userId int) string {
return preFutOrder + strconv.Itoa(userId)
}
// GetUserFutCoinLockKey 币本位--分布式锁合约 用户 key
func GetUserFutCoinLockKey(userId int) string {
return preFutCoinOrder + strconv.Itoa(userId)
}

View File

@ -0,0 +1,65 @@
package database
import (
"time"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-admin-team/go-admin-core/sdk"
toolsConfig "github.com/go-admin-team/go-admin-core/sdk/config"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
mycasbin "github.com/go-admin-team/go-admin-core/sdk/pkg/casbin"
toolsDB "github.com/go-admin-team/go-admin-core/tools/database"
. "github.com/go-admin-team/go-admin-core/tools/gorm/logger"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"go-admin/common/global"
)
// Setup 配置数据库
func Setup() {
for k := range toolsConfig.DatabasesConfig {
setupSimpleDatabase(k, toolsConfig.DatabasesConfig[k])
}
}
func setupSimpleDatabase(host string, c *toolsConfig.Database) {
if global.Driver == "" {
global.Driver = c.Driver
}
log.Infof("%s => %s", host, pkg.Green(c.Source))
registers := make([]toolsDB.ResolverConfigure, len(c.Registers))
for i := range c.Registers {
registers[i] = toolsDB.NewResolverConfigure(
c.Registers[i].Sources,
c.Registers[i].Replicas,
c.Registers[i].Policy,
c.Registers[i].Tables)
}
resolverConfig := toolsDB.NewConfigure(c.Source, c.MaxIdleConns, c.MaxOpenConns, c.ConnMaxIdleTime, c.ConnMaxLifeTime, registers)
db, err := resolverConfig.Init(&gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
Logger: New(
logger.Config{
SlowThreshold: time.Second,
Colorful: true,
LogLevel: logger.LogLevel(
log.DefaultLogger.Options().Level.LevelForGorm()),
},
),
}, opens[c.Driver])
if err != nil {
log.Fatal(pkg.Red(c.Driver+" connect error :"), err)
} else {
log.Info(pkg.Green(c.Driver + " connect success !"))
}
e := mycasbin.Setup(db, "")
sdk.Runtime.SetDb(host, db)
sdk.Runtime.SetCasbin(host, e)
}

16
common/database/open.go Normal file
View File

@ -0,0 +1,16 @@
//go:build !sqlite3
package database
import (
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
var opens = map[string]func(string) gorm.Dialector{
"mysql": mysql.Open,
"postgres": postgres.Open,
"sqlserver": sqlserver.Open,
}

View File

@ -0,0 +1,19 @@
//go:build sqlite3
// +build sqlite3
package database
import (
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
var opens = map[string]func(string) gorm.Dialector{
"mysql": mysql.Open,
"postgres": postgres.Open,
"sqlite3": sqlite.Open,
"sqlserver": sqlserver.Open,
}

8
common/dto/api_group.go Normal file
View File

@ -0,0 +1,8 @@
package dto
type ApiGroupDto struct {
Id int `json:"id"`
Name string `json:"name"`
ApiUserId int `json:"apiUserId"`
ChildApiUserId int `json:"childApiUserId"`
}

74
common/dto/auto_form.go Normal file
View File

@ -0,0 +1,74 @@
package dto
type AutoForm struct {
Fields []Field `json:"fields"`
FormRef string `json:"formRef"`
FormModel string `json:"formModel"`
Size string `json:"size"`
LabelPosition string `json:"labelPosition"`
LabelWidth int `json:"labelWidth"`
FormRules string `json:"formRules"`
Gutter int `json:"gutter"`
Disabled bool `json:"disabled"`
Span int `json:"span"`
FormBtns bool `json:"formBtns"`
}
type Config struct {
Label string `json:"label"`
LabelWidth interface{} `json:"labelWidth"`
ShowLabel bool `json:"showLabel"`
ChangeTag bool `json:"changeTag"`
Tag string `json:"tag"`
TagIcon string `json:"tagIcon"`
Required bool `json:"required"`
Layout string `json:"layout"`
Span int `json:"span"`
Document string `json:"document"`
RegList []interface{} `json:"regList"`
FormId int `json:"formId"`
RenderKey int64 `json:"renderKey"`
DefaultValue interface{} `json:"defaultValue"`
ShowTip bool `json:"showTip,omitempty"`
ButtonText string `json:"buttonText,omitempty"`
FileSize int `json:"fileSize,omitempty"`
SizeUnit string `json:"sizeUnit,omitempty"`
}
type Option struct {
Label string `json:"label"`
Value string `json:"value"`
}
type Slot struct {
Prepend string `json:"prepend,omitempty"`
Append string `json:"append,omitempty"`
ListType bool `json:"list-type,omitempty"`
Options []Option `json:"options,omitempty"`
}
type Field struct {
Config Config `json:"__config__"`
Slot Slot `json:"__slot__"`
Placeholder string `json:"placeholder,omitempty"`
Style Style `json:"style,omitempty"`
Clearable bool `json:"clearable,omitempty"`
PrefixIcon string `json:"prefix-icon,omitempty"`
SuffixIcon string `json:"suffix-icon,omitempty"`
Maxlength interface{} `json:"maxlength"`
ShowWordLimit bool `json:"show-word-limit,omitempty"`
Readonly bool `json:"readonly,omitempty"`
Disabled bool `json:"disabled"`
VModel string `json:"__vModel__"`
Action string `json:"action,omitempty"`
Accept string `json:"accept,omitempty"`
Name string `json:"name,omitempty"`
AutoUpload bool `json:"auto-upload,omitempty"`
ListType string `json:"list-type,omitempty"`
Multiple bool `json:"multiple,omitempty"`
Filterable bool `json:"filterable,omitempty"`
}
type Style struct {
Width string `json:"width"`
}

106
common/dto/generate.go Normal file
View File

@ -0,0 +1,106 @@
package dto
import (
vd "github.com/bytedance/go-tagexpr/v2/validator"
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/api"
)
type ObjectById struct {
Id int `uri:"id"`
Ids []int `json:"ids"`
}
func (s *ObjectById) Bind(ctx *gin.Context) error {
var err error
log := api.GetRequestLogger(ctx)
err = ctx.ShouldBindUri(s)
if err != nil {
log.Warnf("ShouldBindUri error: %s", err.Error())
return err
}
if ctx.Request.Method == http.MethodDelete {
err = ctx.ShouldBind(&s)
if err != nil {
log.Warnf("ShouldBind error: %s", err.Error())
return err
}
if len(s.Ids) > 0 {
return nil
}
if s.Ids == nil {
s.Ids = make([]int, 0)
}
if s.Id != 0 {
s.Ids = append(s.Ids, s.Id)
}
}
if err = vd.Validate(s); err != nil {
log.Errorf("Validate error: %s", err.Error())
return err
}
return err
}
func (s *ObjectById) GetId() interface{} {
if len(s.Ids) > 0 {
s.Ids = append(s.Ids, s.Id)
return s.Ids
}
return s.Id
}
type ObjectGetReq struct {
Id int `uri:"id"`
}
func (s *ObjectGetReq) Bind(ctx *gin.Context) error {
var err error
log := api.GetRequestLogger(ctx)
err = ctx.ShouldBindUri(s)
if err != nil {
log.Warnf("ShouldBindUri error: %s", err.Error())
return err
}
if err = vd.Validate(s); err != nil {
log.Errorf("Validate error: %s", err.Error())
return err
}
return err
}
func (s *ObjectGetReq) GetId() interface{} {
return s.Id
}
type ObjectDeleteReq struct {
Ids []int `json:"ids"`
}
func (s *ObjectDeleteReq) Bind(ctx *gin.Context) error {
var err error
log := api.GetRequestLogger(ctx)
err = ctx.ShouldBind(&s)
if err != nil {
log.Warnf("ShouldBind error: %s", err.Error())
return err
}
if len(s.Ids) > 0 {
return nil
}
if s.Ids == nil {
s.Ids = make([]int, 0)
}
if err = vd.Validate(s); err != nil {
log.Errorf("Validate error: %s", err.Error())
return err
}
return err
}
func (s *ObjectDeleteReq) GetId() interface{} {
return s.Ids
}

12
common/dto/order.go Normal file
View File

@ -0,0 +1,12 @@
package dto
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func OrderDest(sort string, bl bool) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Order(clause.OrderByColumn{Column: clause.Column{Name: sort}, Desc: bl})
}
}

20
common/dto/pagination.go Normal file
View File

@ -0,0 +1,20 @@
package dto
type Pagination struct {
PageIndex int `form:"pageIndex"`
PageSize int `form:"pageSize"`
}
func (m *Pagination) GetPageIndex() int {
if m.PageIndex <= 0 {
m.PageIndex = 1
}
return m.PageIndex
}
func (m *Pagination) GetPageSize() int {
if m.PageSize <= 0 {
m.PageSize = 10
}
return m.PageSize
}

84
common/dto/search.go Normal file
View File

@ -0,0 +1,84 @@
package dto
import (
"github.com/go-admin-team/go-admin-core/tools/search"
"go-admin/common/global"
"gorm.io/gorm"
)
type GeneralDelDto struct {
Id int `uri:"id" json:"id" validate:"required"`
Ids []int `json:"ids"`
}
func (g GeneralDelDto) GetIds() []int {
ids := make([]int, 0)
if g.Id != 0 {
ids = append(ids, g.Id)
}
if len(g.Ids) > 0 {
for _, id := range g.Ids {
if id > 0 {
ids = append(ids, id)
}
}
} else {
if g.Id > 0 {
ids = append(ids, g.Id)
}
}
if len(ids) <= 0 {
//方式全部删除
ids = append(ids, 0)
}
return ids
}
type GeneralGetDto struct {
Id int `uri:"id" json:"id" validate:"required"`
}
func MakeCondition(q interface{}) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
condition := &search.GormCondition{
GormPublic: search.GormPublic{},
Join: make([]*search.GormJoin, 0),
}
search.ResolveSearchQuery(global.Driver, q, condition)
for _, join := range condition.Join {
if join == nil {
continue
}
db = db.Joins(join.JoinOn)
for k, v := range join.Where {
db = db.Where(k, v...)
}
for k, v := range join.Or {
db = db.Or(k, v...)
}
for _, o := range join.Order {
db = db.Order(o)
}
}
for k, v := range condition.Where {
db = db.Where(k, v...)
}
for k, v := range condition.Or {
db = db.Or(k, v...)
}
for _, o := range condition.Order {
db = db.Order(o)
}
return db
}
}
func Paginate(pageSize, pageIndex int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
offset := (pageIndex - 1) * pageSize
if offset < 0 {
offset = 0
}
return db.Offset(offset).Limit(pageSize)
}
}

21
common/dto/type.go Normal file
View File

@ -0,0 +1,21 @@
package dto
import (
"github.com/gin-gonic/gin"
"go-admin/common/models"
)
type Index interface {
Generate() Index
Bind(ctx *gin.Context) error
GetPageIndex() int
GetPageSize() int
GetNeedSearch() interface{}
}
type Control interface {
Generate() Control
Bind(ctx *gin.Context) error
GenerateM() (models.ActiveRecord, error)
GetId() interface{}
}

View File

@ -0,0 +1,45 @@
package file_store
import "fmt"
type OXS struct {
// Endpoint 访问域名
Endpoint string
// AccessKeyID AK
AccessKeyID string
// AccessKeySecret AKS
AccessKeySecret string
// BucketName 桶名称
BucketName string
}
// Setup 配置文件存储driver
func (e *OXS) Setup(driver DriverType, options ...ClientOption) FileStoreType {
fileStoreType := driver
var fileStore FileStoreType
switch fileStoreType {
case AliYunOSS:
fileStore = new(ALiYunOSS)
err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName)
if err != nil {
fmt.Println(err)
}
return fileStore
case HuaweiOBS:
fileStore = new(HuaWeiOBS)
err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName)
if err != nil {
fmt.Println(err)
}
return fileStore
case QiNiuKodo:
fileStore = new(QiNiuKODO)
err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName)
if err != nil {
fmt.Println(err)
}
return fileStore
}
return nil
}

View File

@ -0,0 +1,27 @@
package file_store
// DriverType 驱动类型
type DriverType string
const (
// HuaweiOBS 华为云OBS
HuaweiOBS DriverType = "HuaweiOBS"
// AliYunOSS 阿里云OSS
AliYunOSS DriverType = "AliYunOSS"
// QiNiuKodo 七牛云kodo
QiNiuKodo DriverType = "QiNiuKodo"
)
type ClientOption map[string]interface{}
// TODO: FileStoreType名称待定
// FileStoreType OXS
type FileStoreType interface {
// Setup 装载 endpoint sss
Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error
// UpLoad 上传
UpLoad(yourObjectName string, localFile interface{}) error
// GetTempToken 获取临时Token
GetTempToken() (string, error)
}

111
common/file_store/kodo.go Normal file
View File

@ -0,0 +1,111 @@
package file_store
import (
"context"
"fmt"
"github.com/qiniu/go-sdk/v7/auth/qbox"
"github.com/qiniu/go-sdk/v7/storage"
)
type Zone string
const (
// HuaDong 华东
HuaDong Zone = "HuaDong"
// HuaBei 华北
HuaBei Zone = "HuaBei"
// HuaNan 华南
HuaNan Zone = "HuaNan"
// BeiMei 北美
BeiMei Zone = "BeiMei"
// XinJiaPo 新加坡
XinJiaPo Zone = "XinJiaPo"
)
type QiNiuKODO struct {
Client interface{}
BucketName string
cfg storage.Config
options []ClientOption
}
func (e *QiNiuKODO) getToken() string {
putPolicy := storage.PutPolicy{
Scope: e.BucketName,
}
if len(e.options) > 0 && e.options[0]["Expires"] != nil {
putPolicy.Expires = e.options[0]["Expires"].(uint64)
}
upToken := putPolicy.UploadToken(e.Client.(*qbox.Mac))
return upToken
}
//Setup 装载
//endpoint sss
func (e *QiNiuKODO) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error {
mac := qbox.NewMac(accessKeyID, accessKeySecret)
// 获取存储空间。
cfg := storage.Config{}
// 空间对应的机房
e.setZoneORDefault(cfg, options...)
// 是否使用https域名
cfg.UseHTTPS = true
// 上传是否使用CDN上传加速
cfg.UseCdnDomains = false
e.Client = mac
e.BucketName = BucketName
e.cfg = cfg
e.options = options
return nil
}
// setZoneORDefault 设置Zone或者默认华东
func (e *QiNiuKODO) setZoneORDefault(cfg storage.Config, options ...ClientOption) {
if len(options) > 0 && options[0]["Zone"] != nil {
if _, ok := options[0]["Zone"].(Zone); !ok {
cfg.Zone = &storage.ZoneHuadong
}
switch options[0]["Zone"].(Zone) {
case HuaDong:
cfg.Zone = &storage.ZoneHuadong
case HuaBei:
cfg.Zone = &storage.ZoneHuabei
case HuaNan:
cfg.Zone = &storage.ZoneHuanan
case BeiMei:
cfg.Zone = &storage.ZoneBeimei
case XinJiaPo:
cfg.Zone = &storage.ZoneXinjiapo
default:
cfg.Zone = &storage.ZoneHuadong
}
}
}
// UpLoad 文件上传
func (e *QiNiuKODO) UpLoad(yourObjectName string, localFile interface{}) error {
// 构建表单上传的对象
formUploader := storage.NewFormUploader(&e.cfg)
ret := storage.PutRet{}
// 可选配置
putExtra := storage.PutExtra{
Params: map[string]string{
"x:name": "github logo",
},
}
err := formUploader.PutFile(context.Background(), &ret, e.getToken(), yourObjectName, localFile.(string), &putExtra)
if err != nil {
fmt.Println(err)
return err
}
fmt.Println(ret.Key, ret.Hash)
return nil
}
func (e *QiNiuKODO) GetTempToken() (string, error) {
token := e.getToken()
return token, nil
}

View File

@ -0,0 +1,23 @@
package file_store
import (
"testing"
)
func TestKODOUpload(t *testing.T) {
e := OXS{"", "", "", ""}
var oxs = e.Setup(QiNiuKodo, map[string]interface{}{"Zone": "华东"})
err := oxs.UpLoad("test.png", "./test.png")
if err != nil {
t.Error(err)
}
t.Log("ok")
}
func TestKODOGetTempToken(t *testing.T) {
e := OXS{"", "", "", ""}
var oxs = e.Setup(QiNiuKodo, map[string]interface{}{"Zone": "华东"})
token, _ := oxs.GetTempToken()
t.Log(token)
t.Log("ok")
}

52
common/file_store/obs.go Normal file
View File

@ -0,0 +1,52 @@
package file_store
import (
"fmt"
"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
"log"
)
type HuaWeiOBS struct {
Client interface{}
BucketName string
}
func (e *HuaWeiOBS) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error {
// 创建ObsClient结构体
client, err := obs.New(accessKeyID, accessKeySecret, endpoint)
if err != nil {
log.Println("Error:", err)
return err
}
e.Client = client
e.BucketName = BucketName
return nil
}
// UpLoad 文件上传
// yourObjectName 文件路径名称与objectKey是同一概念表示断点续传上传文件到OSS时需要指定包含文件后缀在内的完整路径例如abc/efg/123.jpg
func (e *HuaWeiOBS) UpLoad(yourObjectName string, localFile interface{}) error {
// 获取存储空间。
input := &obs.PutFileInput{}
input.Bucket = e.BucketName
input.Key = yourObjectName
input.SourceFile = localFile.(string)
output, err := e.Client.(*obs.ObsClient).PutFile(input)
if err == nil {
fmt.Printf("RequestId:%s\n", output.RequestId)
fmt.Printf("ETag:%s, StorageClass:%s\n", output.ETag, output.StorageClass)
} else {
if obsError, ok := err.(obs.ObsError); ok {
fmt.Println(obsError.Code)
fmt.Println(obsError.Message)
} else {
fmt.Println(err)
}
}
return nil
}
func (e *HuaWeiOBS) GetTempToken() (string, error) {
return "", nil
}

View File

@ -0,0 +1,15 @@
package file_store
import (
"testing"
)
func TestOBSUpload(t *testing.T) {
e := OXS{"", "", "", ""}
var oxs = e.Setup(HuaweiOBS)
err := oxs.UpLoad("test.png", "./test.png")
if err != nil {
t.Error(err)
}
t.Log("ok")
}

48
common/file_store/oss.go Normal file
View File

@ -0,0 +1,48 @@
package file_store
import (
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"log"
)
type ALiYunOSS struct {
Client interface{}
BucketName string
}
//Setup 装载
//endpoint sss
func (e *ALiYunOSS) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error {
client, err := oss.New(endpoint, accessKeyID, accessKeySecret)
if err != nil {
log.Println("Error:", err)
return err
}
e.Client = client
e.BucketName = BucketName
return nil
}
// UpLoad 文件上传
func (e *ALiYunOSS) UpLoad(yourObjectName string, localFile interface{}) error {
// 获取存储空间。
bucket, err := e.Client.(*oss.Client).Bucket(e.BucketName)
if err != nil {
log.Println("Error:", err)
return err
}
// 设置分片大小为100 KB指定分片上传并发数为3并开启断点续传上传。
// 其中<yourObjectName>与objectKey是同一概念表示断点续传上传文件到OSS时需要指定包含文件后缀在内的完整路径例如abc/efg/123.jpg。
// "LocalFile"为filePath100*1024为partSize。
err = bucket.UploadFile(yourObjectName, localFile.(string), 100*1024, oss.Routines(3), oss.Checkpoint(true, ""))
if err != nil {
log.Println("Error:", err)
return err
}
return nil
}
func (e *ALiYunOSS) GetTempToken() (string, error) {
return "", nil
}

View File

@ -0,0 +1,16 @@
package file_store
import (
"testing"
)
func TestOSSUpload(t *testing.T) {
// 打括号内填写自己的测试信息即可
e := OXS{}
var oxs = e.Setup(AliYunOSS)
err := oxs.UpLoad("test.png", "./test.png")
if err != nil {
t.Error(err)
}
t.Log("ok")
}

20
common/global/adm.go Normal file
View File

@ -0,0 +1,20 @@
package global
const (
// Version go-admin version info
Version = "2.1.2"
)
var (
// Driver 数据库驱动
Driver string
)
const (
//钱包 回调配置
SYS_CONFIG_CALLBACK = "CoinGateCallBack"
//钱包 取消配置
SYS_CONFIG_CANCECL = "CoinGateCancel"
////钱包 成功配置
SYS_CONFIG_SUCCESS = "CoinGateSuccess"
)

18
common/global/casbin.go Normal file
View File

@ -0,0 +1,18 @@
package global
import (
"github.com/casbin/casbin/v2"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk"
"github.com/go-admin-team/go-admin-core/sdk/api"
)
func LoadPolicy(c *gin.Context) (*casbin.SyncedEnforcer, error) {
log := api.GetRequestLogger(c)
if err := sdk.Runtime.GetCasbinKey(c.Request.Host).LoadPolicy(); err == nil {
return sdk.Runtime.GetCasbinKey(c.Request.Host), err
} else {
log.Errorf("casbin rbac_model or policy init error, %s ", err.Error())
return nil, err
}
}

25
common/global/coingate.go Normal file
View File

@ -0,0 +1,25 @@
package global
const (
//内部生成默认状态
COINGATE_STATUS_DEFAULT = "default"
//新创建的发票。购物者尚未选择付款货币。
COINGATE_STATUS_NEW = "new"
// 购物者已选择支付货币。正在等待付款
COINGATE_STATUS_PENDING = "pending"
//购物者已转账支付发票款项。正在等待区块链网络确认。
COINGATE_STATUS_CONFIRMING = "confirming"
//付款已由网络确认,并记入商家账户。购买的商品/服务可以安全地交付给购物者。
COINGATE_STATUS_PAID = "paid"
//由于 AML/CTF 合规原因,付款被网络拒绝或被标记为无效
COINGATE_STATUS_INVALID = "invalid"
//购物者未在规定时间内付款默认值20 分钟),因此发票已过期。
COINGATE_STATUS_EXPIRED = "expired"
//购物者取消了发票。
COINGATE_STATUS_CANCELED = "canceled"
//付款已退还给购物者
COINGATE_STATUS_REFUNDED = "refunded"
//部分付款已退还给购物者。
COINGATE_STATUS_PARTIALLY_REFUNDED = "partially_refunded"
)

11
common/global/exchange.go Normal file
View File

@ -0,0 +1,11 @@
package global
//交易所类型字典
const (
EXCHANGE_BINANCE = "binance"
EXCHANGE_OKEX = "okex"
EXCHANGE_GATE = "gate"
EXCHANGE_COINBASE = "coinbase"
EXCHANGE_BITFINEX = "bitfinex"
EXCHANGE_BITMEX = "bitmex"
)

4
common/global/logo.go Normal file
View File

@ -0,0 +1,4 @@
package global
// LogoContent go-admin ascii显示减少静态文件依赖
var LogoContent = []byte{10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 95, 95, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 32, 32, 32, 32, 32, 32, 32, 32, 44, 39, 32, 32, 44, 32, 96, 46, 32, 32, 44, 45, 45, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 46, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 46, 39, 124, 32, 32, 32, 32, 32, 44, 45, 43, 45, 44, 46, 39, 32, 95, 32, 124, 44, 45, 45, 46, 39, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 10, 32, 32, 44, 45, 45, 45, 45, 46, 95, 44, 46, 32, 32, 39, 32, 32, 32, 44, 39, 92, 32, 32, 32, 44, 39, 32, 32, 46, 39, 32, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 32, 124, 32, 58, 32, 32, 44, 45, 43, 45, 46, 32, 59, 32, 32, 32, 44, 32, 124, 124, 124, 32, 32, 124, 44, 32, 32, 32, 32, 32, 32, 44, 45, 43, 45, 46, 32, 47, 32, 32, 124, 10, 32, 47, 32, 32, 32, 47, 32, 32, 39, 32, 47, 32, 47, 32, 32, 32, 47, 32, 32, 32, 124, 44, 45, 45, 45, 46, 39, 32, 32, 32, 44, 32, 44, 45, 45, 46, 45, 45, 46, 32, 32, 32, 32, 32, 32, 124, 32, 32, 32, 124, 32, 124, 32, 44, 45, 45, 46, 39, 124, 39, 32, 32, 32, 124, 32, 32, 124, 124, 96, 45, 45, 39, 95, 32, 32, 32, 32, 32, 44, 45, 45, 46, 39, 124, 39, 32, 32, 32, 124, 10, 124, 32, 32, 32, 58, 32, 32, 32, 32, 32, 124, 46, 32, 32, 32, 59, 32, 44, 46, 32, 58, 124, 32, 32, 32, 124, 32, 32, 32, 32, 124, 47, 32, 32, 32, 32, 32, 32, 32, 92, 32, 32, 32, 44, 45, 45, 46, 95, 95, 124, 32, 124, 124, 32, 32, 32, 124, 32, 32, 44, 39, 44, 32, 124, 32, 32, 124, 44, 44, 39, 32, 44, 39, 124, 32, 32, 32, 124, 32, 32, 32, 124, 32, 32, 44, 34, 39, 32, 124, 10, 124, 32, 32, 32, 124, 32, 46, 92, 32, 32, 46, 39, 32, 32, 32, 124, 32, 124, 58, 32, 58, 58, 32, 32, 32, 58, 32, 32, 46, 39, 46, 45, 45, 46, 32, 32, 46, 45, 46, 32, 124, 32, 47, 32, 32, 32, 44, 39, 32, 32, 32, 124, 124, 32, 32, 32, 124, 32, 47, 32, 32, 124, 32, 124, 45, 45, 39, 32, 39, 32, 32, 124, 32, 124, 32, 32, 32, 124, 32, 32, 32, 124, 32, 47, 32, 32, 124, 32, 124, 10, 46, 32, 32, 32, 59, 32, 39, 59, 32, 32, 124, 39, 32, 32, 32, 124, 32, 46, 59, 32, 58, 58, 32, 32, 32, 124, 46, 39, 32, 32, 32, 92, 95, 95, 92, 47, 58, 32, 46, 32, 46, 46, 32, 32, 32, 39, 32, 32, 47, 32, 32, 124, 124, 32, 32, 32, 58, 32, 124, 32, 32, 124, 32, 44, 32, 32, 32, 32, 124, 32, 32, 124, 32, 58, 32, 32, 32, 124, 32, 32, 32, 124, 32, 124, 32, 32, 124, 32, 124, 10, 39, 32, 32, 32, 46, 32, 32, 32, 46, 32, 124, 124, 32, 32, 32, 58, 32, 32, 32, 32, 124, 96, 45, 45, 45, 39, 32, 32, 32, 32, 32, 44, 34, 32, 46, 45, 45, 46, 59, 32, 124, 39, 32, 32, 32, 59, 32, 124, 58, 32, 32, 124, 124, 32, 32, 32, 58, 32, 124, 32, 32, 124, 47, 32, 32, 32, 32, 32, 39, 32, 32, 58, 32, 124, 95, 95, 32, 124, 32, 32, 32, 124, 32, 124, 32, 32, 124, 47, 10, 32, 96, 45, 45, 45, 96, 45, 39, 124, 32, 124, 32, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 32, 32, 47, 32, 32, 44, 46, 32, 32, 124, 124, 32, 32, 32, 124, 32, 39, 47, 32, 32, 39, 124, 32, 32, 32, 124, 32, 124, 96, 45, 39, 32, 32, 32, 32, 32, 32, 124, 32, 32, 124, 32, 39, 46, 39, 124, 124, 32, 32, 32, 124, 32, 124, 45, 45, 39, 10, 32, 46, 39, 95, 95, 47, 92, 95, 58, 32, 124, 32, 32, 96, 45, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 59, 32, 32, 58, 32, 32, 32, 46, 39, 32, 32, 32, 92, 32, 32, 32, 58, 32, 32, 32, 32, 58, 124, 124, 32, 32, 32, 59, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 59, 32, 32, 58, 32, 32, 32, 32, 59, 124, 32, 32, 32, 124, 47, 10, 32, 124, 32, 32, 32, 58, 32, 32, 32, 32, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 44, 32, 32, 32, 32, 32, 46, 45, 46, 47, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 39, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 44, 32, 32, 32, 47, 32, 39, 45, 45, 45, 39, 10, 32, 32, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 45, 45, 96, 45, 45, 45, 39, 32, 32, 32, 32, 32, 96, 45, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 96, 45, 39, 10, 32, 32, 32, 96, 45, 45, 96, 45, 39, 10}

View File

@ -0,0 +1,69 @@
package global
const (
//现货
SPOT = "1"
//合约
FUT = "2"
//现货-24h行情 {交易所类型code,交易对名称}
TICKER_SPOT = "tc_spot:%s:%s"
//合约-24h行情 {交易所类型code,交易对名称}
TICKER_FUTURES = "tc_fut:%s:%s"
//合约-资金费率
FUNDING_INFO_FUTURES = "fi_fut"
//合约-k线
K_FUT = "k_fut"
//现货-k线
K_SPOT = "k_spot"
//代币-配置
COIN_DETAIL = "c_spot_detail"
//代币-现货24h涨跌幅
COIN_PRICE_CHANGE = "c_spot_priceChange"
//代币-现货热门排序
COIN_HOT_SORT = "c_spot_hot_sort"
//代币-现货新币排序
COIN_NEW_SORT = "c_spot_new_sort"
//代币-现货主流排序
COIN_MAIN_SORT = "c_spot_main_sort"
//代币-合约配置
COIN_FUTURES_DETAIL = "c_fut_detail"
//代币-合约24h涨跌幅
COIN_FUTURES_PRICE_CHANGE = "c_fut_priceChange"
//代币-合约热门排序
COIN_FUTURES_HOT_SORT = "c_fut_hot_sort"
//代币-合约新币排序
COIN_FUTURES_NEW_SORT = "c_fut_new_sort"
//代币-合约主流排序
COIN_FUTURES_MAIN_SORT = "c_fut_main_sort"
//U本位合约-24h涨跌幅
UFUTURES_PRICE_CHANGE = "ufut_priceChange"
//币本位合约-24h涨跌幅
CFUTURES_PRICE_CHANGE = "cfut_priceChange"
//用户订阅信息
USER_SUBSCRIBE = "user_sub:%s" //{apikey}
//币种配置
RECHARGE_COIN = "recharge_coin"
)
const (
//参数管理
SYS_CONFIG = "sys_config"
//字典数据
DICT_DATA_PREFIX = "dic_list"
)
const (
//api websocket 错误信息
API_WEBSOCKET_ERR = "api_ws_err:%s"
)
const (
//交易对-现货
SYMBOL_SPOT = 0
//交易对-合约
SYMBOL_FUTURES = 1
)

60
common/global/state.go Normal file
View File

@ -0,0 +1,60 @@
package global
const (
//币种配置-开启交易
VTS_COIN_TRADE_Y = 1
//币种配置-关闭交易
VTS_COIN_TRADE_N = 2
//币种配置-已上架
VTS_STATUS_Y = 3
//币种配置-未上架
VTS_STATUS_N = 1
//币种配置-显示在首页-是
VTS_SHOW_HOME_Y = 1
//币种配置-显示在首页-否
VTS_SHOW_HOME_N = 2
// 币种配置-现货
VTS_COIN_CATEGORY_SPOT = 0
// 币种配置-合约
VTS_COIN_CATEGORY_FUTURES = 1
)
const (
//api 启用
AD_API_STATUS_Y = 1
//api 禁用
AD_API_STATUS_N = 2
)
const (
//代理-http
PROXY_TYPE_HTTP = "http"
////代理-https
PROXY_TYPE_HTTPS = "https"
////代理-socks5
PROXY_TYPE_SOCKS5 = "socks5"
)
const (
//充值内容 线上钱包
TRAN_TYPE_WALLET = 1
//充值类型 内部
TRAN_TYPE_ADMIN = 2
)
const (
//资金账户状态-正常
OTC_HOLD_STATUS_NOMAL = 3
//资金账户状态-冻结
OTC_HOLD_STATUS_FREEZEN = 1
)
const (
//充值确认状态-未确认
RECHARGE_CONFIRM_STATUS_UNCONFIRMED = "un_confirm"
//充值确认状态-已确认
RECHARGE_CONFIRM_STATUS_CONFIRMED = "confirmed"
)

7
common/global/topic.go Normal file
View File

@ -0,0 +1,7 @@
package global
const (
LoginLog = "login_log_queue"
OperateLog = "operate_log_queue"
ApiCheck = "api_check_queue"
)

View File

@ -0,0 +1,282 @@
package helper
import (
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"time"
"github.com/shopspring/decimal"
)
type BinanceClient struct {
APIKey string
APISecret string
HTTPClient *http.Client
SpotBaseURL string //现货api地址
FutBaseURL string //合约api地址
}
// NewBinanceClient creates a new Binance client
func NewBinanceClient(apiKey, apiSecret string, proxyType, proxyAddr string) (*BinanceClient, error) {
// Create HTTP client with transport settings
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 1000,
IdleConnTimeout: 10 * time.Second, // 设置超时 10 秒
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
err := CreateHtppProxy(proxyType, proxyAddr, client)
if err != nil {
return nil, err
}
return &BinanceClient{
APIKey: apiKey,
APISecret: apiSecret,
HTTPClient: client,
SpotBaseURL: "https://api.binance.com",
FutBaseURL: "https://fapi.binance.com",
}, nil
}
// Helper to get proxy password from URL
func getProxyPassword(proxyURL *url.URL) string {
if password, ok := proxyURL.User.Password(); ok {
return password
}
return ""
}
// signRequest generates HMAC SHA256 signature
func (bc *BinanceClient) signRequest(query string) string {
mac := hmac.New(sha256.New, []byte(bc.APISecret))
mac.Write([]byte(query))
return hex.EncodeToString(mac.Sum(nil))
}
/*
binance 现货请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendSpotRequest(endpoint string, method string, params map[string]string) ([]byte, int, error) {
// Prepare URL
reqURL := bc.SpotBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 0)
}
/*
binance 现货请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendSpotRequestAuth(endpoint string, method string, params map[string]string) ([]byte, int, error) {
// Prepare URL
reqURL := bc.SpotBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 2)
}
/*
binance 现货请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendSpotAuth(endpoint string, method string, params interface{}) ([]byte, int, error) {
// Prepare URL
reqURL := bc.SpotBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 2)
}
/*
binance 现货请求 只需要api key
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendSpotRequestByKey(endpoint string, method string, params interface{}) ([]byte, int, error) {
// Prepare URL
reqURL := bc.SpotBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 1)
}
/*
binance 合约请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendFuturesRequest(endpoint string, method string, params map[string]string) ([]byte, int, error) {
// Prepare URL
reqURL := bc.FutBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 0)
}
/*
binance 合约请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendFuturesRequestAuth(endpoint string, method string, params map[string]string) ([]byte, int, error) {
// Prepare URL
reqURL := bc.FutBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 2)
}
/*
binance 合约请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendFuturesRequestByKey(endpoint string, method string, params map[string]string) ([]byte, int, error) {
// Prepare URL
reqURL := bc.FutBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 1)
}
/*
binance 合约请求
- @endpoint 路由
- @method get、post
- @params 参数map
*/
func (bc *BinanceClient) SendFuturesAuth(endpoint string, method string, params interface{}) ([]byte, int, error) {
// Prepare URL
reqURL := bc.FutBaseURL + endpoint
return bc.SendRequest(reqURL, method, params, 2)
}
// SendRequest sends a request to Binance API
/*
发送请求
- auth 0-不需要 1-只需要key 2-需要key和签名
*/
func (bc *BinanceClient) SendRequest(reqURL string, method string, params interface{}, auth int) ([]byte, int, error) {
method = strings.ToUpper(method)
reqParams := url.Values{}
// 处理 `params`,如果是 map[string]string 则添加为 URL 参数
if paramMap, ok := params.(map[string]string); ok {
for k, v := range paramMap {
reqParams.Add(k, v)
}
} else if v := reflect.ValueOf(params); v.Kind() == reflect.Struct {
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
value := v.Field(i)
// 获取字段名或 JSON 标签名
key := field.Tag.Get("json")
if key == "" {
key = field.Name // 如果没有 json 标签,使用字段名
}
// 检查字段类型并转换为对应的字符串
var strValue string
switch value.Kind() {
case reflect.String:
strValue = value.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
strValue = strconv.FormatInt(value.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
strValue = strconv.FormatUint(value.Uint(), 10)
case reflect.Float32, reflect.Float64:
strValue = strconv.FormatFloat(value.Float(), 'f', -1, 64)
case reflect.Bool:
strValue = strconv.FormatBool(value.Bool())
case reflect.Struct:
// 处理 decimal.Decimal 类型
if value.Type() == reflect.TypeOf(decimal.Decimal{}) {
strValue = value.Interface().(decimal.Decimal).String()
} else {
continue // 跳过其他 struct 类型
}
default:
continue // 跳过不支持的类型
}
// 添加到 reqParams
reqParams.Add(key, strValue)
}
}
// Add timestamp if signature is needed
if auth == 2 && bc.APIKey != "" && bc.APISecret != "" {
reqParams.Add("timestamp", fmt.Sprintf("%d", time.Now().UnixMilli()))
signature := bc.signRequest(reqParams.Encode())
reqParams.Add("signature", signature)
}
// Create HTTP request
var req *http.Request
var err error
if method == http.MethodGet || method == http.MethodDelete {
if len(reqParams) > 0 {
reqURL = fmt.Sprintf("%s?%s", reqURL, reqParams.Encode())
}
req, err = http.NewRequest(method, reqURL, nil)
} else {
// req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
if len(reqParams) > 0 {
reqURL = fmt.Sprintf("%s?%s", reqURL, reqParams.Encode())
}
req, err = http.NewRequest(method, reqURL, nil)
}
if err != nil {
return nil, -1, fmt.Errorf("failed to create request: %w", err)
}
// Set headers
if auth > 0 && bc.APIKey != "" {
req.Header.Set("X-MBX-APIKEY", bc.APIKey)
}
// req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// Send request
resp, err := bc.HTTPClient.Do(req)
if err != nil {
return nil, -1, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
// Read response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, -1, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, resp.StatusCode, fmt.Errorf("%s", body)
}
return body, http.StatusOK, nil
}

View File

@ -0,0 +1,139 @@
package helper
import (
"fmt"
"time"
"github.com/bytedance/sonic"
"github.com/go-redis/redis/v8"
)
// CacheFunction 通用缓存装饰器,支持任意函数类型
func CacheFunctionExpire[T any](fn func(args ...any) (T, error), keyPrefix string, ttl time.Duration) func(args ...any) (T, error) {
return func(args ...any) (T, error) {
// 创建缓存键,基于函数名和参数
cacheKey := fmt.Sprintf("%s:%v", keyPrefix, args)
// 尝试从 Redis 获取缓存
cachedData, err := DefaultRedis.GetString(cacheKey)
var zeroT T // 用于返回零值
if err == redis.Nil {
// 缓存不存在,调用实际函数
result, err := fn(args...)
if err != nil {
return zeroT, err
}
// 将结果序列化并存入 Redis
jsonData, err := sonic.Marshal(result)
if err != nil {
return zeroT, err
}
err = DefaultRedis.SetStringExpire(cacheKey, string(jsonData), ttl)
if err != nil {
return zeroT, err
}
return result, nil
} else if err != nil {
return zeroT, err
}
// 缓存命中,反序列化结果
var result T
err = sonic.Unmarshal([]byte(cachedData), &result)
if err != nil {
return zeroT, err
}
return result, nil
}
}
// CacheFunction 通用缓存装饰器,支持任意函数类型
func CacheFunctionNoArgsExpire[T any](fn func() (T, error), keyPrefix string, ttl time.Duration) func() (T, error) {
return func() (T, error) {
// 创建缓存键,基于函数名和参数
cacheKey := keyPrefix
// 尝试从 Redis 获取缓存
cachedData, err := DefaultRedis.GetString(cacheKey)
var zeroT T // 用于返回零值
if err == redis.Nil {
// 缓存不存在,调用实际函数
result, err := fn()
if err != nil {
return zeroT, err
}
// 将结果序列化并存入 Redis
jsonData, err := sonic.Marshal(result)
if err != nil {
return zeroT, err
}
err = DefaultRedis.SetStringExpire(cacheKey, string(jsonData), ttl)
if err != nil {
return zeroT, err
}
return result, nil
} else if err != nil {
return zeroT, err
}
// 缓存命中,反序列化结果
var result T
err = sonic.Unmarshal([]byte(cachedData), &result)
if err != nil {
return zeroT, err
}
return result, nil
}
}
// DeleteCacheFunction 通用删除缓存装饰器
func DeleteCacheFunction[T any](fn func(args ...any) (T, error), keyPrefix string) func(args ...any) (T, error) {
return func(args ...any) (T, error) {
// 调用实际函数
result, err := fn(args...)
if err != nil {
return result, err
}
// 创建缓存键,基于函数名和参数
cacheKey := fmt.Sprintf("%s:%v", keyPrefix, args)
// 从 Redis 删除缓存
err = DefaultRedis.DeleteString(cacheKey)
if err != nil {
return result, fmt.Errorf("failed to delete cache: %w", err)
}
return result, nil
}
}
// DeleteCacheFunction 通用删除缓存装饰器
func DeleteCacheNoArgsFunction[T any](fn func() (T, error), keyPrefix string) func() (T, error) {
return func() (T, error) {
// 调用实际函数
result, err := fn()
if err != nil {
return result, err
}
// 创建缓存键,基于函数名和参数
cacheKey := keyPrefix
// 从 Redis 删除缓存
err = DefaultRedis.DeleteString(cacheKey)
if err != nil {
return result, fmt.Errorf("failed to delete cache: %w", err)
}
return result, nil
}
}

79
common/helper/proxy.go Normal file
View File

@ -0,0 +1,79 @@
package helper
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"golang.org/x/net/proxy"
)
/*
创建代理
- @proxyType 代理类别 http\https\socks5
- @proxyAddr 代理地址 userName:password@endpoint:port
*/
func CreateHtppProxy(proxyType, proxyAddr string, client *http.Client) error {
// Set up proxy based on type (HTTP, HTTPS, SOCKS5)
transport := &http.Transport{}
if proxyAddr != "" {
if !strings.HasPrefix(proxyAddr, "http://") && !strings.HasPrefix(proxyAddr, "https://") && !strings.HasPrefix(proxyAddr, "socks5://") {
proxyAddr = proxyType + "://" + proxyAddr
}
if proxyType == "" {
proxyType = strings.Split(proxyAddr, "://")[0]
}
switch proxyType {
case "http", "https":
proxyURL, err := url.Parse(proxyAddr)
if err != nil {
return errors.New(fmt.Sprintf("invalid proxy URL: %w", err))
}
// Check if proxy URL contains credentials
if proxyURL.User != nil {
username := proxyURL.User.Username()
password, _ := proxyURL.User.Password()
transport.Proxy = func(req *http.Request) (*url.URL, error) {
req.SetBasicAuth(username, password) // Set basic auth headers
return proxyURL, nil
}
} else {
transport.Proxy = http.ProxyURL(proxyURL)
}
case "socks5":
proxyURL, err := url.Parse(proxyAddr)
if err != nil {
return errors.New(fmt.Sprintf("invalid proxy URL: %w", err))
}
var auth *proxy.Auth
if proxyURL.User != nil {
auth = &proxy.Auth{
User: proxyURL.User.Username(),
Password: getProxyPassword(proxyURL),
}
}
dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct)
if err != nil {
return errors.New(fmt.Sprintf("failed to set up SOCKS5 proxy: %w", err))
}
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
}
default:
return errors.New(fmt.Sprintf("unsupported proxy type: %s", proxyType))
}
client.Transport = transport
}
return nil
}

View File

@ -0,0 +1,695 @@
package helper
import (
"context"
"errors"
"fmt"
"log"
"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
}
// 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.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()
}
/*
获取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) 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
}

183
common/helper/redislock.go Normal file
View File

@ -0,0 +1,183 @@
package helper
import (
"context"
"errors"
"go-admin/pkg/utility"
"math/rand"
"strconv"
"sync"
"sync/atomic"
"time"
log "github.com/go-admin-team/go-admin-core/logger"
"github.com/go-redis/redis/v8"
"go.uber.org/zap"
)
const (
tolerance = 500 // milliseconds
millisPerSecond = 1000
lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end`
delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
touchCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
else
return 0
end`
)
var (
clientRedisLock *redis.Client
onece sync.Once
ErrFailed = errors.New("redsync: failed to acquire lock")
)
// InitLockRedisConn 初始化 Redis 连接
func InitLockRedisConn(host, password, dbIndex string) {
onece.Do(func() {
dbIndexInt, err := strconv.Atoi(dbIndex)
if err != nil {
dbIndexInt = 0
}
clientRedisLock = redis.NewClient(&redis.Options{
Addr: host,
Password: password,
DB: dbIndexInt,
})
// 测试连接
if _, err := clientRedisLock.Ping(context.Background()).Result(); err != nil {
log.Error("Failed to connect to Redis", zap.Error(err))
panic(err)
}
})
}
// RedisLock 分布式锁结构
type RedisLock struct {
redisClient *redis.Client
key string
id string
expiry time.Duration
retryCount int // 重试次数
retryInterval time.Duration // 重试间隔时间
seconds uint32
}
// NewRedisLock 创建一个新的 Redis 锁
func NewRedisLock(key string, timeout uint32, retryCount int, retryInterval time.Duration) *RedisLock {
return &RedisLock{
redisClient: clientRedisLock,
key: key,
id: utility.GetXid(),
expiry: time.Duration(timeout) * time.Second,
retryCount: retryCount,
retryInterval: retryInterval,
seconds: timeout,
}
}
// Acquire 获取锁
func (rl *RedisLock) Acquire() (bool, error) {
return rl.acquireCtx(context.Background())
}
// AcquireWait 获取锁,支持重试和超时
func (rl *RedisLock) AcquireWait(ctx context.Context) (bool, error) {
if ctx == nil {
ctx = context.Background()
}
for i := 0; i < rl.retryCount; i++ {
if i > 0 {
// 指数退避
baseInterval := time.Duration(int64(rl.retryInterval) * (1 << i)) // 指数增长
if baseInterval > time.Second {
baseInterval = time.Second
}
// 随机退避
retryInterval := time.Duration(rand.Int63n(int64(baseInterval))) // 随机退避
if retryInterval < time.Millisecond*100 {
retryInterval = time.Millisecond * 100 // 至少 100ms
}
select {
case <-ctx.Done():
return false, ctx.Err()
case <-time.After(retryInterval):
}
}
ok, err := rl.acquireCtx(ctx)
if ok {
return true, nil
}
if err != nil {
log.Error("Failed to acquire lock", zap.String("key", rl.key), zap.Error(err))
}
}
return false, ErrFailed
}
// Release 释放锁
func (rl *RedisLock) Release() (bool, error) {
return rl.releaseCtx(context.Background())
}
// Touch 续期锁
func (rl *RedisLock) Touch() (bool, error) {
return rl.touchCtx(context.Background())
}
// acquireCtx 获取锁的核心逻辑
func (rl *RedisLock) acquireCtx(ctx context.Context) (bool, error) {
resp, err := rl.redisClient.Eval(ctx, lockCommand, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(rl.seconds)*millisPerSecond + tolerance),
}).Result()
if err == redis.Nil {
return false, nil
} else if err != nil {
log.Error("Error acquiring lock", zap.String("key", rl.key), zap.Error(err))
return false, err
}
reply, ok := resp.(string)
return ok && reply == "OK", nil
}
// releaseCtx 释放锁的核心逻辑
func (rl *RedisLock) releaseCtx(ctx context.Context) (bool, error) {
resp, err := rl.redisClient.Eval(ctx, delCommand, []string{rl.key}, []string{rl.id}).Result()
if err != nil {
log.Error("Error releasing lock", zap.String("key", rl.key), zap.Error(err))
return false, err
}
reply, ok := resp.(int64)
return ok && reply == 1, nil
}
// touchCtx 续期锁的核心逻辑
func (rl *RedisLock) touchCtx(ctx context.Context) (bool, error) {
seconds := atomic.LoadUint32(&rl.seconds)
resp, err := rl.redisClient.Eval(ctx, touchCommand, []string{rl.key}, []string{
rl.id, strconv.Itoa(int(seconds)*millisPerSecond + tolerance),
}).Result()
if err != nil {
log.Error("Error touching lock", zap.String("key", rl.key), zap.Error(err))
return false, err
}
reply, ok := resp.(int64)
return ok && reply == 1, nil
}

View File

@ -0,0 +1,50 @@
package helper
import (
"strconv"
"sync"
extendConfig "go-admin/config"
"github.com/bwmarrin/snowflake"
log "github.com/go-admin-team/go-admin-core/logger"
)
// 全局雪花节点实例
var (
node *snowflake.Node
once sync.Once
)
func InitSnowflakeNode() error {
var err error
node, err = snowflake.NewNode(extendConfig.ExtConfig.ServiceId)
if err != nil {
return err
}
return nil
}
// 获取订单雪花id
func GetOrderNo() string {
if node == nil {
once.Do(func() {
if node == nil {
if err := InitSnowflakeNode(); err != nil {
log.Fatalf("初始化雪花算法节点失败: %v", err)
}
}
})
}
if node == nil {
log.Fatal("雪花算法节点未初始化")
}
orderID := node.Generate()
idStr := strconv.FormatInt(orderID.Int64(), 10)
return idStr
}

27
common/ip.go Normal file
View File

@ -0,0 +1,27 @@
package common
import (
"github.com/gin-gonic/gin"
"strings"
)
func GetClientIP(c *gin.Context) string {
ClientIP := c.ClientIP()
//fmt.Println("ClientIP:", ClientIP)
RemoteIP := c.RemoteIP()
//fmt.Println("RemoteIP:", RemoteIP)
ip := c.Request.Header.Get("X-Forwarded-For")
if strings.Contains(ip, "127.0.0.1") || ip == "" {
ip = c.Request.Header.Get("X-real-ip")
}
if ip == "" {
ip = "127.0.0.1"
}
if RemoteIP != "127.0.0.1" {
ip = RemoteIP
}
if ClientIP != "127.0.0.1" {
ip = ClientIP
}
return ip
}

104
common/middleware/auth.go Normal file
View File

@ -0,0 +1,104 @@
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-admin-team/go-admin-core/sdk/pkg"
"go-admin/common/middleware/dto"
"go-admin/common/service/sysservice/sysstatuscode"
statuscode "go-admin/common/status_code"
"go-admin/pkg/cryptohelper/jwthelper"
"strconv"
"strings"
"time"
"github.com/go-admin-team/go-admin-core/sdk/config"
jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth"
"go-admin/common/middleware/handler"
)
// AuthInit jwt验证new
func AuthInit() (*jwt.GinJWTMiddleware, error) {
timeout := time.Hour
if config.ApplicationConfig.Mode == "dev" {
timeout = time.Duration(876010) * time.Hour
} else {
if config.JwtConfig.Timeout != 0 {
timeout = time.Duration(config.JwtConfig.Timeout) * time.Second
}
}
return jwt.New(&jwt.GinJWTMiddleware{
Realm: "test zone",
Key: []byte(config.JwtConfig.Secret),
Timeout: timeout,
MaxRefresh: time.Hour,
PayloadFunc: handler.PayloadFunc,
IdentityHandler: handler.IdentityHandler,
Authenticator: handler.Authenticator,
Authorizator: handler.Authorizator,
Unauthorized: handler.Unauthorized,
TokenLookup: "header: Authorization, query: token, cookie: jwt",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
})
}
func FrontedAuth(c *gin.Context) {
// 从请求头中获取 token 和 os
token := c.GetHeader("Authorization")
source, _ := strconv.Atoi(c.GetHeader("os"))
// 如果 token 不存在,返回未登录的状态
if len(token) == 0 {
err := ResponseWithStatus(c, dto.NotLoginStatus, statuscode.NotLoggedIn)
if err != nil {
return
}
c.Abort() // 停止后续中间件的执行
return
}
// 验证 token 并获取结果
flag, rew := jwthelper.MidValidToken(token, source)
if flag < 0 || len(rew) == 0 {
if flag == -1 {
ResponseWithStatus(c, dto.NotLoginStatus, statuscode.NotLoggedIn)
} else if flag == -2 {
ResponseWithStatus(c, dto.ReLoginStatus, statuscode.ReLogin)
}
c.Abort() // 停止后续中间件的执行
return
}
// 将解析后的 token 设置到请求头中
c.Request.Header.Set("ParseToken", rew)
// 继续处理请求
c.Next()
}
// ResponseWithStatus 带状态的响应
func ResponseWithStatus(ctx *gin.Context, status int, code int, data ...interface{}) error {
// 获取语言对应的 msg
msg := sysstatuscode.GetStatusCodeDescription(ctx, code)
if msg == `` {
msg = strconv.Itoa(code)
} else {
// 配置了语言包参数
if strings.Contains(msg, "%") && len(data) > 1 {
msg = fmt.Sprintf(msg, data[1:]...)
}
}
resp := dto.Response{
Status: status,
Code: code,
Msg: msg,
}
resp.RequestID = pkg.GenerateMsgIDFromContext(ctx)
if len(data) > 0 {
resp.Data = data[0]
}
ctx.JSON(200, resp)
return nil
}

Some files were not shown because too many files have changed in this diff Show More