@@ -0,0 +1,44 @@ | |||
# Binaries for programs and plugins | |||
*.exe | |||
*.exe~ | |||
*.dll | |||
*.so | |||
*.dylib | |||
# Test binary, built with `go test -c` | |||
*.test | |||
# Output of the go coverage tool, specifically when used with LiteIDE | |||
*.out | |||
.idea | |||
.vscode | |||
.DS_Store | |||
Thumbs.db | |||
*.swp | |||
*.swn | |||
*.swo | |||
*.swm | |||
*.7z | |||
*.zip | |||
*.rar | |||
*.tar | |||
*.tar.gz | |||
go.sum | |||
/etc/cfg.yaml | |||
images | |||
test/test.json | |||
etc/cfg.yml | |||
t.json | |||
t1.json | |||
t2.json | |||
t3.json | |||
t.go | |||
wait-for-it.sh | |||
test.go | |||
xorm | |||
test.csv | |||
nginx.conf | |||
.devcontainer | |||
.devcontainer/Dockerfile | |||
.devcontainer/sources.list | |||
/t1.go | |||
.idea/* | |||
/.idea/modules.xml |
@@ -0,0 +1,70 @@ | |||
package db | |||
import ( | |||
zhios_o2o_business_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/utils/logx" | |||
"database/sql" | |||
_ "github.com/go-sql-driver/mysql" //必须导入mysql驱动,否则会panic | |||
"xorm.io/xorm" | |||
) | |||
var Db *xorm.Engine | |||
/********************************************* 公用方法 *********************************************/ | |||
// 数据批量插入 | |||
func DbInsertBatch(Db *xorm.Engine, m ...interface{}) error { | |||
if len(m) == 0 { | |||
return nil | |||
} | |||
id, err := Db.Insert(m...) | |||
if id == 0 || err != nil { | |||
return zhios_o2o_business_logx.Warn("cannot insert data :", err) | |||
} | |||
return nil | |||
} | |||
// QueryNativeString 查询原生sql | |||
func QueryNativeString(Db *xorm.Engine, sql string, args ...interface{}) ([]map[string]string, error) { | |||
results, err := Db.SQL(sql, args...).QueryString() | |||
return results, err | |||
} | |||
// UpdateComm common update | |||
func UpdateComm(Db *xorm.Engine, id interface{}, model interface{}) (int64, error) { | |||
row, err := Db.ID(id).Update(model) | |||
return row, err | |||
} | |||
// InsertComm common insert | |||
func InsertComm(Db *xorm.Engine, model interface{}) (int64, error) { | |||
row, err := Db.InsertOne(model) | |||
return row, err | |||
} | |||
// GetComm | |||
// payload *model | |||
// return *model,has,err | |||
func GetComm(Db *xorm.Engine, model interface{}) (interface{}, bool, error) { | |||
has, err := Db.Get(model) | |||
if err != nil { | |||
_ = zhios_o2o_business_logx.Warn(err) | |||
return nil, false, err | |||
} | |||
return model, has, nil | |||
} | |||
// InsertCommWithSession common insert | |||
func InsertCommWithSession(session *xorm.Session, model interface{}) (int64, error) { | |||
row, err := session.InsertOne(model) | |||
return row, err | |||
} | |||
// ExecuteOriginalSql 执行原生sql | |||
func ExecuteOriginalSql(session *xorm.Session, sql string) (sql.Result, error) { | |||
result, err := session.Exec(sql) | |||
if err != nil { | |||
_ = zhios_o2o_business_logx.Warn(err) | |||
return nil, err | |||
} | |||
return result, nil | |||
} |
@@ -0,0 +1,96 @@ | |||
package db | |||
import ( | |||
"code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/db/model" | |||
zhios_o2o_business_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/utils/logx" | |||
"errors" | |||
"xorm.io/xorm" | |||
) | |||
func UpdateMerchant(engine *xorm.Engine, merchant *model.O2oMerchant) error { | |||
update, err := engine.ID(merchant.Id).AllCols().Update(merchant) | |||
if err != nil || update == 0 { | |||
if err == nil { | |||
err = errors.New("更新数据异常!") | |||
} | |||
return err | |||
} | |||
return nil | |||
} | |||
func MerchantExistByPhoneAndPassword(Db *xorm.Engine, username string, password string) (bool, error) { | |||
has, err := Db.Where("phone = ? AND password = ?", username, password).Exist(&model.O2oMerchant{}) | |||
if err != nil { | |||
return false, err | |||
} | |||
return has, nil | |||
} | |||
func MerchantFindByMobileOrId(Db *xorm.Engine, mobileOrId string) (*model.O2oMerchant, error) { | |||
var m model.O2oMerchant | |||
if has, err := Db.Where("(phone = ? OR id = ?)", mobileOrId, mobileOrId). | |||
Get(&m); err != nil || has == false { | |||
return nil, zhios_o2o_business_logx.Warn(err) | |||
} | |||
return &m, nil | |||
} | |||
func MerchantFindByUId(Db *xorm.Engine, uid string) (*model.O2oMerchant, error) { | |||
var m model.O2oMerchant | |||
if has, err := Db.Where("uid = ?", uid). | |||
Get(&m); err != nil || has == false { | |||
return nil, zhios_o2o_business_logx.Warn(err) | |||
} | |||
return &m, nil | |||
} | |||
// UserisExistByMobile is exist | |||
func MerchantExistByMobile(Db *xorm.Engine, n string) (bool, error) { | |||
has, err := Db.Where("phone = ?", n).Exist(&model.O2oMerchant{}) | |||
if err != nil { | |||
return false, err | |||
} | |||
return has, nil | |||
} | |||
// MerchantGetByMobileIgnoreDelete search merchant by mobile ignore delete | |||
func MerchantGetByMobileIgnoreDelete(Db *xorm.Engine, mobile string) (*model.O2oMerchant, bool, error) { | |||
m := new(model.O2oMerchant) | |||
has, err := Db.Where("phone = ?", mobile).Get(m) | |||
if err != nil { | |||
return nil, false, zhios_o2o_business_logx.Warn(err) | |||
} | |||
return m, has, nil | |||
} | |||
func MerchantUpdate(Db *xorm.Engine, merchant *model.O2oMerchant, forceCols ...string) (int64, error) { | |||
var ( | |||
affected int64 | |||
err error | |||
) | |||
if forceCols != nil { | |||
affected, err = Db.ID(merchant.Id).Cols(forceCols...).Update(merchant) | |||
} else { | |||
affected, err = Db.ID(merchant.Id).Update(merchant) | |||
} | |||
if err != nil { | |||
return 0, zhios_o2o_business_logx.Warn(err) | |||
} | |||
return affected, nil | |||
} | |||
//MerchantInsert is insert user | |||
func MerchantInsert(Db *xorm.Engine, user *model.O2oMerchant) (int64, error) { | |||
affected, err := Db.Insert(user) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return affected, nil | |||
} | |||
// MerchantDelete is delete user | |||
func MerchantDelete(Db *xorm.Engine, uid interface{}) (int64, error) { | |||
return Db.ID(uid).Delete(model.O2oMerchant{}) | |||
} |
@@ -0,0 +1,16 @@ | |||
package db | |||
import ( | |||
"code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/db/model" | |||
zhios_o2o_business_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/utils/logx" | |||
"xorm.io/xorm" | |||
) | |||
func MerchantFindByStoreManager(Db *xorm.Engine, uid string) (*model.O2oStore, error) { | |||
var m model.O2oStore | |||
if has, err := Db.Where("store_manager = ?", uid). | |||
Get(&m); err != nil || has == false { | |||
return nil, zhios_o2o_business_logx.Warn(err) | |||
} | |||
return &m, nil | |||
} |
@@ -0,0 +1,38 @@ | |||
package db | |||
import ( | |||
"code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/db/model" | |||
zhios_o2o_business_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/utils/logx" | |||
"errors" | |||
"xorm.io/xorm" | |||
) | |||
// GetStoreFansByUid 查找用戶歸屬店鋪粉絲記錄 | |||
func GetStoreFansByUid(Db *xorm.Engine, uid string, storeId string) (*model.O2oStoreFans, error) { | |||
var m model.O2oStoreFans | |||
if has, err := Db.Where("user_id = ?", uid).And("store_id = ?", storeId).Get(&m); err != nil || !has { | |||
return nil, zhios_o2o_business_logx.Warn(err) | |||
} | |||
return &m, nil | |||
} | |||
func UpdateStoreFans(engine *xorm.Engine, data *model.O2oStoreFans) error { | |||
update, err := engine.ID(data.Id).AllCols().Update(data) | |||
if err != nil || update == 0 { | |||
if err == nil { | |||
err = errors.New("更新数据异常!") | |||
} | |||
return err | |||
} | |||
return nil | |||
} | |||
//StoreFansInsertOne is 插入一条流水记录 | |||
func StoreFansInsertOne(Db *xorm.Engine, m *model.O2oStoreFans) error { | |||
_, err := Db.InsertOne(m) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,23 @@ | |||
package model | |||
import ( | |||
"time" | |||
) | |||
type O2oMerchant struct { | |||
Id int `json:"id" xorm:"not null pk autoincr INT(11)"` | |||
Pid int `json:"pid" xorm:"comment('主账号id') INT(11)"` | |||
AvatarUrl string `json:"avatar_url" xorm:"comment('用户头像') VARCHAR(255)"` | |||
Type string `json:"type" xorm:"not null comment('商家类型,可多个') VARCHAR(255)"` | |||
Phone string `json:"phone" xorm:"not null comment('商家手机号') VARCHAR(255)"` | |||
Wechat string `json:"wechat" xorm:"comment('微信号') VARCHAR(255)"` | |||
Password string `json:"password" xorm:"not null comment('密码') VARCHAR(255)"` | |||
Name string `json:"name" xorm:"not null comment('名称') VARCHAR(255)"` | |||
FunctionalAuthority string `json:"functional_authority" xorm:"not null comment('功能权限') VARCHAR(255)"` | |||
State int `json:"state" xorm:"default 1 comment('商家状态') TINYINT(1)"` | |||
Token string `json:"token" xorm:"comment('token') VARCHAR(520)"` | |||
Amount float64 `json:"amount" xorm:"comment('商家余额') DOUBLE(10,2)"` | |||
CreateTime time.Time `json:"create_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` | |||
UpdateTime time.Time `json:"update_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` | |||
DeletedTime time.Time `json:"deleted_time" xorm:"comment('删除时间') DATETIME"` | |||
} |
@@ -0,0 +1,54 @@ | |||
package model | |||
import ( | |||
"time" | |||
) | |||
type O2oStore struct { | |||
Id int `json:"id" xorm:"not null pk autoincr INT(11)"` | |||
Pid int `json:"pid" xorm:"comment('用于以后连锁店使用') INT(11)"` | |||
StoreManager int `json:"store_manager" xorm:"comment('店长(商家id)') INT(11)"` | |||
ManagerRealname string `json:"manager_realname" xorm:"comment('店长真实名字') VARCHAR(255)"` | |||
ManagerPhone string `json:"manager_phone" xorm:"not null comment('店长手机号') VARCHAR(255)"` | |||
Business float32 `json:"business" xorm:"comment('营业额') FLOAT(8,2)"` | |||
Name string `json:"name" xorm:"comment('店铺名字') VARCHAR(255)"` | |||
Type int `json:"type" xorm:"not null comment('店铺类型,类目id') INT(11)"` | |||
Sales string `json:"sales" xorm:"not null default '0' comment('总销量') VARCHAR(255)"` | |||
ShopAvatarUrl string `json:"shop_avatar_url" xorm:"comment('店铺头像') VARCHAR(255)"` | |||
ShopBgimgUrl string `json:"shop_bgImg_url" xorm:"comment('店铺背景图') VARCHAR(255)"` | |||
State int `json:"state" xorm:"not null default 1 comment('店铺状态:1未认证,2营业中,3,休息中,4关闭,5审核中,6认证失败') INT(255)"` | |||
Label string `json:"label" xorm:"comment('店铺标签') VARCHAR(255)"` | |||
Remake string `json:"remake" xorm:"comment('店铺备注') VARCHAR(255)"` | |||
PayChannel string `json:"pay_channel" xorm:"comment('支持的付款方式,可多种') VARCHAR(255)"` | |||
StoreAddress string `json:"store_address" xorm:"comment('店铺地址') VARCHAR(255)"` | |||
StoreAddressDetails string `json:"store_address_details" xorm:"comment('店铺详情地址') VARCHAR(255)"` | |||
AuthInformation string `json:"auth_information" xorm:"comment('认证信息json') LONGTEXT"` | |||
IsHoliday int `json:"is_holiday" xorm:"comment('是否假日营业') TINYINT(1)"` | |||
BusinessDay string `json:"business_day" xorm:"not null comment('(1-6)表示周一到周六') VARCHAR(255)"` | |||
BusinessDayTitle string `json:"business_day_title" xorm:"not null comment('周一到周六') VARCHAR(255)"` | |||
BusinessStartTime time.Time `json:"business_start_time" xorm:"comment('营业开始时间') DATETIME"` | |||
BusinessEndTime time.Time `json:"business_end_time" xorm:"comment('营业结束时间') DATETIME"` | |||
IsEatIn int `json:"is_eat_in" xorm:"default 0 comment('是否支持堂食') TINYINT(1)"` | |||
IsDelivery int `json:"is_delivery" xorm:"default 0 comment('开启配送') TINYINT(1)"` | |||
IsAutoOrder int `json:"is_auto_order" xorm:"default 0 comment('是否自动接单') TINYINT(1)"` | |||
QrCodeImgUrl string `json:"qr_code_img_url" xorm:"comment('聚合二维码图片地址') VARCHAR(255)"` | |||
TransferRate string `json:"transfer_rate" xorm:"default '0' comment('转账费率') VARCHAR(255)"` | |||
Longitude string `json:"longitude" xorm:"not null default '' comment('经度') VARCHAR(255)"` | |||
Latitude string `json:"latitude" xorm:"not null default '' comment('纬度') VARCHAR(255)"` | |||
AccountAlipay string `json:"account_alipay" xorm:"default '' comment('支付宝账户号') VARCHAR(50)"` | |||
AccountAlipayRealName string `json:"account_alipay_real_name" xorm:"default '' comment('支付宝持有人真实姓名') VARCHAR(255)"` | |||
JoinKey string `json:"join_key" xorm:"not null default '' comment('加盟口令') VARCHAR(255)"` | |||
StoreDiscount string `json:"store_discount" xorm:"not null default '9.8' comment('店铺折扣') VARCHAR(255)"` | |||
ValidTime time.Time `json:"valid_time" xorm:"comment('有效时间') DATETIME"` | |||
CreateTime time.Time `json:"create_time" xorm:"created not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` | |||
UpdateTime time.Time `json:"update_time" xorm:"updated not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` | |||
DeletedTime time.Time `json:"deleted_time" xorm:"comment('删除时间') DATETIME"` | |||
IsMealFee int `json:"is_meal_fee" xorm:"default 0 comment('是否开启餐位费(0:关闭1:开启)') TINYINT(3)"` | |||
MealFeeData string `json:"meal_fee_data" xorm:"comment('餐位费设置信息()') VARCHAR(255)"` | |||
FunctionData string `json:"function_data" xorm:"not null default '' comment('功能开关 json') VARCHAR(255)"` | |||
PayJumpType int `json:"pay_jump_type" xorm:"not null default 1 comment('支付跳转:1:小程序, 2:APP 默认:小程序') TINYINT(1)"` | |||
PayDiscount string `json:"pay_discount" xorm:"comment('收款码折扣') VARCHAR(255)"` | |||
PlaceOrdSet int `json:"place_ord_set" xorm:"not null default 1 comment('客户端商家下单按钮控制(0关1开)') TINYINT(1)"` | |||
StoreMainCategory int `json:"store_main_category" xorm:"comment('店铺主营类目(读店铺分类的is_main 1和2的)') INT(11)"` | |||
PayToMerchantType int `json:"pay_to_merchant_type" xorm:"not null default 0 comment('聚合二维码收款方式(付款给商家使用独立小程序/公域小程序) 0:公域 1:私域') TINYINT(1)"` | |||
} |
@@ -0,0 +1,17 @@ | |||
package model | |||
import ( | |||
"time" | |||
) | |||
type O2oStoreFans struct { | |||
Id int `json:"id" xorm:"not null pk autoincr INT(11)"` | |||
UserId int `json:"user_id" xorm:"not null comment('用户id') INT(11)"` | |||
StoreId int `json:"store_id" xorm:"not null comment('店铺id') INT(11)"` | |||
Remake string `json:"remake" xorm:"comment('备注') VARCHAR(255)"` | |||
OrderNum int `json:"order_num" xorm:"not null default 0 comment('下单次数') INT(20)"` | |||
CreateTime time.Time `json:"create_time" xorm:"created not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` | |||
UpdateTime time.Time `json:"update_time" xorm:"updated not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` | |||
Integral string `json:"integral" xorm:"default 0.0000 comment('店铺积分') DECIMAL(14,4)"` | |||
Type int `json:"type" xorm:"not null default 0 comment('') INT(11)"` | |||
} |
@@ -0,0 +1,18 @@ | |||
module code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git | |||
go 1.15 | |||
require ( | |||
github.com/gin-gonic/gin v1.8.0 | |||
github.com/go-redis/redis v6.15.9+incompatible | |||
github.com/go-sql-driver/mysql v1.6.0 | |||
github.com/gomodule/redigo v1.8.8 | |||
github.com/iGoogle-ink/gopay v1.5.36 | |||
github.com/iGoogle-ink/gotil v1.0.20 | |||
github.com/pkg/errors v0.9.1 | |||
github.com/syyongx/php2go v0.9.6 | |||
go.uber.org/zap v1.16.0 | |||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 | |||
xorm.io/builder v0.3.10 // indirect | |||
xorm.io/xorm v1.3.0 | |||
) |
@@ -0,0 +1,19 @@ | |||
package md | |||
// 缓存key统一管理, %s格式化为masterId | |||
const ( | |||
AppCfgCacheKey = "%s:cfg_cache:%s" // 占位符: masterId, key的第一个字母 | |||
VirtualCoinCfgCacheKey = "%s:virtual_coin_cfg" | |||
PlanRewardCfgCacheKey = "%s:plan_reward_cfg" | |||
UnionSetCacheCfg = "%s:union_set_cfg:%s" // 联盟设置缓存key | |||
UserFinValidUpdateLock = "%s:user_fin_valid_update_lock:%s" // 用户余额更新锁(能拿到锁才能更新余额) | |||
WithdrawApplyQueueListKey = "withdraw_apply_queue" // 提现队列 | |||
TplBottomNavRedisKey = "%s:tpl_nav_bottom_key:%s" // master_id platform | |||
SysModByIdRedisKey = "%s:sys_mod_tpl_by_id:%s" | |||
CfgCacheTime = 86400 | |||
) |
@@ -0,0 +1,52 @@ | |||
package o2o | |||
import ( | |||
"code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/db" | |||
"code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/db/model" | |||
zhios_o2o_business_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/utils" | |||
zhios_o2o_business_logx "code.fnuoos.com/go_rely_warehouse/zyos_go_o2o_business.git/utils/logx" | |||
"time" | |||
"xorm.io/xorm" | |||
) | |||
//通过上级查询是否商家,绑定关系 | |||
func GetParentUidToBindFans(eg *xorm.Engine, uid, parentUid string) { | |||
merchantList, err := db.MerchantFindByUId(eg, parentUid) | |||
if err != nil || merchantList == nil { | |||
return | |||
} | |||
manager, err := db.MerchantFindByStoreManager(eg, zhios_o2o_business_utils.IntToStr(merchantList.Id)) | |||
if err != nil || manager == nil { | |||
return | |||
} | |||
DealCommonPayStoreFans(eg, uid, zhios_o2o_business_utils.IntToStr(manager.Id), "0", "拉新", "1") | |||
} | |||
//DealCommonPayStoreFans 處理通用支付中的店鋪粉絲 | |||
/*** | |||
uid 用户id | |||
storeId o2o_store的 id | |||
isUpdate 0只新增 1可修改 | |||
remake 备注 | |||
types 0消费者 1拉新粉丝 | |||
*/ | |||
func DealCommonPayStoreFans(eg *xorm.Engine, uid string, storeId, isUpdate, remake, types string) { | |||
storeFans, err := db.GetStoreFansByUid(eg, uid, storeId) | |||
if storeFans == nil { | |||
var insertData = model.O2oStoreFans{ | |||
UserId: zhios_o2o_business_utils.StrToInt(uid), | |||
StoreId: zhios_o2o_business_utils.StrToInt(storeId), | |||
Remake: remake, | |||
CreateTime: time.Now(), | |||
UpdateTime: time.Now(), | |||
Type: zhios_o2o_business_utils.StrToInt(types), | |||
} | |||
err = db.StoreFansInsertOne(eg, &insertData) | |||
} else if isUpdate == "1" { | |||
storeFans.OrderNum++ | |||
err = db.UpdateStoreFans(eg, storeFans) | |||
} | |||
if err != nil { | |||
zhios_o2o_business_logx.Warn(err, "處理通用支付中的店鋪粉絲-失敗!") | |||
} | |||
} |
@@ -0,0 +1,421 @@ | |||
package cache | |||
import ( | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"time" | |||
) | |||
const ( | |||
redisDialTTL = 10 * time.Second | |||
redisReadTTL = 3 * time.Second | |||
redisWriteTTL = 3 * time.Second | |||
redisIdleTTL = 10 * time.Second | |||
redisPoolTTL = 10 * time.Second | |||
redisPoolSize int = 512 | |||
redisMaxIdleConn int = 64 | |||
redisMaxActive int = 512 | |||
) | |||
var ( | |||
ErrNil = errors.New("nil return") | |||
ErrWrongArgsNum = errors.New("args num error") | |||
ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64") | |||
) | |||
// 以下为提供类型转换 | |||
func Int(reply interface{}, err error) (int, error) { | |||
if err != nil { | |||
return 0, err | |||
} | |||
switch reply := reply.(type) { | |||
case int: | |||
return reply, nil | |||
case int8: | |||
return int(reply), nil | |||
case int16: | |||
return int(reply), nil | |||
case int32: | |||
return int(reply), nil | |||
case int64: | |||
x := int(reply) | |||
if int64(x) != reply { | |||
return 0, strconv.ErrRange | |||
} | |||
return x, nil | |||
case uint: | |||
n := int(reply) | |||
if n < 0 { | |||
return 0, strconv.ErrRange | |||
} | |||
return n, nil | |||
case uint8: | |||
return int(reply), nil | |||
case uint16: | |||
return int(reply), nil | |||
case uint32: | |||
n := int(reply) | |||
if n < 0 { | |||
return 0, strconv.ErrRange | |||
} | |||
return n, nil | |||
case uint64: | |||
n := int(reply) | |||
if n < 0 { | |||
return 0, strconv.ErrRange | |||
} | |||
return n, nil | |||
case []byte: | |||
data := string(reply) | |||
if len(data) == 0 { | |||
return 0, ErrNil | |||
} | |||
n, err := strconv.ParseInt(data, 10, 0) | |||
return int(n), err | |||
case string: | |||
if len(reply) == 0 { | |||
return 0, ErrNil | |||
} | |||
n, err := strconv.ParseInt(reply, 10, 0) | |||
return int(n), err | |||
case nil: | |||
return 0, ErrNil | |||
case error: | |||
return 0, reply | |||
} | |||
return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply) | |||
} | |||
func Int64(reply interface{}, err error) (int64, error) { | |||
if err != nil { | |||
return 0, err | |||
} | |||
switch reply := reply.(type) { | |||
case int: | |||
return int64(reply), nil | |||
case int8: | |||
return int64(reply), nil | |||
case int16: | |||
return int64(reply), nil | |||
case int32: | |||
return int64(reply), nil | |||
case int64: | |||
return reply, nil | |||
case uint: | |||
n := int64(reply) | |||
if n < 0 { | |||
return 0, strconv.ErrRange | |||
} | |||
return n, nil | |||
case uint8: | |||
return int64(reply), nil | |||
case uint16: | |||
return int64(reply), nil | |||
case uint32: | |||
return int64(reply), nil | |||
case uint64: | |||
n := int64(reply) | |||
if n < 0 { | |||
return 0, strconv.ErrRange | |||
} | |||
return n, nil | |||
case []byte: | |||
data := string(reply) | |||
if len(data) == 0 { | |||
return 0, ErrNil | |||
} | |||
n, err := strconv.ParseInt(data, 10, 64) | |||
return n, err | |||
case string: | |||
if len(reply) == 0 { | |||
return 0, ErrNil | |||
} | |||
n, err := strconv.ParseInt(reply, 10, 64) | |||
return n, err | |||
case nil: | |||
return 0, ErrNil | |||
case error: | |||
return 0, reply | |||
} | |||
return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply) | |||
} | |||
func Uint64(reply interface{}, err error) (uint64, error) { | |||
if err != nil { | |||
return 0, err | |||
} | |||
switch reply := reply.(type) { | |||
case uint: | |||
return uint64(reply), nil | |||
case uint8: | |||
return uint64(reply), nil | |||
case uint16: | |||
return uint64(reply), nil | |||
case uint32: | |||
return uint64(reply), nil | |||
case uint64: | |||
return reply, nil | |||
case int: | |||
if reply < 0 { | |||
return 0, ErrNegativeInt | |||
} | |||
return uint64(reply), nil | |||
case int8: | |||
if reply < 0 { | |||
return 0, ErrNegativeInt | |||
} | |||
return uint64(reply), nil | |||
case int16: | |||
if reply < 0 { | |||
return 0, ErrNegativeInt | |||
} | |||
return uint64(reply), nil | |||
case int32: | |||
if reply < 0 { | |||
return 0, ErrNegativeInt | |||
} | |||
return uint64(reply), nil | |||
case int64: | |||
if reply < 0 { | |||
return 0, ErrNegativeInt | |||
} | |||
return uint64(reply), nil | |||
case []byte: | |||
data := string(reply) | |||
if len(data) == 0 { | |||
return 0, ErrNil | |||
} | |||
n, err := strconv.ParseUint(data, 10, 64) | |||
return n, err | |||
case string: | |||
if len(reply) == 0 { | |||
return 0, ErrNil | |||
} | |||
n, err := strconv.ParseUint(reply, 10, 64) | |||
return n, err | |||
case nil: | |||
return 0, ErrNil | |||
case error: | |||
return 0, reply | |||
} | |||
return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply) | |||
} | |||
func Float64(reply interface{}, err error) (float64, error) { | |||
if err != nil { | |||
return 0, err | |||
} | |||
var value float64 | |||
err = nil | |||
switch v := reply.(type) { | |||
case float32: | |||
value = float64(v) | |||
case float64: | |||
value = v | |||
case int: | |||
value = float64(v) | |||
case int8: | |||
value = float64(v) | |||
case int16: | |||
value = float64(v) | |||
case int32: | |||
value = float64(v) | |||
case int64: | |||
value = float64(v) | |||
case uint: | |||
value = float64(v) | |||
case uint8: | |||
value = float64(v) | |||
case uint16: | |||
value = float64(v) | |||
case uint32: | |||
value = float64(v) | |||
case uint64: | |||
value = float64(v) | |||
case []byte: | |||
data := string(v) | |||
if len(data) == 0 { | |||
return 0, ErrNil | |||
} | |||
value, err = strconv.ParseFloat(string(v), 64) | |||
case string: | |||
if len(v) == 0 { | |||
return 0, ErrNil | |||
} | |||
value, err = strconv.ParseFloat(v, 64) | |||
case nil: | |||
err = ErrNil | |||
case error: | |||
err = v | |||
default: | |||
err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v) | |||
} | |||
return value, err | |||
} | |||
func Bool(reply interface{}, err error) (bool, error) { | |||
if err != nil { | |||
return false, err | |||
} | |||
switch reply := reply.(type) { | |||
case bool: | |||
return reply, nil | |||
case int64: | |||
return reply != 0, nil | |||
case []byte: | |||
data := string(reply) | |||
if len(data) == 0 { | |||
return false, ErrNil | |||
} | |||
return strconv.ParseBool(data) | |||
case string: | |||
if len(reply) == 0 { | |||
return false, ErrNil | |||
} | |||
return strconv.ParseBool(reply) | |||
case nil: | |||
return false, ErrNil | |||
case error: | |||
return false, reply | |||
} | |||
return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply) | |||
} | |||
func Bytes(reply interface{}, err error) ([]byte, error) { | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch reply := reply.(type) { | |||
case []byte: | |||
if len(reply) == 0 { | |||
return nil, ErrNil | |||
} | |||
return reply, nil | |||
case string: | |||
data := []byte(reply) | |||
if len(data) == 0 { | |||
return nil, ErrNil | |||
} | |||
return data, nil | |||
case nil: | |||
return nil, ErrNil | |||
case error: | |||
return nil, reply | |||
} | |||
return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply) | |||
} | |||
func String(reply interface{}, err error) (string, error) { | |||
if err != nil { | |||
return "", err | |||
} | |||
value := "" | |||
err = nil | |||
switch v := reply.(type) { | |||
case string: | |||
if len(v) == 0 { | |||
return "", ErrNil | |||
} | |||
value = v | |||
case []byte: | |||
if len(v) == 0 { | |||
return "", ErrNil | |||
} | |||
value = string(v) | |||
case int: | |||
value = strconv.FormatInt(int64(v), 10) | |||
case int8: | |||
value = strconv.FormatInt(int64(v), 10) | |||
case int16: | |||
value = strconv.FormatInt(int64(v), 10) | |||
case int32: | |||
value = strconv.FormatInt(int64(v), 10) | |||
case int64: | |||
value = strconv.FormatInt(v, 10) | |||
case uint: | |||
value = strconv.FormatUint(uint64(v), 10) | |||
case uint8: | |||
value = strconv.FormatUint(uint64(v), 10) | |||
case uint16: | |||
value = strconv.FormatUint(uint64(v), 10) | |||
case uint32: | |||
value = strconv.FormatUint(uint64(v), 10) | |||
case uint64: | |||
value = strconv.FormatUint(v, 10) | |||
case float32: | |||
value = strconv.FormatFloat(float64(v), 'f', -1, 32) | |||
case float64: | |||
value = strconv.FormatFloat(v, 'f', -1, 64) | |||
case bool: | |||
value = strconv.FormatBool(v) | |||
case nil: | |||
err = ErrNil | |||
case error: | |||
err = v | |||
default: | |||
err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v) | |||
} | |||
return value, err | |||
} | |||
func Strings(reply interface{}, err error) ([]string, error) { | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch reply := reply.(type) { | |||
case []interface{}: | |||
result := make([]string, len(reply)) | |||
for i := range reply { | |||
if reply[i] == nil { | |||
continue | |||
} | |||
switch subReply := reply[i].(type) { | |||
case string: | |||
result[i] = subReply | |||
case []byte: | |||
result[i] = string(subReply) | |||
default: | |||
return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i]) | |||
} | |||
} | |||
return result, nil | |||
case []string: | |||
return reply, nil | |||
case nil: | |||
return nil, ErrNil | |||
case error: | |||
return nil, reply | |||
} | |||
return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply) | |||
} | |||
func Values(reply interface{}, err error) ([]interface{}, error) { | |||
if err != nil { | |||
return nil, err | |||
} | |||
switch reply := reply.(type) { | |||
case []interface{}: | |||
return reply, nil | |||
case nil: | |||
return nil, ErrNil | |||
case error: | |||
return nil, reply | |||
} | |||
return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply) | |||
} |
@@ -0,0 +1,403 @@ | |||
package cache | |||
import ( | |||
"encoding/json" | |||
"errors" | |||
"log" | |||
"strings" | |||
"time" | |||
redigo "github.com/gomodule/redigo/redis" | |||
) | |||
// configuration | |||
type Config struct { | |||
Server string | |||
Password string | |||
MaxIdle int // Maximum number of idle connections in the pool. | |||
// Maximum number of connections allocated by the pool at a given time. | |||
// When zero, there is no limit on the number of connections in the pool. | |||
MaxActive int | |||
// Close connections after remaining idle for this duration. If the value | |||
// is zero, then idle connections are not closed. Applications should set | |||
// the timeout to a value less than the server's timeout. | |||
IdleTimeout time.Duration | |||
// If Wait is true and the pool is at the MaxActive limit, then Get() waits | |||
// for a connection to be returned to the pool before returning. | |||
Wait bool | |||
KeyPrefix string // prefix to all keys; example is "dev environment name" | |||
KeyDelimiter string // delimiter to be used while appending keys; example is ":" | |||
KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?" | |||
} | |||
var pool *redigo.Pool | |||
var conf *Config | |||
func NewRedis(addr string) { | |||
if addr == "" { | |||
panic("\nredis connect string cannot be empty\n") | |||
} | |||
pool = &redigo.Pool{ | |||
MaxIdle: redisMaxIdleConn, | |||
IdleTimeout: redisIdleTTL, | |||
MaxActive: redisMaxActive, | |||
// MaxConnLifetime: redisDialTTL, | |||
Wait: true, | |||
Dial: func() (redigo.Conn, error) { | |||
c, err := redigo.Dial("tcp", addr, | |||
redigo.DialConnectTimeout(redisDialTTL), | |||
redigo.DialReadTimeout(redisReadTTL), | |||
redigo.DialWriteTimeout(redisWriteTTL), | |||
) | |||
if err != nil { | |||
log.Println("Redis Dial failed: ", err) | |||
return nil, err | |||
} | |||
return c, err | |||
}, | |||
TestOnBorrow: func(c redigo.Conn, t time.Time) error { | |||
_, err := c.Do("PING") | |||
if err != nil { | |||
log.Println("Unable to ping to redis server:", err) | |||
} | |||
return err | |||
}, | |||
} | |||
conn := pool.Get() | |||
defer conn.Close() | |||
if conn.Err() != nil { | |||
println("\nredis connect " + addr + " error: " + conn.Err().Error()) | |||
} else { | |||
println("\nredis connect " + addr + " success!\n") | |||
} | |||
} | |||
func Do(cmd string, args ...interface{}) (reply interface{}, err error) { | |||
conn := pool.Get() | |||
defer conn.Close() | |||
return conn.Do(cmd, args...) | |||
} | |||
func GetPool() *redigo.Pool { | |||
return pool | |||
} | |||
func ParseKey(key string, vars []string) (string, error) { | |||
arr := strings.Split(key, conf.KeyPlaceholder) | |||
actualKey := "" | |||
if len(arr) != len(vars)+1 { | |||
return "", errors.New("redis/connection.go: Insufficient arguments to parse key") | |||
} else { | |||
for index, val := range arr { | |||
if index == 0 { | |||
actualKey = arr[index] | |||
} else { | |||
actualKey += vars[index-1] + val | |||
} | |||
} | |||
} | |||
return getPrefixedKey(actualKey), nil | |||
} | |||
func getPrefixedKey(key string) string { | |||
return conf.KeyPrefix + conf.KeyDelimiter + key | |||
} | |||
func StripEnvKey(key string) string { | |||
return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) | |||
} | |||
func SplitKey(key string) []string { | |||
return strings.Split(key, conf.KeyDelimiter) | |||
} | |||
func Expire(key string, ttl int) (interface{}, error) { | |||
return Do("EXPIRE", key, ttl) | |||
} | |||
func Persist(key string) (interface{}, error) { | |||
return Do("PERSIST", key) | |||
} | |||
func Del(key string) (interface{}, error) { | |||
return Do("DEL", key) | |||
} | |||
func Set(key string, data interface{}) (interface{}, error) { | |||
// set | |||
return Do("SET", key, data) | |||
} | |||
func SetNX(key string, data interface{}) (interface{}, error) { | |||
return Do("SETNX", key, data) | |||
} | |||
func SetEx(key string, data interface{}, ttl int) (interface{}, error) { | |||
return Do("SETEX", key, ttl, data) | |||
} | |||
func SetJson(key string, data interface{}, ttl int) bool { | |||
c, err := json.Marshal(data) | |||
if err != nil { | |||
return false | |||
} | |||
if ttl < 1 { | |||
_, err = Set(key, c) | |||
} else { | |||
_, err = SetEx(key, c, ttl) | |||
} | |||
if err != nil { | |||
return false | |||
} | |||
return true | |||
} | |||
func GetJson(key string, dst interface{}) error { | |||
b, err := GetBytes(key) | |||
if err != nil { | |||
return err | |||
} | |||
if err = json.Unmarshal(b, dst); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func Get(key string) (interface{}, error) { | |||
// get | |||
return Do("GET", key) | |||
} | |||
func GetTTL(key string) (time.Duration, error) { | |||
ttl, err := redigo.Int64(Do("TTL", key)) | |||
return time.Duration(ttl) * time.Second, err | |||
} | |||
func GetBytes(key string) ([]byte, error) { | |||
return redigo.Bytes(Do("GET", key)) | |||
} | |||
func GetString(key string) (string, error) { | |||
return redigo.String(Do("GET", key)) | |||
} | |||
func GetStringMap(key string) (map[string]string, error) { | |||
return redigo.StringMap(Do("GET", key)) | |||
} | |||
func GetInt(key string) (int, error) { | |||
return redigo.Int(Do("GET", key)) | |||
} | |||
func GetInt64(key string) (int64, error) { | |||
return redigo.Int64(Do("GET", key)) | |||
} | |||
func GetStringLength(key string) (int, error) { | |||
return redigo.Int(Do("STRLEN", key)) | |||
} | |||
func ZAdd(key string, score float64, data interface{}) (interface{}, error) { | |||
return Do("ZADD", key, score, data) | |||
} | |||
func ZAddNX(key string, score float64, data interface{}) (interface{}, error) { | |||
return Do("ZADD", key, "NX", score, data) | |||
} | |||
func ZRem(key string, data interface{}) (interface{}, error) { | |||
return Do("ZREM", key, data) | |||
} | |||
func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { | |||
if withScores { | |||
return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) | |||
} | |||
return redigo.Values(Do("ZRANGE", key, start, end)) | |||
} | |||
func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) { | |||
return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end)) | |||
} | |||
func ZCard(setName string) (int64, error) { | |||
return redigo.Int64(Do("ZCARD", setName)) | |||
} | |||
func ZScan(setName string) (int64, error) { | |||
return redigo.Int64(Do("ZCARD", setName)) | |||
} | |||
func SAdd(setName string, data interface{}) (interface{}, error) { | |||
return Do("SADD", setName, data) | |||
} | |||
func SCard(setName string) (int64, error) { | |||
return redigo.Int64(Do("SCARD", setName)) | |||
} | |||
func SIsMember(setName string, data interface{}) (bool, error) { | |||
return redigo.Bool(Do("SISMEMBER", setName, data)) | |||
} | |||
func SMembers(setName string) ([]string, error) { | |||
return redigo.Strings(Do("SMEMBERS", setName)) | |||
} | |||
func SRem(setName string, data interface{}) (interface{}, error) { | |||
return Do("SREM", setName, data) | |||
} | |||
func HSet(key string, HKey string, data interface{}) (interface{}, error) { | |||
return Do("HSET", key, HKey, data) | |||
} | |||
func HGet(key string, HKey string) (interface{}, error) { | |||
return Do("HGET", key, HKey) | |||
} | |||
func HMGet(key string, hashKeys ...string) ([]interface{}, error) { | |||
ret, err := Do("HMGET", key, hashKeys) | |||
if err != nil { | |||
return nil, err | |||
} | |||
reta, ok := ret.([]interface{}) | |||
if !ok { | |||
return nil, errors.New("result not an array") | |||
} | |||
return reta, nil | |||
} | |||
func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { | |||
if len(hashKeys) == 0 || len(hashKeys) != len(vals) { | |||
var ret interface{} | |||
return ret, errors.New("bad length") | |||
} | |||
input := []interface{}{key} | |||
for i, v := range hashKeys { | |||
input = append(input, v, vals[i]) | |||
} | |||
return Do("HMSET", input...) | |||
} | |||
func HGetString(key string, HKey string) (string, error) { | |||
return redigo.String(Do("HGET", key, HKey)) | |||
} | |||
func HGetFloat(key string, HKey string) (float64, error) { | |||
f, err := redigo.Float64(Do("HGET", key, HKey)) | |||
return f, err | |||
} | |||
func HGetInt(key string, HKey string) (int, error) { | |||
return redigo.Int(Do("HGET", key, HKey)) | |||
} | |||
func HGetInt64(key string, HKey string) (int64, error) { | |||
return redigo.Int64(Do("HGET", key, HKey)) | |||
} | |||
func HGetBool(key string, HKey string) (bool, error) { | |||
return redigo.Bool(Do("HGET", key, HKey)) | |||
} | |||
func HDel(key string, HKey string) (interface{}, error) { | |||
return Do("HDEL", key, HKey) | |||
} | |||
func HGetAll(key string) (map[string]interface{}, error) { | |||
vals, err := redigo.Values(Do("HGETALL", key)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
num := len(vals) / 2 | |||
result := make(map[string]interface{}, num) | |||
for i := 0; i < num; i++ { | |||
key, _ := redigo.String(vals[2*i], nil) | |||
result[key] = vals[2*i+1] | |||
} | |||
return result, nil | |||
} | |||
func FlushAll() bool { | |||
res, _ := redigo.String(Do("FLUSHALL")) | |||
if res == "" { | |||
return false | |||
} | |||
return true | |||
} | |||
// NOTE: Use this in production environment with extreme care. | |||
// Read more here:https://redigo.io/commands/keys | |||
func Keys(pattern string) ([]string, error) { | |||
return redigo.Strings(Do("KEYS", pattern)) | |||
} | |||
func HKeys(key string) ([]string, error) { | |||
return redigo.Strings(Do("HKEYS", key)) | |||
} | |||
func Exists(key string) bool { | |||
count, err := redigo.Int(Do("EXISTS", key)) | |||
if count == 0 || err != nil { | |||
return false | |||
} | |||
return true | |||
} | |||
func Incr(key string) (int64, error) { | |||
return redigo.Int64(Do("INCR", key)) | |||
} | |||
func Decr(key string) (int64, error) { | |||
return redigo.Int64(Do("DECR", key)) | |||
} | |||
func IncrBy(key string, incBy int64) (int64, error) { | |||
return redigo.Int64(Do("INCRBY", key, incBy)) | |||
} | |||
func DecrBy(key string, decrBy int64) (int64, error) { | |||
return redigo.Int64(Do("DECRBY", key)) | |||
} | |||
func IncrByFloat(key string, incBy float64) (float64, error) { | |||
return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) | |||
} | |||
func DecrByFloat(key string, decrBy float64) (float64, error) { | |||
return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) | |||
} | |||
// use for message queue | |||
func LPush(key string, data interface{}) (interface{}, error) { | |||
// set | |||
return Do("LPUSH", key, data) | |||
} | |||
func LPop(key string) (interface{}, error) { | |||
return Do("LPOP", key) | |||
} | |||
func LPopString(key string) (string, error) { | |||
return redigo.String(Do("LPOP", key)) | |||
} | |||
func LPopFloat(key string) (float64, error) { | |||
f, err := redigo.Float64(Do("LPOP", key)) | |||
return f, err | |||
} | |||
func LPopInt(key string) (int, error) { | |||
return redigo.Int(Do("LPOP", key)) | |||
} | |||
func LPopInt64(key string) (int64, error) { | |||
return redigo.Int64(Do("LPOP", key)) | |||
} | |||
func RPush(key string, data interface{}) (interface{}, error) { | |||
// set | |||
return Do("RPUSH", key, data) | |||
} | |||
func RPop(key string) (interface{}, error) { | |||
return Do("RPOP", key) | |||
} | |||
func RPopString(key string) (string, error) { | |||
return redigo.String(Do("RPOP", key)) | |||
} | |||
func RPopFloat(key string) (float64, error) { | |||
f, err := redigo.Float64(Do("RPOP", key)) | |||
return f, err | |||
} | |||
func RPopInt(key string) (int, error) { | |||
return redigo.Int(Do("RPOP", key)) | |||
} | |||
func RPopInt64(key string) (int64, error) { | |||
return redigo.Int64(Do("RPOP", key)) | |||
} | |||
func Scan(cursor int64, pattern string, count int64) (int64, []string, error) { | |||
var items []string | |||
var newCursor int64 | |||
values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
values, err = redigo.Scan(values, &newCursor, &items) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
return newCursor, items, nil | |||
} |
@@ -0,0 +1,622 @@ | |||
package cache | |||
import ( | |||
"strconv" | |||
"time" | |||
"github.com/go-redis/redis" | |||
) | |||
var pools *redis.ClusterClient | |||
func NewRedisCluster(addrs []string) error { | |||
opt := &redis.ClusterOptions{ | |||
Addrs: addrs, | |||
PoolSize: redisPoolSize, | |||
PoolTimeout: redisPoolTTL, | |||
IdleTimeout: redisIdleTTL, | |||
DialTimeout: redisDialTTL, | |||
ReadTimeout: redisReadTTL, | |||
WriteTimeout: redisWriteTTL, | |||
} | |||
pools = redis.NewClusterClient(opt) | |||
if err := pools.Ping().Err(); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func RCGet(key string) (interface{}, error) { | |||
res, err := pools.Get(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func RCSet(key string, value interface{}) error { | |||
err := pools.Set(key, value, 0).Err() | |||
return convertError(err) | |||
} | |||
func RCGetSet(key string, value interface{}) (interface{}, error) { | |||
res, err := pools.GetSet(key, value).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func RCSetNx(key string, value interface{}) (int64, error) { | |||
res, err := pools.SetNX(key, value, 0).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
if res { | |||
return 1, nil | |||
} | |||
return 0, nil | |||
} | |||
func RCSetEx(key string, value interface{}, timeout int64) error { | |||
_, err := pools.Set(key, value, time.Duration(timeout)*time.Second).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 | |||
func RCSetNxEx(key string, value interface{}, timeout int64) error { | |||
res, err := pools.SetNX(key, value, time.Duration(timeout)*time.Second).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
if res { | |||
return nil | |||
} | |||
return ErrNil | |||
} | |||
func RCMGet(keys ...string) ([]interface{}, error) { | |||
res, err := pools.MGet(keys...).Result() | |||
return res, convertError(err) | |||
} | |||
// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} | |||
func RCMSet(kvs map[string]interface{}) error { | |||
pairs := make([]string, 0, len(kvs)*2) | |||
for k, v := range kvs { | |||
val, err := String(v, nil) | |||
if err != nil { | |||
return err | |||
} | |||
pairs = append(pairs, k, val) | |||
} | |||
return convertError(pools.MSet(pairs).Err()) | |||
} | |||
// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} | |||
func RCMSetNX(kvs map[string]interface{}) (bool, error) { | |||
pairs := make([]string, 0, len(kvs)*2) | |||
for k, v := range kvs { | |||
val, err := String(v, nil) | |||
if err != nil { | |||
return false, err | |||
} | |||
pairs = append(pairs, k, val) | |||
} | |||
res, err := pools.MSetNX(pairs).Result() | |||
return res, convertError(err) | |||
} | |||
func RCExpireAt(key string, timestamp int64) (int64, error) { | |||
res, err := pools.ExpireAt(key, time.Unix(timestamp, 0)).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
if res { | |||
return 1, nil | |||
} | |||
return 0, nil | |||
} | |||
func RCDel(keys ...string) (int64, error) { | |||
args := make([]interface{}, 0, len(keys)) | |||
for _, key := range keys { | |||
args = append(args, key) | |||
} | |||
res, err := pools.Del(keys...).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCIncr(key string) (int64, error) { | |||
res, err := pools.Incr(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCIncrBy(key string, delta int64) (int64, error) { | |||
res, err := pools.IncrBy(key, delta).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCExpire(key string, duration int64) (int64, error) { | |||
res, err := pools.Expire(key, time.Duration(duration)*time.Second).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
if res { | |||
return 1, nil | |||
} | |||
return 0, nil | |||
} | |||
func RCExists(key string) (bool, error) { | |||
res, err := pools.Exists(key).Result() | |||
if err != nil { | |||
return false, convertError(err) | |||
} | |||
if res > 0 { | |||
return true, nil | |||
} | |||
return false, nil | |||
} | |||
func RCHGet(key string, field string) (interface{}, error) { | |||
res, err := pools.HGet(key, field).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func RCHLen(key string) (int64, error) { | |||
res, err := pools.HLen(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCHSet(key string, field string, val interface{}) error { | |||
value, err := String(val, nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
_, err = pools.HSet(key, field, value).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
func RCHDel(key string, fields ...string) (int64, error) { | |||
args := make([]interface{}, 0, len(fields)+1) | |||
args = append(args, key) | |||
for _, field := range fields { | |||
args = append(args, field) | |||
} | |||
res, err := pools.HDel(key, fields...).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCHMGet(key string, fields ...string) (interface{}, error) { | |||
args := make([]interface{}, 0, len(fields)+1) | |||
args = append(args, key) | |||
for _, field := range fields { | |||
args = append(args, field) | |||
} | |||
if len(fields) == 0 { | |||
return nil, ErrNil | |||
} | |||
res, err := pools.HMGet(key, fields...).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCHMSet(key string, kvs ...interface{}) error { | |||
if len(kvs) == 0 { | |||
return nil | |||
} | |||
if len(kvs)%2 != 0 { | |||
return ErrWrongArgsNum | |||
} | |||
var err error | |||
v := map[string]interface{}{} // todo change | |||
v["field"], err = String(kvs[0], nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
v["value"], err = String(kvs[1], nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
pairs := make([]string, 0, len(kvs)-2) | |||
if len(kvs) > 2 { | |||
for _, kv := range kvs[2:] { | |||
kvString, err := String(kv, nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
pairs = append(pairs, kvString) | |||
} | |||
} | |||
v["paris"] = pairs | |||
_, err = pools.HMSet(key, v).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
func RCHKeys(key string) ([]string, error) { | |||
res, err := pools.HKeys(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCHVals(key string) ([]interface{}, error) { | |||
res, err := pools.HVals(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
rs := make([]interface{}, 0, len(res)) | |||
for _, res := range res { | |||
rs = append(rs, res) | |||
} | |||
return rs, nil | |||
} | |||
func RCHGetAll(key string) (map[string]string, error) { | |||
vals, err := pools.HGetAll(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return vals, nil | |||
} | |||
func RCHIncrBy(key, field string, delta int64) (int64, error) { | |||
res, err := pools.HIncrBy(key, field, delta).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCZAdd(key string, kvs ...interface{}) (int64, error) { | |||
args := make([]interface{}, 0, len(kvs)+1) | |||
args = append(args, key) | |||
args = append(args, kvs...) | |||
if len(kvs) == 0 { | |||
return 0, nil | |||
} | |||
if len(kvs)%2 != 0 { | |||
return 0, ErrWrongArgsNum | |||
} | |||
zs := make([]redis.Z, len(kvs)/2) | |||
for i := 0; i < len(kvs); i += 2 { | |||
idx := i / 2 | |||
score, err := Float64(kvs[i], nil) | |||
if err != nil && err != ErrNil { | |||
return 0, err | |||
} | |||
zs[idx].Score = score | |||
zs[idx].Member = kvs[i+1] | |||
} | |||
res, err := pools.ZAdd(key, zs...).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCZRem(key string, members ...string) (int64, error) { | |||
args := make([]interface{}, 0, len(members)) | |||
args = append(args, key) | |||
for _, member := range members { | |||
args = append(args, member) | |||
} | |||
res, err := pools.ZRem(key, members).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, err | |||
} | |||
func RCZRange(key string, min, max int64, withScores bool) (interface{}, error) { | |||
res := make([]interface{}, 0) | |||
if withScores { | |||
zs, err := pools.ZRangeWithScores(key, min, max).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
for _, z := range zs { | |||
res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) | |||
} | |||
} else { | |||
ms, err := pools.ZRange(key, min, max).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
for _, m := range ms { | |||
res = append(res, m) | |||
} | |||
} | |||
return res, nil | |||
} | |||
func RCZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { | |||
opt := new(redis.ZRangeBy) | |||
opt.Min = strconv.FormatInt(int64(min), 10) | |||
opt.Max = strconv.FormatInt(int64(max), 10) | |||
opt.Count = -1 | |||
opt.Offset = 0 | |||
vals, err := pools.ZRangeByScoreWithScores(key, *opt).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
res := make(map[string]int64, len(vals)) | |||
for _, val := range vals { | |||
key, err := String(val.Member, nil) | |||
if err != nil && err != ErrNil { | |||
return nil, err | |||
} | |||
res[key] = int64(val.Score) | |||
} | |||
return res, nil | |||
} | |||
func RCLRange(key string, start, stop int64) (interface{}, error) { | |||
res, err := pools.LRange(key, start, stop).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCLSet(key string, index int, value interface{}) error { | |||
err := pools.LSet(key, int64(index), value).Err() | |||
return convertError(err) | |||
} | |||
func RCLLen(key string) (int64, error) { | |||
res, err := pools.LLen(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCLRem(key string, count int, value interface{}) (int, error) { | |||
val, _ := value.(string) | |||
res, err := pools.LRem(key, int64(count), val).Result() | |||
if err != nil { | |||
return int(res), convertError(err) | |||
} | |||
return int(res), nil | |||
} | |||
func RCTTl(key string) (int64, error) { | |||
duration, err := pools.TTL(key).Result() | |||
if err != nil { | |||
return int64(duration.Seconds()), convertError(err) | |||
} | |||
return int64(duration.Seconds()), nil | |||
} | |||
func RCLPop(key string) (interface{}, error) { | |||
res, err := pools.LPop(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCRPop(key string) (interface{}, error) { | |||
res, err := pools.RPop(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCBLPop(key string, timeout int) (interface{}, error) { | |||
res, err := pools.BLPop(time.Duration(timeout)*time.Second, key).Result() | |||
if err != nil { | |||
// 兼容redis 2.x | |||
if err == redis.Nil { | |||
return nil, ErrNil | |||
} | |||
return nil, err | |||
} | |||
return res[1], nil | |||
} | |||
func RCBRPop(key string, timeout int) (interface{}, error) { | |||
res, err := pools.BRPop(time.Duration(timeout)*time.Second, key).Result() | |||
if err != nil { | |||
// 兼容redis 2.x | |||
if err == redis.Nil { | |||
return nil, ErrNil | |||
} | |||
return nil, convertError(err) | |||
} | |||
return res[1], nil | |||
} | |||
func RCLPush(key string, value ...interface{}) error { | |||
args := make([]interface{}, 0, len(value)+1) | |||
args = append(args, key) | |||
args = append(args, value...) | |||
vals := make([]string, 0, len(value)) | |||
for _, v := range value { | |||
val, err := String(v, nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
vals = append(vals, val) | |||
} | |||
_, err := pools.LPush(key, vals).Result() // todo ... | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
func RCRPush(key string, value ...interface{}) error { | |||
args := make([]interface{}, 0, len(value)+1) | |||
args = append(args, key) | |||
args = append(args, value...) | |||
vals := make([]string, 0, len(value)) | |||
for _, v := range value { | |||
val, err := String(v, nil) | |||
if err != nil && err != ErrNil { | |||
if err == ErrNil { | |||
continue | |||
} | |||
return err | |||
} | |||
if val == "" { | |||
continue | |||
} | |||
vals = append(vals, val) | |||
} | |||
_, err := pools.RPush(key, vals).Result() // todo ... | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} | |||
func RCBRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { | |||
res, err := pools.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} | |||
func RCRPopLPush(srcKey string, destKey string) (interface{}, error) { | |||
res, err := pools.RPopLPush(srcKey, destKey).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCSAdd(key string, members ...interface{}) (int64, error) { | |||
args := make([]interface{}, 0, len(members)+1) | |||
args = append(args, key) | |||
args = append(args, members...) | |||
ms := make([]string, 0, len(members)) | |||
for _, member := range members { | |||
m, err := String(member, nil) | |||
if err != nil && err != ErrNil { | |||
return 0, err | |||
} | |||
ms = append(ms, m) | |||
} | |||
res, err := pools.SAdd(key, ms).Result() // todo ... | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCSPop(key string) ([]byte, error) { | |||
res, err := pools.SPop(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func RCSIsMember(key string, member interface{}) (bool, error) { | |||
m, _ := member.(string) | |||
res, err := pools.SIsMember(key, m).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCSRem(key string, members ...interface{}) (int64, error) { | |||
args := make([]interface{}, 0, len(members)+1) | |||
args = append(args, key) | |||
args = append(args, members...) | |||
ms := make([]string, 0, len(members)) | |||
for _, member := range members { | |||
m, err := String(member, nil) | |||
if err != nil && err != ErrNil { | |||
return 0, err | |||
} | |||
ms = append(ms, m) | |||
} | |||
res, err := pools.SRem(key, ms).Result() // todo ... | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCSMembers(key string) ([]string, error) { | |||
res, err := pools.SMembers(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCScriptLoad(luaScript string) (interface{}, error) { | |||
res, err := pools.ScriptLoad(luaScript).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCEvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { | |||
vals := make([]interface{}, 0, len(keysArgs)+2) | |||
vals = append(vals, sha1, numberKeys) | |||
vals = append(vals, keysArgs...) | |||
keys := make([]string, 0, numberKeys) | |||
args := make([]string, 0, len(keysArgs)-numberKeys) | |||
for i, value := range keysArgs { | |||
val, err := String(value, nil) | |||
if err != nil && err != ErrNil { | |||
return nil, err | |||
} | |||
if i < numberKeys { | |||
keys = append(keys, val) | |||
} else { | |||
args = append(args, val) | |||
} | |||
} | |||
res, err := pools.EvalSha(sha1, keys, args).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCEval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { | |||
vals := make([]interface{}, 0, len(keysArgs)+2) | |||
vals = append(vals, luaScript, numberKeys) | |||
vals = append(vals, keysArgs...) | |||
keys := make([]string, 0, numberKeys) | |||
args := make([]string, 0, len(keysArgs)-numberKeys) | |||
for i, value := range keysArgs { | |||
val, err := String(value, nil) | |||
if err != nil && err != ErrNil { | |||
return nil, err | |||
} | |||
if i < numberKeys { | |||
keys = append(keys, val) | |||
} else { | |||
args = append(args, val) | |||
} | |||
} | |||
res, err := pools.Eval(luaScript, keys, args).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCGetBit(key string, offset int64) (int64, error) { | |||
res, err := pools.GetBit(key, offset).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func RCSetBit(key string, offset uint32, value int) (int, error) { | |||
res, err := pools.SetBit(key, int64(offset), value).Result() | |||
return int(res), convertError(err) | |||
} | |||
func RCGetClient() *redis.ClusterClient { | |||
return pools | |||
} | |||
func convertError(err error) error { | |||
if err == redis.Nil { | |||
// 为了兼容redis 2.x,这里不返回 ErrNil,ErrNil在调用redis_cluster_reply函数时才返回 | |||
return nil | |||
} | |||
return err | |||
} |
@@ -0,0 +1,324 @@ | |||
package cache | |||
import ( | |||
"errors" | |||
"log" | |||
"strings" | |||
"time" | |||
redigo "github.com/gomodule/redigo/redis" | |||
) | |||
type RedisPool struct { | |||
*redigo.Pool | |||
} | |||
func NewRedisPool(cfg *Config) *RedisPool { | |||
return &RedisPool{&redigo.Pool{ | |||
MaxIdle: cfg.MaxIdle, | |||
IdleTimeout: cfg.IdleTimeout, | |||
MaxActive: cfg.MaxActive, | |||
Wait: cfg.Wait, | |||
Dial: func() (redigo.Conn, error) { | |||
c, err := redigo.Dial("tcp", cfg.Server) | |||
if err != nil { | |||
log.Println("Redis Dial failed: ", err) | |||
return nil, err | |||
} | |||
if cfg.Password != "" { | |||
if _, err := c.Do("AUTH", cfg.Password); err != nil { | |||
c.Close() | |||
log.Println("Redis AUTH failed: ", err) | |||
return nil, err | |||
} | |||
} | |||
return c, err | |||
}, | |||
TestOnBorrow: func(c redigo.Conn, t time.Time) error { | |||
_, err := c.Do("PING") | |||
if err != nil { | |||
log.Println("Unable to ping to redis server:", err) | |||
} | |||
return err | |||
}, | |||
}} | |||
} | |||
func (p *RedisPool) Do(cmd string, args ...interface{}) (reply interface{}, err error) { | |||
conn := pool.Get() | |||
defer conn.Close() | |||
return conn.Do(cmd, args...) | |||
} | |||
func (p *RedisPool) GetPool() *redigo.Pool { | |||
return pool | |||
} | |||
func (p *RedisPool) ParseKey(key string, vars []string) (string, error) { | |||
arr := strings.Split(key, conf.KeyPlaceholder) | |||
actualKey := "" | |||
if len(arr) != len(vars)+1 { | |||
return "", errors.New("redis/connection.go: Insufficient arguments to parse key") | |||
} else { | |||
for index, val := range arr { | |||
if index == 0 { | |||
actualKey = arr[index] | |||
} else { | |||
actualKey += vars[index-1] + val | |||
} | |||
} | |||
} | |||
return getPrefixedKey(actualKey), nil | |||
} | |||
func (p *RedisPool) getPrefixedKey(key string) string { | |||
return conf.KeyPrefix + conf.KeyDelimiter + key | |||
} | |||
func (p *RedisPool) StripEnvKey(key string) string { | |||
return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) | |||
} | |||
func (p *RedisPool) SplitKey(key string) []string { | |||
return strings.Split(key, conf.KeyDelimiter) | |||
} | |||
func (p *RedisPool) Expire(key string, ttl int) (interface{}, error) { | |||
return Do("EXPIRE", key, ttl) | |||
} | |||
func (p *RedisPool) Persist(key string) (interface{}, error) { | |||
return Do("PERSIST", key) | |||
} | |||
func (p *RedisPool) Del(key string) (interface{}, error) { | |||
return Do("DEL", key) | |||
} | |||
func (p *RedisPool) Set(key string, data interface{}) (interface{}, error) { | |||
// set | |||
return Do("SET", key, data) | |||
} | |||
func (p *RedisPool) SetNX(key string, data interface{}) (interface{}, error) { | |||
return Do("SETNX", key, data) | |||
} | |||
func (p *RedisPool) SetEx(key string, data interface{}, ttl int) (interface{}, error) { | |||
return Do("SETEX", key, ttl, data) | |||
} | |||
func (p *RedisPool) Get(key string) (interface{}, error) { | |||
// get | |||
return Do("GET", key) | |||
} | |||
func (p *RedisPool) GetStringMap(key string) (map[string]string, error) { | |||
// get | |||
return redigo.StringMap(Do("GET", key)) | |||
} | |||
func (p *RedisPool) GetTTL(key string) (time.Duration, error) { | |||
ttl, err := redigo.Int64(Do("TTL", key)) | |||
return time.Duration(ttl) * time.Second, err | |||
} | |||
func (p *RedisPool) GetBytes(key string) ([]byte, error) { | |||
return redigo.Bytes(Do("GET", key)) | |||
} | |||
func (p *RedisPool) GetString(key string) (string, error) { | |||
return redigo.String(Do("GET", key)) | |||
} | |||
func (p *RedisPool) GetInt(key string) (int, error) { | |||
return redigo.Int(Do("GET", key)) | |||
} | |||
func (p *RedisPool) GetStringLength(key string) (int, error) { | |||
return redigo.Int(Do("STRLEN", key)) | |||
} | |||
func (p *RedisPool) ZAdd(key string, score float64, data interface{}) (interface{}, error) { | |||
return Do("ZADD", key, score, data) | |||
} | |||
func (p *RedisPool) ZRem(key string, data interface{}) (interface{}, error) { | |||
return Do("ZREM", key, data) | |||
} | |||
func (p *RedisPool) ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { | |||
if withScores { | |||
return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) | |||
} | |||
return redigo.Values(Do("ZRANGE", key, start, end)) | |||
} | |||
func (p *RedisPool) SAdd(setName string, data interface{}) (interface{}, error) { | |||
return Do("SADD", setName, data) | |||
} | |||
func (p *RedisPool) SCard(setName string) (int64, error) { | |||
return redigo.Int64(Do("SCARD", setName)) | |||
} | |||
func (p *RedisPool) SIsMember(setName string, data interface{}) (bool, error) { | |||
return redigo.Bool(Do("SISMEMBER", setName, data)) | |||
} | |||
func (p *RedisPool) SMembers(setName string) ([]string, error) { | |||
return redigo.Strings(Do("SMEMBERS", setName)) | |||
} | |||
func (p *RedisPool) SRem(setName string, data interface{}) (interface{}, error) { | |||
return Do("SREM", setName, data) | |||
} | |||
func (p *RedisPool) HSet(key string, HKey string, data interface{}) (interface{}, error) { | |||
return Do("HSET", key, HKey, data) | |||
} | |||
func (p *RedisPool) HGet(key string, HKey string) (interface{}, error) { | |||
return Do("HGET", key, HKey) | |||
} | |||
func (p *RedisPool) HMGet(key string, hashKeys ...string) ([]interface{}, error) { | |||
ret, err := Do("HMGET", key, hashKeys) | |||
if err != nil { | |||
return nil, err | |||
} | |||
reta, ok := ret.([]interface{}) | |||
if !ok { | |||
return nil, errors.New("result not an array") | |||
} | |||
return reta, nil | |||
} | |||
func (p *RedisPool) HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { | |||
if len(hashKeys) == 0 || len(hashKeys) != len(vals) { | |||
var ret interface{} | |||
return ret, errors.New("bad length") | |||
} | |||
input := []interface{}{key} | |||
for i, v := range hashKeys { | |||
input = append(input, v, vals[i]) | |||
} | |||
return Do("HMSET", input...) | |||
} | |||
func (p *RedisPool) HGetString(key string, HKey string) (string, error) { | |||
return redigo.String(Do("HGET", key, HKey)) | |||
} | |||
func (p *RedisPool) HGetFloat(key string, HKey string) (float64, error) { | |||
f, err := redigo.Float64(Do("HGET", key, HKey)) | |||
return float64(f), err | |||
} | |||
func (p *RedisPool) HGetInt(key string, HKey string) (int, error) { | |||
return redigo.Int(Do("HGET", key, HKey)) | |||
} | |||
func (p *RedisPool) HGetInt64(key string, HKey string) (int64, error) { | |||
return redigo.Int64(Do("HGET", key, HKey)) | |||
} | |||
func (p *RedisPool) HGetBool(key string, HKey string) (bool, error) { | |||
return redigo.Bool(Do("HGET", key, HKey)) | |||
} | |||
func (p *RedisPool) HDel(key string, HKey string) (interface{}, error) { | |||
return Do("HDEL", key, HKey) | |||
} | |||
func (p *RedisPool) HGetAll(key string) (map[string]interface{}, error) { | |||
vals, err := redigo.Values(Do("HGETALL", key)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
num := len(vals) / 2 | |||
result := make(map[string]interface{}, num) | |||
for i := 0; i < num; i++ { | |||
key, _ := redigo.String(vals[2*i], nil) | |||
result[key] = vals[2*i+1] | |||
} | |||
return result, nil | |||
} | |||
// NOTE: Use this in production environment with extreme care. | |||
// Read more here:https://redigo.io/commands/keys | |||
func (p *RedisPool) Keys(pattern string) ([]string, error) { | |||
return redigo.Strings(Do("KEYS", pattern)) | |||
} | |||
func (p *RedisPool) HKeys(key string) ([]string, error) { | |||
return redigo.Strings(Do("HKEYS", key)) | |||
} | |||
func (p *RedisPool) Exists(key string) (bool, error) { | |||
count, err := redigo.Int(Do("EXISTS", key)) | |||
if count == 0 { | |||
return false, err | |||
} else { | |||
return true, err | |||
} | |||
} | |||
func (p *RedisPool) Incr(key string) (int64, error) { | |||
return redigo.Int64(Do("INCR", key)) | |||
} | |||
func (p *RedisPool) Decr(key string) (int64, error) { | |||
return redigo.Int64(Do("DECR", key)) | |||
} | |||
func (p *RedisPool) IncrBy(key string, incBy int64) (int64, error) { | |||
return redigo.Int64(Do("INCRBY", key, incBy)) | |||
} | |||
func (p *RedisPool) DecrBy(key string, decrBy int64) (int64, error) { | |||
return redigo.Int64(Do("DECRBY", key)) | |||
} | |||
func (p *RedisPool) IncrByFloat(key string, incBy float64) (float64, error) { | |||
return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) | |||
} | |||
func (p *RedisPool) DecrByFloat(key string, decrBy float64) (float64, error) { | |||
return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) | |||
} | |||
// use for message queue | |||
func (p *RedisPool) LPush(key string, data interface{}) (interface{}, error) { | |||
// set | |||
return Do("LPUSH", key, data) | |||
} | |||
func (p *RedisPool) LPop(key string) (interface{}, error) { | |||
return Do("LPOP", key) | |||
} | |||
func (p *RedisPool) LPopString(key string) (string, error) { | |||
return redigo.String(Do("LPOP", key)) | |||
} | |||
func (p *RedisPool) LPopFloat(key string) (float64, error) { | |||
f, err := redigo.Float64(Do("LPOP", key)) | |||
return float64(f), err | |||
} | |||
func (p *RedisPool) LPopInt(key string) (int, error) { | |||
return redigo.Int(Do("LPOP", key)) | |||
} | |||
func (p *RedisPool) LPopInt64(key string) (int64, error) { | |||
return redigo.Int64(Do("LPOP", key)) | |||
} | |||
func (p *RedisPool) RPush(key string, data interface{}) (interface{}, error) { | |||
// set | |||
return Do("RPUSH", key, data) | |||
} | |||
func (p *RedisPool) RPop(key string) (interface{}, error) { | |||
return Do("RPOP", key) | |||
} | |||
func (p *RedisPool) RPopString(key string) (string, error) { | |||
return redigo.String(Do("RPOP", key)) | |||
} | |||
func (p *RedisPool) RPopFloat(key string) (float64, error) { | |||
f, err := redigo.Float64(Do("RPOP", key)) | |||
return float64(f), err | |||
} | |||
func (p *RedisPool) RPopInt(key string) (int, error) { | |||
return redigo.Int(Do("RPOP", key)) | |||
} | |||
func (p *RedisPool) RPopInt64(key string) (int64, error) { | |||
return redigo.Int64(Do("RPOP", key)) | |||
} | |||
func (p *RedisPool) Scan(cursor int64, pattern string, count int64) (int64, []string, error) { | |||
var items []string | |||
var newCursor int64 | |||
values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
values, err = redigo.Scan(values, &newCursor, &items) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
return newCursor, items, nil | |||
} |
@@ -0,0 +1,617 @@ | |||
package cache | |||
import ( | |||
"strconv" | |||
"time" | |||
"github.com/go-redis/redis" | |||
) | |||
type RedisClusterPool struct { | |||
client *redis.ClusterClient | |||
} | |||
func NewRedisClusterPool(addrs []string) (*RedisClusterPool, error) { | |||
opt := &redis.ClusterOptions{ | |||
Addrs: addrs, | |||
PoolSize: 512, | |||
PoolTimeout: 10 * time.Second, | |||
IdleTimeout: 10 * time.Second, | |||
DialTimeout: 10 * time.Second, | |||
ReadTimeout: 3 * time.Second, | |||
WriteTimeout: 3 * time.Second, | |||
} | |||
c := redis.NewClusterClient(opt) | |||
if err := c.Ping().Err(); err != nil { | |||
return nil, err | |||
} | |||
return &RedisClusterPool{client: c}, nil | |||
} | |||
func (p *RedisClusterPool) Get(key string) (interface{}, error) { | |||
res, err := p.client.Get(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func (p *RedisClusterPool) Set(key string, value interface{}) error { | |||
err := p.client.Set(key, value, 0).Err() | |||
return convertError(err) | |||
} | |||
func (p *RedisClusterPool) GetSet(key string, value interface{}) (interface{}, error) { | |||
res, err := p.client.GetSet(key, value).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func (p *RedisClusterPool) SetNx(key string, value interface{}) (int64, error) { | |||
res, err := p.client.SetNX(key, value, 0).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
if res { | |||
return 1, nil | |||
} | |||
return 0, nil | |||
} | |||
func (p *RedisClusterPool) SetEx(key string, value interface{}, timeout int64) error { | |||
_, err := p.client.Set(key, value, time.Duration(timeout)*time.Second).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 | |||
func (p *RedisClusterPool) SetNxEx(key string, value interface{}, timeout int64) error { | |||
res, err := p.client.SetNX(key, value, time.Duration(timeout)*time.Second).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
if res { | |||
return nil | |||
} | |||
return ErrNil | |||
} | |||
func (p *RedisClusterPool) MGet(keys ...string) ([]interface{}, error) { | |||
res, err := p.client.MGet(keys...).Result() | |||
return res, convertError(err) | |||
} | |||
// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} | |||
func (p *RedisClusterPool) MSet(kvs map[string]interface{}) error { | |||
pairs := make([]string, 0, len(kvs)*2) | |||
for k, v := range kvs { | |||
val, err := String(v, nil) | |||
if err != nil { | |||
return err | |||
} | |||
pairs = append(pairs, k, val) | |||
} | |||
return convertError(p.client.MSet(pairs).Err()) | |||
} | |||
// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} | |||
func (p *RedisClusterPool) MSetNX(kvs map[string]interface{}) (bool, error) { | |||
pairs := make([]string, 0, len(kvs)*2) | |||
for k, v := range kvs { | |||
val, err := String(v, nil) | |||
if err != nil { | |||
return false, err | |||
} | |||
pairs = append(pairs, k, val) | |||
} | |||
res, err := p.client.MSetNX(pairs).Result() | |||
return res, convertError(err) | |||
} | |||
func (p *RedisClusterPool) ExpireAt(key string, timestamp int64) (int64, error) { | |||
res, err := p.client.ExpireAt(key, time.Unix(timestamp, 0)).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
if res { | |||
return 1, nil | |||
} | |||
return 0, nil | |||
} | |||
func (p *RedisClusterPool) Del(keys ...string) (int64, error) { | |||
args := make([]interface{}, 0, len(keys)) | |||
for _, key := range keys { | |||
args = append(args, key) | |||
} | |||
res, err := p.client.Del(keys...).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) Incr(key string) (int64, error) { | |||
res, err := p.client.Incr(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) IncrBy(key string, delta int64) (int64, error) { | |||
res, err := p.client.IncrBy(key, delta).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) Expire(key string, duration int64) (int64, error) { | |||
res, err := p.client.Expire(key, time.Duration(duration)*time.Second).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
if res { | |||
return 1, nil | |||
} | |||
return 0, nil | |||
} | |||
func (p *RedisClusterPool) Exists(key string) (bool, error) { // todo (bool, error) | |||
res, err := p.client.Exists(key).Result() | |||
if err != nil { | |||
return false, convertError(err) | |||
} | |||
if res > 0 { | |||
return true, nil | |||
} | |||
return false, nil | |||
} | |||
func (p *RedisClusterPool) HGet(key string, field string) (interface{}, error) { | |||
res, err := p.client.HGet(key, field).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func (p *RedisClusterPool) HLen(key string) (int64, error) { | |||
res, err := p.client.HLen(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) HSet(key string, field string, val interface{}) error { | |||
value, err := String(val, nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
_, err = p.client.HSet(key, field, value).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
func (p *RedisClusterPool) HDel(key string, fields ...string) (int64, error) { | |||
args := make([]interface{}, 0, len(fields)+1) | |||
args = append(args, key) | |||
for _, field := range fields { | |||
args = append(args, field) | |||
} | |||
res, err := p.client.HDel(key, fields...).Result() | |||
if err != nil { | |||
return 0, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) HMGet(key string, fields ...string) (interface{}, error) { | |||
args := make([]interface{}, 0, len(fields)+1) | |||
args = append(args, key) | |||
for _, field := range fields { | |||
args = append(args, field) | |||
} | |||
if len(fields) == 0 { | |||
return nil, ErrNil | |||
} | |||
res, err := p.client.HMGet(key, fields...).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) HMSet(key string, kvs ...interface{}) error { | |||
if len(kvs) == 0 { | |||
return nil | |||
} | |||
if len(kvs)%2 != 0 { | |||
return ErrWrongArgsNum | |||
} | |||
var err error | |||
v := map[string]interface{}{} // todo change | |||
v["field"], err = String(kvs[0], nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
v["value"], err = String(kvs[1], nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
pairs := make([]string, 0, len(kvs)-2) | |||
if len(kvs) > 2 { | |||
for _, kv := range kvs[2:] { | |||
kvString, err := String(kv, nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
pairs = append(pairs, kvString) | |||
} | |||
} | |||
v["paris"] = pairs | |||
_, err = p.client.HMSet(key, v).Result() | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
func (p *RedisClusterPool) HKeys(key string) ([]string, error) { | |||
res, err := p.client.HKeys(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) HVals(key string) ([]interface{}, error) { | |||
res, err := p.client.HVals(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
rs := make([]interface{}, 0, len(res)) | |||
for _, res := range res { | |||
rs = append(rs, res) | |||
} | |||
return rs, nil | |||
} | |||
func (p *RedisClusterPool) HGetAll(key string) (map[string]string, error) { | |||
vals, err := p.client.HGetAll(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return vals, nil | |||
} | |||
func (p *RedisClusterPool) HIncrBy(key, field string, delta int64) (int64, error) { | |||
res, err := p.client.HIncrBy(key, field, delta).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) ZAdd(key string, kvs ...interface{}) (int64, error) { | |||
args := make([]interface{}, 0, len(kvs)+1) | |||
args = append(args, key) | |||
args = append(args, kvs...) | |||
if len(kvs) == 0 { | |||
return 0, nil | |||
} | |||
if len(kvs)%2 != 0 { | |||
return 0, ErrWrongArgsNum | |||
} | |||
zs := make([]redis.Z, len(kvs)/2) | |||
for i := 0; i < len(kvs); i += 2 { | |||
idx := i / 2 | |||
score, err := Float64(kvs[i], nil) | |||
if err != nil && err != ErrNil { | |||
return 0, err | |||
} | |||
zs[idx].Score = score | |||
zs[idx].Member = kvs[i+1] | |||
} | |||
res, err := p.client.ZAdd(key, zs...).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) ZRem(key string, members ...string) (int64, error) { | |||
args := make([]interface{}, 0, len(members)) | |||
args = append(args, key) | |||
for _, member := range members { | |||
args = append(args, member) | |||
} | |||
res, err := p.client.ZRem(key, members).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, err | |||
} | |||
func (p *RedisClusterPool) ZRange(key string, min, max int64, withScores bool) (interface{}, error) { | |||
res := make([]interface{}, 0) | |||
if withScores { | |||
zs, err := p.client.ZRangeWithScores(key, min, max).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
for _, z := range zs { | |||
res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) | |||
} | |||
} else { | |||
ms, err := p.client.ZRange(key, min, max).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
for _, m := range ms { | |||
res = append(res, m) | |||
} | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) ZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { | |||
opt := new(redis.ZRangeBy) | |||
opt.Min = strconv.FormatInt(int64(min), 10) | |||
opt.Max = strconv.FormatInt(int64(max), 10) | |||
opt.Count = -1 | |||
opt.Offset = 0 | |||
vals, err := p.client.ZRangeByScoreWithScores(key, *opt).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
res := make(map[string]int64, len(vals)) | |||
for _, val := range vals { | |||
key, err := String(val.Member, nil) | |||
if err != nil && err != ErrNil { | |||
return nil, err | |||
} | |||
res[key] = int64(val.Score) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) LRange(key string, start, stop int64) (interface{}, error) { | |||
res, err := p.client.LRange(key, start, stop).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) LSet(key string, index int, value interface{}) error { | |||
err := p.client.LSet(key, int64(index), value).Err() | |||
return convertError(err) | |||
} | |||
func (p *RedisClusterPool) LLen(key string) (int64, error) { | |||
res, err := p.client.LLen(key).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) LRem(key string, count int, value interface{}) (int, error) { | |||
val, _ := value.(string) | |||
res, err := p.client.LRem(key, int64(count), val).Result() | |||
if err != nil { | |||
return int(res), convertError(err) | |||
} | |||
return int(res), nil | |||
} | |||
func (p *RedisClusterPool) TTl(key string) (int64, error) { | |||
duration, err := p.client.TTL(key).Result() | |||
if err != nil { | |||
return int64(duration.Seconds()), convertError(err) | |||
} | |||
return int64(duration.Seconds()), nil | |||
} | |||
func (p *RedisClusterPool) LPop(key string) (interface{}, error) { | |||
res, err := p.client.LPop(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) RPop(key string) (interface{}, error) { | |||
res, err := p.client.RPop(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) BLPop(key string, timeout int) (interface{}, error) { | |||
res, err := p.client.BLPop(time.Duration(timeout)*time.Second, key).Result() | |||
if err != nil { | |||
// 兼容redis 2.x | |||
if err == redis.Nil { | |||
return nil, ErrNil | |||
} | |||
return nil, err | |||
} | |||
return res[1], nil | |||
} | |||
func (p *RedisClusterPool) BRPop(key string, timeout int) (interface{}, error) { | |||
res, err := p.client.BRPop(time.Duration(timeout)*time.Second, key).Result() | |||
if err != nil { | |||
// 兼容redis 2.x | |||
if err == redis.Nil { | |||
return nil, ErrNil | |||
} | |||
return nil, convertError(err) | |||
} | |||
return res[1], nil | |||
} | |||
func (p *RedisClusterPool) LPush(key string, value ...interface{}) error { | |||
args := make([]interface{}, 0, len(value)+1) | |||
args = append(args, key) | |||
args = append(args, value...) | |||
vals := make([]string, 0, len(value)) | |||
for _, v := range value { | |||
val, err := String(v, nil) | |||
if err != nil && err != ErrNil { | |||
return err | |||
} | |||
vals = append(vals, val) | |||
} | |||
_, err := p.client.LPush(key, vals).Result() // todo ... | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
func (p *RedisClusterPool) RPush(key string, value ...interface{}) error { | |||
args := make([]interface{}, 0, len(value)+1) | |||
args = append(args, key) | |||
args = append(args, value...) | |||
vals := make([]string, 0, len(value)) | |||
for _, v := range value { | |||
val, err := String(v, nil) | |||
if err != nil && err != ErrNil { | |||
if err == ErrNil { | |||
continue | |||
} | |||
return err | |||
} | |||
if val == "" { | |||
continue | |||
} | |||
vals = append(vals, val) | |||
} | |||
_, err := p.client.RPush(key, vals).Result() // todo ... | |||
if err != nil { | |||
return convertError(err) | |||
} | |||
return nil | |||
} | |||
// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} | |||
func (p *RedisClusterPool) BRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { | |||
res, err := p.client.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} | |||
func (p *RedisClusterPool) RPopLPush(srcKey string, destKey string) (interface{}, error) { | |||
res, err := p.client.RPopLPush(srcKey, destKey).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) SAdd(key string, members ...interface{}) (int64, error) { | |||
args := make([]interface{}, 0, len(members)+1) | |||
args = append(args, key) | |||
args = append(args, members...) | |||
ms := make([]string, 0, len(members)) | |||
for _, member := range members { | |||
m, err := String(member, nil) | |||
if err != nil && err != ErrNil { | |||
return 0, err | |||
} | |||
ms = append(ms, m) | |||
} | |||
res, err := p.client.SAdd(key, ms).Result() // todo ... | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) SPop(key string) ([]byte, error) { | |||
res, err := p.client.SPop(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return []byte(res), nil | |||
} | |||
func (p *RedisClusterPool) SIsMember(key string, member interface{}) (bool, error) { | |||
m, _ := member.(string) | |||
res, err := p.client.SIsMember(key, m).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) SRem(key string, members ...interface{}) (int64, error) { | |||
args := make([]interface{}, 0, len(members)+1) | |||
args = append(args, key) | |||
args = append(args, members...) | |||
ms := make([]string, 0, len(members)) | |||
for _, member := range members { | |||
m, err := String(member, nil) | |||
if err != nil && err != ErrNil { | |||
return 0, err | |||
} | |||
ms = append(ms, m) | |||
} | |||
res, err := p.client.SRem(key, ms).Result() // todo ... | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) SMembers(key string) ([]string, error) { | |||
res, err := p.client.SMembers(key).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) ScriptLoad(luaScript string) (interface{}, error) { | |||
res, err := p.client.ScriptLoad(luaScript).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) EvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { | |||
vals := make([]interface{}, 0, len(keysArgs)+2) | |||
vals = append(vals, sha1, numberKeys) | |||
vals = append(vals, keysArgs...) | |||
keys := make([]string, 0, numberKeys) | |||
args := make([]string, 0, len(keysArgs)-numberKeys) | |||
for i, value := range keysArgs { | |||
val, err := String(value, nil) | |||
if err != nil && err != ErrNil { | |||
return nil, err | |||
} | |||
if i < numberKeys { | |||
keys = append(keys, val) | |||
} else { | |||
args = append(args, val) | |||
} | |||
} | |||
res, err := p.client.EvalSha(sha1, keys, args).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) Eval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { | |||
vals := make([]interface{}, 0, len(keysArgs)+2) | |||
vals = append(vals, luaScript, numberKeys) | |||
vals = append(vals, keysArgs...) | |||
keys := make([]string, 0, numberKeys) | |||
args := make([]string, 0, len(keysArgs)-numberKeys) | |||
for i, value := range keysArgs { | |||
val, err := String(value, nil) | |||
if err != nil && err != ErrNil { | |||
return nil, err | |||
} | |||
if i < numberKeys { | |||
keys = append(keys, val) | |||
} else { | |||
args = append(args, val) | |||
} | |||
} | |||
res, err := p.client.Eval(luaScript, keys, args).Result() | |||
if err != nil { | |||
return nil, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) GetBit(key string, offset int64) (int64, error) { | |||
res, err := p.client.GetBit(key, offset).Result() | |||
if err != nil { | |||
return res, convertError(err) | |||
} | |||
return res, nil | |||
} | |||
func (p *RedisClusterPool) SetBit(key string, offset uint32, value int) (int, error) { | |||
res, err := p.client.SetBit(key, int64(offset), value).Result() | |||
return int(res), convertError(err) | |||
} | |||
func (p *RedisClusterPool) GetClient() *redis.ClusterClient { | |||
return pools | |||
} |
@@ -0,0 +1,366 @@ | |||
package zhios_o2o_business_utils | |||
import ( | |||
"encoding/binary" | |||
"encoding/json" | |||
"fmt" | |||
"math" | |||
"strconv" | |||
"strings" | |||
) | |||
func ToString(raw interface{}, e error) (res string) { | |||
if e != nil { | |||
return "" | |||
} | |||
return AnyToString(raw) | |||
} | |||
func ToInt64(raw interface{}, e error) int64 { | |||
if e != nil { | |||
return 0 | |||
} | |||
return AnyToInt64(raw) | |||
} | |||
func AnyToBool(raw interface{}) bool { | |||
switch i := raw.(type) { | |||
case float32, float64, int, int64, uint, uint8, uint16, uint32, uint64, int8, int16, int32: | |||
return i != 0 | |||
case []byte: | |||
return i != nil | |||
case string: | |||
if i == "false" { | |||
return false | |||
} | |||
return i != "" | |||
case error: | |||
return false | |||
case nil: | |||
return true | |||
} | |||
val := fmt.Sprint(raw) | |||
val = strings.TrimLeft(val, "&") | |||
if strings.TrimLeft(val, "{}") == "" { | |||
return false | |||
} | |||
if strings.TrimLeft(val, "[]") == "" { | |||
return false | |||
} | |||
// ptr type | |||
b, err := json.Marshal(raw) | |||
if err != nil { | |||
return false | |||
} | |||
if strings.TrimLeft(string(b), "\"\"") == "" { | |||
return false | |||
} | |||
if strings.TrimLeft(string(b), "{}") == "" { | |||
return false | |||
} | |||
return true | |||
} | |||
func AnyToInt64(raw interface{}) int64 { | |||
switch i := raw.(type) { | |||
case string: | |||
res, _ := strconv.ParseInt(i, 10, 64) | |||
return res | |||
case []byte: | |||
return BytesToInt64(i) | |||
case int: | |||
return int64(i) | |||
case int64: | |||
return i | |||
case uint: | |||
return int64(i) | |||
case uint8: | |||
return int64(i) | |||
case uint16: | |||
return int64(i) | |||
case uint32: | |||
return int64(i) | |||
case uint64: | |||
return int64(i) | |||
case int8: | |||
return int64(i) | |||
case int16: | |||
return int64(i) | |||
case int32: | |||
return int64(i) | |||
case float32: | |||
return int64(i) | |||
case float64: | |||
return int64(i) | |||
case error: | |||
return 0 | |||
case bool: | |||
if i { | |||
return 1 | |||
} | |||
return 0 | |||
} | |||
return 0 | |||
} | |||
func AnyToString(raw interface{}) string { | |||
switch i := raw.(type) { | |||
case []byte: | |||
return string(i) | |||
case int: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int64: | |||
return strconv.FormatInt(i, 10) | |||
case float32: | |||
return Float64ToStr(float64(i)) | |||
case float64: | |||
return Float64ToStr(i) | |||
case uint: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint64: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case string: | |||
return i | |||
case error: | |||
return i.Error() | |||
case bool: | |||
return strconv.FormatBool(i) | |||
} | |||
return fmt.Sprintf("%#v", raw) | |||
} | |||
func AnyToFloat64(raw interface{}) float64 { | |||
switch i := raw.(type) { | |||
case []byte: | |||
f, _ := strconv.ParseFloat(string(i), 64) | |||
return f | |||
case int: | |||
return float64(i) | |||
case int64: | |||
return float64(i) | |||
case float32: | |||
return float64(i) | |||
case float64: | |||
return i | |||
case uint: | |||
return float64(i) | |||
case uint8: | |||
return float64(i) | |||
case uint16: | |||
return float64(i) | |||
case uint32: | |||
return float64(i) | |||
case uint64: | |||
return float64(i) | |||
case int8: | |||
return float64(i) | |||
case int16: | |||
return float64(i) | |||
case int32: | |||
return float64(i) | |||
case string: | |||
f, _ := strconv.ParseFloat(i, 64) | |||
return f | |||
case bool: | |||
if i { | |||
return 1 | |||
} | |||
} | |||
return 0 | |||
} | |||
func ToByte(raw interface{}, e error) []byte { | |||
if e != nil { | |||
return []byte{} | |||
} | |||
switch i := raw.(type) { | |||
case string: | |||
return []byte(i) | |||
case int: | |||
return Int64ToBytes(int64(i)) | |||
case int64: | |||
return Int64ToBytes(i) | |||
case float32: | |||
return Float32ToByte(i) | |||
case float64: | |||
return Float64ToByte(i) | |||
case uint: | |||
return Int64ToBytes(int64(i)) | |||
case uint8: | |||
return Int64ToBytes(int64(i)) | |||
case uint16: | |||
return Int64ToBytes(int64(i)) | |||
case uint32: | |||
return Int64ToBytes(int64(i)) | |||
case uint64: | |||
return Int64ToBytes(int64(i)) | |||
case int8: | |||
return Int64ToBytes(int64(i)) | |||
case int16: | |||
return Int64ToBytes(int64(i)) | |||
case int32: | |||
return Int64ToBytes(int64(i)) | |||
case []byte: | |||
return i | |||
case error: | |||
return []byte(i.Error()) | |||
case bool: | |||
if i { | |||
return []byte("true") | |||
} | |||
return []byte("false") | |||
} | |||
return []byte(fmt.Sprintf("%#v", raw)) | |||
} | |||
func Int64ToBytes(i int64) []byte { | |||
var buf = make([]byte, 8) | |||
binary.BigEndian.PutUint64(buf, uint64(i)) | |||
return buf | |||
} | |||
func BytesToInt64(buf []byte) int64 { | |||
return int64(binary.BigEndian.Uint64(buf)) | |||
} | |||
func StrToInt(s string) int { | |||
res, _ := strconv.Atoi(s) | |||
return res | |||
} | |||
func StrToInt64(s string) int64 { | |||
res, _ := strconv.ParseInt(s, 10, 64) | |||
return res | |||
} | |||
func Float32ToByte(float float32) []byte { | |||
bits := math.Float32bits(float) | |||
bytes := make([]byte, 4) | |||
binary.LittleEndian.PutUint32(bytes, bits) | |||
return bytes | |||
} | |||
func ByteToFloat32(bytes []byte) float32 { | |||
bits := binary.LittleEndian.Uint32(bytes) | |||
return math.Float32frombits(bits) | |||
} | |||
func Float64ToByte(float float64) []byte { | |||
bits := math.Float64bits(float) | |||
bytes := make([]byte, 8) | |||
binary.LittleEndian.PutUint64(bytes, bits) | |||
return bytes | |||
} | |||
func ByteToFloat64(bytes []byte) float64 { | |||
bits := binary.LittleEndian.Uint64(bytes) | |||
return math.Float64frombits(bits) | |||
} | |||
func Float64ToStr(f float64) string { | |||
return strconv.FormatFloat(f, 'f', 2, 64) | |||
} | |||
func Float64ToStrPrec1(f float64) string { | |||
return strconv.FormatFloat(f, 'f', 1, 64) | |||
} | |||
func Float64ToStrByPrec(f float64, prec int) string { | |||
return strconv.FormatFloat(f, 'f', prec, 64) | |||
} | |||
func Float32ToStr(f float32) string { | |||
return Float64ToStr(float64(f)) | |||
} | |||
func StrToFloat64(s string) float64 { | |||
res, err := strconv.ParseFloat(s, 64) | |||
if err != nil { | |||
return 0 | |||
} | |||
return res | |||
} | |||
func StrToFormat(s string, prec int) string { | |||
ex := strings.Split(s, ".") | |||
if len(ex) == 2 { | |||
if StrToFloat64(ex[1]) == 0 { //小数点后面为空就是不要小数点了 | |||
return ex[0] | |||
} | |||
//看取多少位 | |||
str := ex[1] | |||
str1 := str | |||
if prec < len(str) { | |||
str1 = str[0:prec] | |||
} else { | |||
for i := 0; i < prec-len(str); i++ { | |||
str1 += "0" | |||
} | |||
} | |||
if prec > 0 { | |||
return ex[0] + "." + str1 | |||
} else { | |||
return ex[0] | |||
} | |||
} | |||
return s | |||
} | |||
func StrToFloat32(s string) float32 { | |||
res, err := strconv.ParseFloat(s, 32) | |||
if err != nil { | |||
return 0 | |||
} | |||
return float32(res) | |||
} | |||
func StrToBool(s string) bool { | |||
b, _ := strconv.ParseBool(s) | |||
return b | |||
} | |||
func BoolToStr(b bool) string { | |||
if b { | |||
return "true" | |||
} | |||
return "false" | |||
} | |||
func FloatToInt64(f float64) int64 { | |||
return int64(f) | |||
} | |||
func IntToStr(i int) string { | |||
return strconv.Itoa(i) | |||
} | |||
func Int64ToStr(i int64) string { | |||
return strconv.FormatInt(i, 10) | |||
} | |||
func IntToFloat64(i int) float64 { | |||
s := strconv.Itoa(i) | |||
res, err := strconv.ParseFloat(s, 64) | |||
if err != nil { | |||
return 0 | |||
} | |||
return res | |||
} | |||
func Int64ToFloat64(i int64) float64 { | |||
s := strconv.FormatInt(i, 10) | |||
res, err := strconv.ParseFloat(s, 64) | |||
if err != nil { | |||
return 0 | |||
} | |||
return res | |||
} |
@@ -0,0 +1,209 @@ | |||
package zhios_o2o_business_utils | |||
import ( | |||
"bytes" | |||
"crypto/tls" | |||
"fmt" | |||
"io" | |||
"io/ioutil" | |||
"net/http" | |||
"net/url" | |||
"sort" | |||
"strings" | |||
"time" | |||
) | |||
var CurlDebug bool | |||
func CurlGet(router string, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodGet, router, nil, header) | |||
} | |||
func CurlGetJson(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl_new(http.MethodGet, router, body, header) | |||
} | |||
// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string | |||
func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodPost, router, body, header) | |||
} | |||
func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodPut, router, body, header) | |||
} | |||
// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string | |||
func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodPatch, router, body, header) | |||
} | |||
// CurlDelete is curl delete | |||
func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) { | |||
return curl(http.MethodDelete, router, body, header) | |||
} | |||
func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) { | |||
var reqBody io.Reader | |||
contentType := "application/json" | |||
switch v := body.(type) { | |||
case string: | |||
reqBody = strings.NewReader(v) | |||
case []byte: | |||
reqBody = bytes.NewReader(v) | |||
case map[string]string: | |||
val := url.Values{} | |||
for k, v := range v { | |||
val.Set(k, v) | |||
} | |||
reqBody = strings.NewReader(val.Encode()) | |||
contentType = "application/x-www-form-urlencoded" | |||
case map[string]interface{}: | |||
val := url.Values{} | |||
for k, v := range v { | |||
val.Set(k, v.(string)) | |||
} | |||
reqBody = strings.NewReader(val.Encode()) | |||
contentType = "application/x-www-form-urlencoded" | |||
} | |||
if header == nil { | |||
header = map[string]string{"Content-Type": contentType} | |||
} | |||
if _, ok := header["Content-Type"]; !ok { | |||
header["Content-Type"] = contentType | |||
} | |||
resp, er := CurlReq(method, router, reqBody, header) | |||
if er != nil { | |||
return nil, er | |||
} | |||
res, err := ioutil.ReadAll(resp.Body) | |||
if CurlDebug { | |||
blob := SerializeStr(body) | |||
if contentType != "application/json" { | |||
blob = HttpBuild(body) | |||
} | |||
fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", | |||
router, | |||
time.Now().Format("2006-01-02 15:04:05.000"), | |||
method, | |||
contentType, | |||
HttpBuildQuery(header), | |||
blob, | |||
err, | |||
SerializeStr(resp.Header), | |||
string(res), | |||
) | |||
} | |||
resp.Body.Close() | |||
return res, err | |||
} | |||
func curl_new(method, router string, body interface{}, header map[string]string) ([]byte, error) { | |||
var reqBody io.Reader | |||
contentType := "application/json" | |||
if header == nil { | |||
header = map[string]string{"Content-Type": contentType} | |||
} | |||
if _, ok := header["Content-Type"]; !ok { | |||
header["Content-Type"] = contentType | |||
} | |||
resp, er := CurlReq(method, router, reqBody, header) | |||
if er != nil { | |||
return nil, er | |||
} | |||
res, err := ioutil.ReadAll(resp.Body) | |||
if CurlDebug { | |||
blob := SerializeStr(body) | |||
if contentType != "application/json" { | |||
blob = HttpBuild(body) | |||
} | |||
fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", | |||
router, | |||
time.Now().Format("2006-01-02 15:04:05.000"), | |||
method, | |||
contentType, | |||
HttpBuildQuery(header), | |||
blob, | |||
err, | |||
SerializeStr(resp.Header), | |||
string(res), | |||
) | |||
} | |||
resp.Body.Close() | |||
return res, err | |||
} | |||
func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) { | |||
req, _ := http.NewRequest(method, router, reqBody) | |||
if header != nil { | |||
for k, v := range header { | |||
req.Header.Set(k, v) | |||
} | |||
} | |||
// 绕过github等可能因为特征码返回503问题 | |||
// https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/ | |||
defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009, | |||
0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a} | |||
client := &http.Client{ | |||
Transport: &http.Transport{ | |||
TLSClientConfig: &tls.Config{ | |||
InsecureSkipVerify: true, | |||
CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...), | |||
}, | |||
}, | |||
// 获取301重定向 | |||
CheckRedirect: func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
}, | |||
} | |||
return client.Do(req) | |||
} | |||
// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序 a=123&b=321 | |||
func HttpBuildQuery(args map[string]string, sortAsc ...bool) string { | |||
str := "" | |||
if len(args) == 0 { | |||
return str | |||
} | |||
if len(sortAsc) > 0 { | |||
keys := make([]string, 0, len(args)) | |||
for k := range args { | |||
keys = append(keys, k) | |||
} | |||
if sortAsc[0] { | |||
sort.Strings(keys) | |||
} else { | |||
sort.Sort(sort.Reverse(sort.StringSlice(keys))) | |||
} | |||
for _, k := range keys { | |||
str += "&" + k + "=" + args[k] | |||
} | |||
} else { | |||
for k, v := range args { | |||
str += "&" + k + "=" + v | |||
} | |||
} | |||
return str[1:] | |||
} | |||
func HttpBuild(body interface{}, sortAsc ...bool) string { | |||
params := map[string]string{} | |||
if args, ok := body.(map[string]interface{}); ok { | |||
for k, v := range args { | |||
params[k] = AnyToString(v) | |||
} | |||
return HttpBuildQuery(params, sortAsc...) | |||
} | |||
if args, ok := body.(map[string]string); ok { | |||
for k, v := range args { | |||
params[k] = AnyToString(v) | |||
} | |||
return HttpBuildQuery(params, sortAsc...) | |||
} | |||
if args, ok := body.(map[string]int); ok { | |||
for k, v := range args { | |||
params[k] = AnyToString(v) | |||
} | |||
return HttpBuildQuery(params, sortAsc...) | |||
} | |||
return AnyToString(body) | |||
} |
@@ -0,0 +1,22 @@ | |||
package zhios_o2o_business_utils | |||
import ( | |||
"os" | |||
"path" | |||
"strings" | |||
"time" | |||
) | |||
// 获取文件后缀 | |||
func FileExt(fname string) string { | |||
return strings.ToLower(strings.TrimLeft(path.Ext(fname), ".")) | |||
} | |||
func FilePutContents(fileName string, content string) { | |||
fd, _ := os.OpenFile("./tmp/"+fileName+".logs", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) | |||
fd_time := time.Now().Format("2006-01-02 15:04:05") | |||
fd_content := strings.Join([]string{"[", fd_time, "] ", content, "\n"}, "") | |||
buf := []byte(fd_content) | |||
fd.Write(buf) | |||
fd.Close() | |||
} |
@@ -0,0 +1,245 @@ | |||
package zhios_o2o_business_logx | |||
import ( | |||
"os" | |||
"strings" | |||
"time" | |||
"go.uber.org/zap" | |||
"go.uber.org/zap/zapcore" | |||
) | |||
type LogConfig struct { | |||
AppName string `yaml:"app_name" json:"app_name" toml:"app_name"` | |||
Level string `yaml:"level" json:"level" toml:"level"` | |||
StacktraceLevel string `yaml:"stacktrace_level" json:"stacktrace_level" toml:"stacktrace_level"` | |||
IsStdOut bool `yaml:"is_stdout" json:"is_stdout" toml:"is_stdout"` | |||
TimeFormat string `yaml:"time_format" json:"time_format" toml:"time_format"` // second, milli, nano, standard, iso, | |||
Encoding string `yaml:"encoding" json:"encoding" toml:"encoding"` // console, json | |||
Skip int `yaml:"skip" json:"skip" toml:"skip"` | |||
IsFileOut bool `yaml:"is_file_out" json:"is_file_out" toml:"is_file_out"` | |||
FileDir string `yaml:"file_dir" json:"file_dir" toml:"file_dir"` | |||
FileName string `yaml:"file_name" json:"file_name" toml:"file_name"` | |||
FileMaxSize int `yaml:"file_max_size" json:"file_max_size" toml:"file_max_size"` | |||
FileMaxAge int `yaml:"file_max_age" json:"file_max_age" toml:"file_max_age"` | |||
} | |||
var ( | |||
l *LogX = defaultLogger() | |||
conf *LogConfig | |||
) | |||
// default logger setting | |||
func defaultLogger() *LogX { | |||
conf = &LogConfig{ | |||
Level: "debug", | |||
StacktraceLevel: "error", | |||
IsStdOut: true, | |||
TimeFormat: "standard", | |||
Encoding: "console", | |||
Skip: 2, | |||
} | |||
writers := []zapcore.WriteSyncer{os.Stdout} | |||
lg, lv := newZapLogger(setLogLevel(conf.Level), setLogLevel(conf.StacktraceLevel), conf.Encoding, conf.TimeFormat, conf.Skip, zapcore.NewMultiWriteSyncer(writers...)) | |||
zap.RedirectStdLog(lg) | |||
return &LogX{logger: lg, atomLevel: lv} | |||
} | |||
// initial standard log, if you don't init, it will use default logger setting | |||
func InitDefaultLogger(cfg *LogConfig) { | |||
var writers []zapcore.WriteSyncer | |||
if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { | |||
writers = append(writers, os.Stdout) | |||
} | |||
if cfg.IsFileOut { | |||
writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) | |||
} | |||
lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) | |||
zap.RedirectStdLog(lg) | |||
if cfg.AppName != "" { | |||
lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 | |||
} | |||
l = &LogX{logger: lg, atomLevel: lv} | |||
} | |||
// create a new logger | |||
func NewLogger(cfg *LogConfig) *LogX { | |||
var writers []zapcore.WriteSyncer | |||
if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { | |||
writers = append(writers, os.Stdout) | |||
} | |||
if cfg.IsFileOut { | |||
writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) | |||
} | |||
lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) | |||
zap.RedirectStdLog(lg) | |||
if cfg.AppName != "" { | |||
lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 | |||
} | |||
return &LogX{logger: lg, atomLevel: lv} | |||
} | |||
// create a new zaplog logger | |||
func newZapLogger(level, stacktrace zapcore.Level, encoding, timeType string, skip int, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) { | |||
encCfg := zapcore.EncoderConfig{ | |||
TimeKey: "T", | |||
LevelKey: "L", | |||
NameKey: "N", | |||
CallerKey: "C", | |||
MessageKey: "M", | |||
StacktraceKey: "S", | |||
LineEnding: zapcore.DefaultLineEnding, | |||
EncodeCaller: zapcore.ShortCallerEncoder, | |||
EncodeDuration: zapcore.NanosDurationEncoder, | |||
EncodeLevel: zapcore.LowercaseLevelEncoder, | |||
} | |||
setTimeFormat(timeType, &encCfg) // set time type | |||
atmLvl := zap.NewAtomicLevel() // set level | |||
atmLvl.SetLevel(level) | |||
encoder := zapcore.NewJSONEncoder(encCfg) // 确定encoder格式 | |||
if encoding == "console" { | |||
encoder = zapcore.NewConsoleEncoder(encCfg) | |||
} | |||
return zap.New(zapcore.NewCore(encoder, output, atmLvl), zap.AddCaller(), zap.AddStacktrace(stacktrace), zap.AddCallerSkip(skip)), &atmLvl | |||
} | |||
// set log level | |||
func setLogLevel(lvl string) zapcore.Level { | |||
switch strings.ToLower(lvl) { | |||
case "panic": | |||
return zapcore.PanicLevel | |||
case "fatal": | |||
return zapcore.FatalLevel | |||
case "error": | |||
return zapcore.ErrorLevel | |||
case "warn", "warning": | |||
return zapcore.WarnLevel | |||
case "info": | |||
return zapcore.InfoLevel | |||
default: | |||
return zapcore.DebugLevel | |||
} | |||
} | |||
// set time format | |||
func setTimeFormat(timeType string, z *zapcore.EncoderConfig) { | |||
switch strings.ToLower(timeType) { | |||
case "iso": // iso8601 standard | |||
z.EncodeTime = zapcore.ISO8601TimeEncoder | |||
case "sec": // only for unix second, without millisecond | |||
z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { | |||
enc.AppendInt64(t.Unix()) | |||
} | |||
case "second": // unix second, with millisecond | |||
z.EncodeTime = zapcore.EpochTimeEncoder | |||
case "milli", "millisecond": // millisecond | |||
z.EncodeTime = zapcore.EpochMillisTimeEncoder | |||
case "nano", "nanosecond": // nanosecond | |||
z.EncodeTime = zapcore.EpochNanosTimeEncoder | |||
default: // standard format | |||
z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { | |||
enc.AppendString(t.Format("2006-01-02 15:04:05.000")) | |||
} | |||
} | |||
} | |||
func GetLevel() string { | |||
switch l.atomLevel.Level() { | |||
case zapcore.PanicLevel: | |||
return "panic" | |||
case zapcore.FatalLevel: | |||
return "fatal" | |||
case zapcore.ErrorLevel: | |||
return "error" | |||
case zapcore.WarnLevel: | |||
return "warn" | |||
case zapcore.InfoLevel: | |||
return "info" | |||
default: | |||
return "debug" | |||
} | |||
} | |||
func SetLevel(lvl string) { | |||
l.atomLevel.SetLevel(setLogLevel(lvl)) | |||
} | |||
// temporary add call skip | |||
func AddCallerSkip(skip int) *LogX { | |||
l.logger.WithOptions(zap.AddCallerSkip(skip)) | |||
return l | |||
} | |||
// permanent add call skip | |||
func AddDepth(skip int) *LogX { | |||
l.logger = l.logger.WithOptions(zap.AddCallerSkip(skip)) | |||
return l | |||
} | |||
// permanent add options | |||
func AddOptions(opts ...zap.Option) *LogX { | |||
l.logger = l.logger.WithOptions(opts...) | |||
return l | |||
} | |||
func AddField(k string, v interface{}) { | |||
l.logger.With(zap.Any(k, v)) | |||
} | |||
func AddFields(fields map[string]interface{}) *LogX { | |||
for k, v := range fields { | |||
l.logger.With(zap.Any(k, v)) | |||
} | |||
return l | |||
} | |||
// Normal log | |||
func Debug(e interface{}, args ...interface{}) error { | |||
return l.Debug(e, args...) | |||
} | |||
func Info(e interface{}, args ...interface{}) error { | |||
return l.Info(e, args...) | |||
} | |||
func Warn(e interface{}, args ...interface{}) error { | |||
return l.Warn(e, args...) | |||
} | |||
func Error(e interface{}, args ...interface{}) error { | |||
return l.Error(e, args...) | |||
} | |||
func Panic(e interface{}, args ...interface{}) error { | |||
return l.Panic(e, args...) | |||
} | |||
func Fatal(e interface{}, args ...interface{}) error { | |||
return l.Fatal(e, args...) | |||
} | |||
// Format logs | |||
func Debugf(format string, args ...interface{}) error { | |||
return l.Debugf(format, args...) | |||
} | |||
func Infof(format string, args ...interface{}) error { | |||
return l.Infof(format, args...) | |||
} | |||
func Warnf(format string, args ...interface{}) error { | |||
return l.Warnf(format, args...) | |||
} | |||
func Errorf(format string, args ...interface{}) error { | |||
return l.Errorf(format, args...) | |||
} | |||
func Panicf(format string, args ...interface{}) error { | |||
return l.Panicf(format, args...) | |||
} | |||
func Fatalf(format string, args ...interface{}) error { | |||
return l.Fatalf(format, args...) | |||
} | |||
func formatFieldMap(m FieldMap) []Field { | |||
var res []Field | |||
for k, v := range m { | |||
res = append(res, zap.Any(k, v)) | |||
} | |||
return res | |||
} |
@@ -0,0 +1,105 @@ | |||
package zhios_o2o_business_logx | |||
import ( | |||
"bytes" | |||
"io" | |||
"os" | |||
"path/filepath" | |||
"time" | |||
"gopkg.in/natefinch/lumberjack.v2" | |||
) | |||
// output interface | |||
type WriteSyncer interface { | |||
io.Writer | |||
Sync() error | |||
} | |||
// split writer | |||
func NewRollingFile(dir, filename string, maxSize, MaxAge int) WriteSyncer { | |||
s, err := os.Stat(dir) | |||
if err != nil || !s.IsDir() { | |||
os.RemoveAll(dir) | |||
if err := os.MkdirAll(dir, 0766); err != nil { | |||
panic(err) | |||
} | |||
} | |||
return newLumberjackWriteSyncer(&lumberjack.Logger{ | |||
Filename: filepath.Join(dir, filename), | |||
MaxSize: maxSize, // megabytes, MB | |||
MaxAge: MaxAge, // days | |||
LocalTime: true, | |||
Compress: false, | |||
}) | |||
} | |||
type lumberjackWriteSyncer struct { | |||
*lumberjack.Logger | |||
buf *bytes.Buffer | |||
logChan chan []byte | |||
closeChan chan interface{} | |||
maxSize int | |||
} | |||
func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer { | |||
ws := &lumberjackWriteSyncer{ | |||
Logger: l, | |||
buf: bytes.NewBuffer([]byte{}), | |||
logChan: make(chan []byte, 5000), | |||
closeChan: make(chan interface{}), | |||
maxSize: 1024, | |||
} | |||
go ws.run() | |||
return ws | |||
} | |||
func (l *lumberjackWriteSyncer) run() { | |||
ticker := time.NewTicker(1 * time.Second) | |||
for { | |||
select { | |||
case <-ticker.C: | |||
if l.buf.Len() > 0 { | |||
l.sync() | |||
} | |||
case bs := <-l.logChan: | |||
_, err := l.buf.Write(bs) | |||
if err != nil { | |||
continue | |||
} | |||
if l.buf.Len() > l.maxSize { | |||
l.sync() | |||
} | |||
case <-l.closeChan: | |||
l.sync() | |||
return | |||
} | |||
} | |||
} | |||
func (l *lumberjackWriteSyncer) Stop() { | |||
close(l.closeChan) | |||
} | |||
func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) { | |||
b := make([]byte, len(bs)) | |||
for i, c := range bs { | |||
b[i] = c | |||
} | |||
l.logChan <- b | |||
return 0, nil | |||
} | |||
func (l *lumberjackWriteSyncer) Sync() error { | |||
return nil | |||
} | |||
func (l *lumberjackWriteSyncer) sync() error { | |||
defer l.buf.Reset() | |||
_, err := l.Logger.Write(l.buf.Bytes()) | |||
if err != nil { | |||
return err | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,192 @@ | |||
package zhios_o2o_business_logx | |||
import ( | |||
"errors" | |||
"fmt" | |||
"strconv" | |||
"go.uber.org/zap" | |||
) | |||
type LogX struct { | |||
logger *zap.Logger | |||
atomLevel *zap.AtomicLevel | |||
} | |||
type Field = zap.Field | |||
type FieldMap map[string]interface{} | |||
// 判断其他类型--start | |||
func getFields(msg string, format bool, args ...interface{}) (string, []Field) { | |||
var str []interface{} | |||
var fields []zap.Field | |||
if len(args) > 0 { | |||
for _, v := range args { | |||
if f, ok := v.(Field); ok { | |||
fields = append(fields, f) | |||
} else if f, ok := v.(FieldMap); ok { | |||
fields = append(fields, formatFieldMap(f)...) | |||
} else { | |||
str = append(str, AnyToString(v)) | |||
} | |||
} | |||
if format { | |||
return fmt.Sprintf(msg, str...), fields | |||
} | |||
str = append([]interface{}{msg}, str...) | |||
return fmt.Sprintln(str...), fields | |||
} | |||
return msg, []Field{} | |||
} | |||
func (l *LogX) Debug(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Debug(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Info(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Info(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Warn(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Warn(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Error(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Error(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) DPanic(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.DPanic(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Panic(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Panic(msg, field...) | |||
} | |||
return e | |||
} | |||
func (l *LogX) Fatal(s interface{}, args ...interface{}) error { | |||
es, e := checkErr(s) | |||
if es != "" { | |||
msg, field := getFields(es, false, args...) | |||
l.logger.Fatal(msg, field...) | |||
} | |||
return e | |||
} | |||
func checkErr(s interface{}) (string, error) { | |||
switch e := s.(type) { | |||
case error: | |||
return e.Error(), e | |||
case string: | |||
return e, errors.New(e) | |||
case []byte: | |||
return string(e), nil | |||
default: | |||
return "", nil | |||
} | |||
} | |||
func (l *LogX) LogError(err error) error { | |||
return l.Error(err.Error()) | |||
} | |||
func (l *LogX) Debugf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Debug(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Infof(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Info(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Warnf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Warn(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Errorf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Error(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) DPanicf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.DPanic(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Panicf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Panic(s, f...) | |||
return errors.New(s) | |||
} | |||
func (l *LogX) Fatalf(msg string, args ...interface{}) error { | |||
s, f := getFields(msg, true, args...) | |||
l.logger.Fatal(s, f...) | |||
return errors.New(s) | |||
} | |||
func AnyToString(raw interface{}) string { | |||
switch i := raw.(type) { | |||
case []byte: | |||
return string(i) | |||
case int: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int64: | |||
return strconv.FormatInt(i, 10) | |||
case float32: | |||
return strconv.FormatFloat(float64(i), 'f', 2, 64) | |||
case float64: | |||
return strconv.FormatFloat(i, 'f', 2, 64) | |||
case uint: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case uint64: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int8: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int16: | |||
return strconv.FormatInt(int64(i), 10) | |||
case int32: | |||
return strconv.FormatInt(int64(i), 10) | |||
case string: | |||
return i | |||
case error: | |||
return i.Error() | |||
} | |||
return fmt.Sprintf("%#v", raw) | |||
} |
@@ -0,0 +1,23 @@ | |||
package zhios_o2o_business_utils | |||
import ( | |||
"encoding/json" | |||
) | |||
func Serialize(data interface{}) []byte { | |||
res, err := json.Marshal(data) | |||
if err != nil { | |||
return []byte{} | |||
} | |||
return res | |||
} | |||
func Unserialize(b []byte, dst interface{}) { | |||
if err := json.Unmarshal(b, dst); err != nil { | |||
dst = nil | |||
} | |||
} | |||
func SerializeStr(data interface{}, arg ...interface{}) string { | |||
return string(Serialize(data)) | |||
} |