@@ -0,0 +1,46 @@ | |||||
# 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 | |||||
*.log | |||||
.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 | |||||
/tmp/* | |||||
.idea/* | |||||
/.idea/modules.xml |
@@ -0,0 +1,34 @@ | |||||
# 多重构建,减少镜像大小 | |||||
# 构建:使用golang:1.18版本 | |||||
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build | |||||
# 容器环境变量添加,会覆盖默认的变量值 | |||||
ENV GO111MODULE=on | |||||
ENV GOPROXY=https://goproxy.cn,direct | |||||
ENV TZ="Asia/Shanghai" | |||||
# 设置工作区 | |||||
WORKDIR /go/release | |||||
# 把全部文件添加到/go/release目录 | |||||
ADD . . | |||||
# 编译:把main.go编译成可执行的二进制文件,命名为zyos | |||||
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -tags netgo -ldflags="-s -w" -installsuffix cgo -o zyos main.go | |||||
FROM ubuntu:xenial as prod | |||||
LABEL maintainer="wuhanqin" | |||||
ENV TZ="Asia/Shanghai" | |||||
COPY static/html static/html | |||||
# 时区纠正 | |||||
RUN rm -f /etc/localtime \ | |||||
&& ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ | |||||
&& echo "Asia/Shanghai" > /etc/timezone | |||||
# 在build阶段复制可执行的go二进制文件app | |||||
COPY --from=build /go/release/zyos ./zyos | |||||
COPY --from=build /go/release/etc/cfg.yml /var/zyos/cfg.yml | |||||
# 启动服务 | |||||
CMD ["./zyos","-c","/var/zyos/cfg.yml"] | |||||
@@ -0,0 +1,34 @@ | |||||
# 多重构建,减少镜像大小 | |||||
# 构建:使用golang:1.15版本 | |||||
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build | |||||
# 容器环境变量添加,会覆盖默认的变量值 | |||||
ENV GO111MODULE=on | |||||
ENV GOPROXY=https://goproxy.cn,direct | |||||
ENV TZ="Asia/Shanghai" | |||||
# 设置工作区 | |||||
WORKDIR /go/release | |||||
# 把全部文件添加到/go/release目录 | |||||
ADD . . | |||||
# 编译:把main.go编译成可执行的二进制文件,命名为zyos | |||||
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -tags netgo -ldflags="-s -w" -installsuffix cgo -o zhimeng_task cmd/task/main.go | |||||
FROM ubuntu:xenial as prod | |||||
LABEL maintainer="wuhanqin" | |||||
ENV TZ="Asia/Shanghai" | |||||
COPY static/html static/html | |||||
# 时区纠正 | |||||
RUN rm -f /etc/localtime \ | |||||
&& ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ | |||||
&& echo "Asia/Shanghai" > /etc/timezone | |||||
# 在build阶段复制可执行的go二进制文件app | |||||
COPY --from=build /go/release/zhimeng_task ./zhimeng_task | |||||
COPY --from=build /go/release/etc/task.yml /var/zyos/task.yml | |||||
# 启动服务 | |||||
CMD ["./zhimeng_task","-c","/var/zyos/task.yml"] | |||||
@@ -0,0 +1,32 @@ | |||||
.PHONY: build clean tool lint help | |||||
APP=applet | |||||
all: build | |||||
build: | |||||
go build -o ./bin/$(APP) ./cmd/main.go | |||||
lite: | |||||
go build -ldflags "-s -w" -o ./bin/$(APP) ./cmd/main.go | |||||
install: | |||||
#@go build -v . | |||||
go install ./cmd/... | |||||
tool: | |||||
go vet ./...; true | |||||
gofmt -w . | |||||
lint: | |||||
golint ./... | |||||
clean: | |||||
rm -rf go-gin-example | |||||
go clean -i . | |||||
help: | |||||
@echo "make: compile packages and dependencies" | |||||
@echo "make tool: run specified go tool" | |||||
@echo "make lint: golint ./..." | |||||
@echo "make clean: remove object files and cached files" |
@@ -0,0 +1,48 @@ | |||||
# applet | |||||
## 要看 nginx.conf 和 wap conf | |||||
## 层级介绍 | |||||
- hdl 做接收数据的报错, 数据校验 | |||||
- svc 做数据处理的报错, 数据转换 | |||||
- lib 只抛出错误给hdl或者svc进行处理, 不做数据校验 | |||||
- db 可以处理db错误,其它错误返回给svc进行处理 | |||||
- mw 中间件 | |||||
- md 结构体 | |||||
#### 介绍 | |||||
基于gin的接口小程序 | |||||
#### 软件架构 | |||||
软件架构说明 | |||||
#### 安装教程 | |||||
1. xxxx | |||||
2. xxxx | |||||
3. xxxx | |||||
#### 使用说明 | |||||
1. xxxx | |||||
2. xxxx | |||||
3. xxxx | |||||
#### 参与贡献 | |||||
1. Fork 本仓库 | |||||
2. 新建 Feat_xxx 分支 | |||||
3. 提交代码 | |||||
4. 新建 Pull Request | |||||
## swagger | |||||
``` | |||||
// 参考:https://segmentfault.com/a/1190000013808421 | |||||
// 安装命令行 | |||||
go get -u github.com/swaggo/swag/cmd/swag | |||||
// 生成 | |||||
swag init | |||||
``` |
@@ -0,0 +1,49 @@ | |||||
package cfg | |||||
import ( | |||||
"time" | |||||
) | |||||
type Config struct { | |||||
Debug bool `yaml:"debug"` | |||||
Prd bool `yaml:"prd"` | |||||
SrvAddr string `yaml:"srv_addr"` | |||||
RedisAddr string `yaml:"redis_addr"` | |||||
DB DBCfg `yaml:"db"` | |||||
ZhimengDB DBCfg `yaml:"zhimeng_db"` | |||||
Log LogCfg `yaml:"log"` | |||||
ES ESCfg `yaml:"es"` | |||||
} | |||||
// 数据库配置结构体 | |||||
type DBCfg struct { | |||||
Host string `yaml:"host"` //ip及端口 | |||||
Name string `yaml:"name"` //库名 | |||||
User string `yaml:"user"` //用户 | |||||
Psw string `yaml:"psw"` //密码 | |||||
ShowLog bool `yaml:"show_log"` //是否显示SQL语句 | |||||
MaxLifetime time.Duration `yaml:"max_lifetime"` | |||||
MaxOpenConns int `yaml:"max_open_conns"` | |||||
MaxIdleConns int `yaml:"max_idle_conns"` | |||||
Path string `yaml:"path"` //日志文件存放路径 | |||||
} | |||||
// 日志配置结构体 | |||||
type LogCfg struct { | |||||
AppName string `yaml:"app_name" ` | |||||
Level string `yaml:"level"` | |||||
IsStdOut bool `yaml:"is_stdout"` | |||||
TimeFormat string `yaml:"time_format"` // second, milli, nano, standard, iso, | |||||
Encoding string `yaml:"encoding"` // console, json | |||||
IsFileOut bool `yaml:"is_file_out"` | |||||
FileDir string `yaml:"file_dir"` | |||||
FileName string `yaml:"file_name"` | |||||
FileMaxSize int `yaml:"file_max_size"` | |||||
FileMaxAge int `yaml:"file_max_age"` | |||||
} | |||||
type ESCfg struct { | |||||
Url string `yaml:"url"` | |||||
User string `yaml:"user"` | |||||
Pwd string `yaml:"pwd"` | |||||
} |
@@ -0,0 +1,3 @@ | |||||
package cfg | |||||
// 统一管理缓存 |
@@ -0,0 +1,9 @@ | |||||
package cfg | |||||
import ( | |||||
"applet/app/utils/cache" | |||||
) | |||||
func InitCache() { | |||||
cache.NewRedis(RedisAddr) | |||||
} |
@@ -0,0 +1,50 @@ | |||||
package cfg | |||||
import ( | |||||
"flag" | |||||
"io/ioutil" | |||||
"gopkg.in/yaml.v2" | |||||
) | |||||
// 配置文件数据,全局变量 | |||||
var ( | |||||
Debug bool | |||||
Prd bool | |||||
SrvAddr string | |||||
RedisAddr string | |||||
DB *DBCfg | |||||
ZhimengDB *DBCfg | |||||
Log *LogCfg | |||||
ES *ESCfg | |||||
) | |||||
// 初始化配置文件,将cfg.yml读入到内存 | |||||
func InitCfg() { | |||||
//用指定的名称、默认值、使用信息注册一个string类型flag。 | |||||
path := flag.String("c", "etc/cfg.yml", "config file") | |||||
//解析命令行参数写入注册的flag里。 | |||||
//解析之后,flag的值可以直接使用。 | |||||
flag.Parse() | |||||
var ( | |||||
c []byte | |||||
err error | |||||
conf *Config | |||||
) | |||||
if c, err = ioutil.ReadFile(*path); err != nil { | |||||
panic(err) | |||||
} | |||||
//yaml.Unmarshal反序列化映射到Config | |||||
if err = yaml.Unmarshal(c, &conf); err != nil { | |||||
panic(err) | |||||
} | |||||
//数据读入内存 | |||||
Prd = conf.Prd | |||||
Debug = conf.Debug | |||||
DB = &conf.DB | |||||
ZhimengDB = &conf.ZhimengDB | |||||
ES = &conf.ES | |||||
Log = &conf.Log | |||||
RedisAddr = conf.RedisAddr | |||||
SrvAddr = conf.SrvAddr | |||||
} |
@@ -0,0 +1,13 @@ | |||||
package cfg | |||||
import ( | |||||
"applet/app/utils" | |||||
"code.fnuoos.com/go_rely_warehouse/zyos_go_es.git/es" | |||||
"encoding/json" | |||||
) | |||||
func InitEs() { | |||||
data, _ := json.Marshal(ES) | |||||
utils.FilePutContents("init_es", string(data)) | |||||
es.Init(ES.Url, ES.User, ES.Pwd) | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package cfg | |||||
import "applet/app/utils/logx" | |||||
func InitLog() { | |||||
logx.InitDefaultLogger(&logx.LogConfig{ | |||||
AppName: Log.AppName, | |||||
Level: Log.Level, | |||||
StacktraceLevel: "error", | |||||
IsStdOut: Log.IsStdOut, | |||||
TimeFormat: Log.TimeFormat, | |||||
Encoding: Log.Encoding, | |||||
IsFileOut: Log.IsFileOut, | |||||
FileDir: Log.FileDir, | |||||
FileName: Log.FileName, | |||||
FileMaxSize: Log.FileMaxSize, | |||||
FileMaxAge: Log.FileMaxAge, | |||||
Skip: 2, | |||||
}) | |||||
} |
@@ -0,0 +1,44 @@ | |||||
package cfg | |||||
import ( | |||||
"flag" | |||||
"io/ioutil" | |||||
"gopkg.in/yaml.v2" | |||||
mc "applet/app/utils/cache/cache" | |||||
"applet/app/utils/logx" | |||||
) | |||||
func InitTaskCfg() { | |||||
path := flag.String("c", "etc/task.yml", "config file") | |||||
flag.Parse() | |||||
var ( | |||||
c []byte | |||||
err error | |||||
conf *Config | |||||
) | |||||
if c, err = ioutil.ReadFile(*path); err != nil { | |||||
panic(err) | |||||
} | |||||
if err = yaml.Unmarshal(c, &conf); err != nil { | |||||
panic(err) | |||||
} | |||||
Prd = conf.Prd | |||||
Debug = conf.Debug | |||||
DB = &conf.DB | |||||
ZhimengDB = &conf.ZhimengDB | |||||
Log = &conf.Log | |||||
RedisAddr = conf.RedisAddr | |||||
ES = &conf.ES | |||||
} | |||||
var MemCache mc.Cache | |||||
func InitMemCache() { | |||||
var err error | |||||
MemCache, err = mc.NewCache("memory", `{"interval":60}`) | |||||
if err != nil { | |||||
logx.Fatal(err.Error()) | |||||
} | |||||
} |
@@ -0,0 +1,114 @@ | |||||
package db | |||||
import ( | |||||
"database/sql" | |||||
"fmt" | |||||
"os" | |||||
_ "github.com/go-sql-driver/mysql" | |||||
"xorm.io/xorm" | |||||
"xorm.io/xorm/log" | |||||
"applet/app/cfg" | |||||
"applet/app/utils/logx" | |||||
) | |||||
var Db *xorm.Engine | |||||
func InitDB(c *cfg.DBCfg) error { | |||||
var err error | |||||
if Db, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", c.User, c.Psw, c.Host, c.Name)); err != nil { | |||||
return err | |||||
} | |||||
Db.SetConnMaxLifetime(c.MaxLifetime) | |||||
Db.SetMaxOpenConns(c.MaxOpenConns) | |||||
Db.SetMaxIdleConns(c.MaxIdleConns) | |||||
if err = Db.Ping(); err != nil { | |||||
return err | |||||
} | |||||
if c.ShowLog { | |||||
Db.ShowSQL(true) | |||||
Db.Logger().SetLevel(0) | |||||
f, err := os.OpenFile(c.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777) | |||||
if err != nil { | |||||
os.RemoveAll(c.Path) | |||||
if f, err = os.OpenFile(c.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777); err != nil { | |||||
return err | |||||
} | |||||
} | |||||
logger := log.NewSimpleLogger(f) | |||||
logger.ShowSQL(true) | |||||
Db.SetLogger(logger) | |||||
} | |||||
return nil | |||||
} | |||||
/********************************************* 公用方法 *********************************************/ | |||||
// DbInsertBatch 数据批量插入 | |||||
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 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 | |||||
} | |||||
func QueryNativeStringSess(sess *xorm.Session, sql string, args ...interface{}) ([]map[string]string, error) { | |||||
results, err := sess.SQL(sql, args...).QueryString() | |||||
return results, err | |||||
} | |||||
// CommonInsert 插入一条或多条数据 | |||||
func CommonInsert(Db *xorm.Engine, data interface{}) (int64, error) { | |||||
row, err := Db.Insert(data) | |||||
return row, err | |||||
} | |||||
// UpdateComm 根据主键更新 | |||||
func UpdateComm(Db *xorm.Engine, id interface{}, model interface{}) (int64, error) { | |||||
row, err := Db.ID(id).Update(model) | |||||
return row, err | |||||
} | |||||
// InsertOneComm 插入一条数据 | |||||
func InsertOneComm(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 { | |||||
_ = logx.Warn(err) | |||||
return nil, false, err | |||||
} | |||||
return model, has, nil | |||||
} | |||||
// ExecuteOriginalSql 执行原生sql | |||||
func ExecuteOriginalSql(Db *xorm.Engine, sql string) (sql.Result, error) { | |||||
result, err := Db.Exec(sql) | |||||
if err != nil { | |||||
_ = logx.Warn(err) | |||||
return nil, err | |||||
} | |||||
return result, nil | |||||
} | |||||
// InsertCommWithSession common insert | |||||
func InsertCommWithSession(session *xorm.Session, model interface{}) (int64, error) { | |||||
row, err := session.InsertOne(model) | |||||
return row, err | |||||
} |
@@ -0,0 +1,132 @@ | |||||
package db | |||||
import ( | |||||
"applet/app/db/model" | |||||
"applet/app/md" | |||||
"applet/app/utils/cache" | |||||
"applet/app/utils/logx" | |||||
"fmt" | |||||
"xorm.io/xorm" | |||||
) | |||||
type SysCfgDb struct { | |||||
Db *xorm.Engine `json:"db"` | |||||
} | |||||
func (sysCfgDb *SysCfgDb) Set() { // set方法 | |||||
sysCfgDb.Db = Db | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgGetAll() (*[]model.SysCfg, error) { | |||||
var cfgList []model.SysCfg | |||||
if err := sysCfgDb.Db.Cols("k,v,memo").Find(&cfgList); err != nil { | |||||
return nil, logx.Error(err) | |||||
} | |||||
return &cfgList, nil | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgGetCron() (*[]model.SysCfg, error) { | |||||
var cfgList []model.SysCfg | |||||
if err := sysCfgDb.Db.Where("`k` LIKE 'data_collect_cron\\_%' AND v != ''").Cols("k,v,memo").Find(&cfgList); err != nil { | |||||
return nil, logx.Error(err) | |||||
} | |||||
return &cfgList, nil | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgGetOneNoDataNoErr(key string) (*model.SysCfg, error) { | |||||
var cfgList model.SysCfg | |||||
has, err := sysCfgDb.Db.Where("`k`=?", key).Get(&cfgList) | |||||
if err != nil { | |||||
return nil, logx.Error(err) | |||||
} | |||||
if !has { | |||||
return nil, nil | |||||
} | |||||
return &cfgList, nil | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgGetOne(key string) (*model.SysCfg, error) { | |||||
var cfgList model.SysCfg | |||||
if has, err := sysCfgDb.Db.Where("`k`=?", key).Get(&cfgList); err != nil || has == false { | |||||
return nil, logx.Error(err) | |||||
} | |||||
return &cfgList, nil | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgGetOneData(key string) string { | |||||
var cfgList model.SysCfg | |||||
if has, err := sysCfgDb.Db.Where("`k`=?", key).Get(&cfgList); err != nil || has == false { | |||||
return "" | |||||
} | |||||
return cfgList.V | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgInsert(key, val, memo string) bool { | |||||
cfg := model.SysCfg{K: key, V: val, Memo: memo} | |||||
_, err := sysCfgDb.Db.InsertOne(&cfg) | |||||
if err != nil { | |||||
logx.Error(err) | |||||
return false | |||||
} | |||||
return true | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgUpdate(key, val string) bool { | |||||
cfg := model.SysCfg{K: key, V: val} | |||||
_, err := Db.Where("`key`=?", key).Cols("val").Update(&cfg) | |||||
if err != nil { | |||||
logx.Error(err) | |||||
return false | |||||
} | |||||
sysCfgDb.SysCfgDel(key) | |||||
return true | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgGetWithDb(HKey string) string { | |||||
cacheKey := fmt.Sprintf(md.AppCfgCacheKey, HKey[0:1]) | |||||
get, err := cache.HGetString(cacheKey, HKey) | |||||
if err != nil || get == "" { | |||||
cfg, err := sysCfgDb.SysCfgGetOne(HKey) | |||||
if err != nil || cfg == nil { | |||||
_ = logx.Error(err) | |||||
return "" | |||||
} | |||||
// key是否存在 | |||||
cacheKeyExist := false | |||||
if cache.Exists(cacheKey) { | |||||
cacheKeyExist = true | |||||
} | |||||
// 设置缓存 | |||||
_, err = cache.HSet(cacheKey, HKey, cfg.V) | |||||
if err != nil { | |||||
_ = logx.Error(err) | |||||
return "" | |||||
} | |||||
if !cacheKeyExist { // 如果是首次设置 设置过期时间 | |||||
_, err := cache.Expire(cacheKey, md.CfgCacheTime) | |||||
if err != nil { | |||||
_ = logx.Error(err) | |||||
return "" | |||||
} | |||||
} | |||||
return cfg.V | |||||
} | |||||
return get | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgDel(HKey string) error { | |||||
cacheKey := fmt.Sprintf(md.AppCfgCacheKey, HKey[0:1]) | |||||
_, err := cache.HDel(cacheKey, HKey) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return nil | |||||
} | |||||
func (sysCfgDb *SysCfgDb) SysCfgFindWithDb(keys ...string) map[string]string { | |||||
res := map[string]string{} | |||||
for _, v := range keys { | |||||
val := sysCfgDb.SysCfgGetWithDb(v) | |||||
res[v] = val | |||||
} | |||||
return res | |||||
} |
@@ -0,0 +1,7 @@ | |||||
package model | |||||
type SysCfg struct { | |||||
K string `json:"k" xorm:"VARCHAR(255)"` | |||||
V string `json:"v" xorm:"TEXT"` | |||||
Memo string `json:"memo" xorm:"VARCHAR(255)"` | |||||
} |
@@ -0,0 +1,37 @@ | |||||
package offical | |||||
import ( | |||||
"applet/app/db" | |||||
"applet/app/db/model" | |||||
) | |||||
func MasterListCfgGetOneData(uid, key string) string { | |||||
var cfgList model.MasterListCfg | |||||
has, err := db.Db.Where("`k`=? and uid=?", key, uid).Get(&cfgList) | |||||
if err != nil { | |||||
return "" | |||||
} | |||||
if has == false { | |||||
cfgList = model.MasterListCfg{Uid: uid, K: key} | |||||
db.Db.InsertOne(&cfgList) | |||||
} | |||||
return cfgList.V | |||||
} | |||||
func MasterListCfgGetKeyAll(key string) *[]model.MasterListCfg { | |||||
var cfgList []model.MasterListCfg | |||||
err := db.Db.Where("`k`=? ", key).Find(&cfgList) | |||||
if err != nil { | |||||
return nil | |||||
} | |||||
return &cfgList | |||||
} | |||||
func MasterListCfgSave(uid, key, val string) { | |||||
var cfgList model.MasterListCfg | |||||
has, err := db.Db.Where("`k`=? and uid=?", key, uid).Get(&cfgList) | |||||
if err != nil || has == false { | |||||
return | |||||
} | |||||
cfgList.V = val | |||||
db.Db.Where("`k`=? and uid=?", key, uid).Update(&cfgList) | |||||
return | |||||
} |
@@ -0,0 +1,15 @@ | |||||
package offical | |||||
import ( | |||||
"applet/app/db" | |||||
"applet/app/db/official/model" | |||||
) | |||||
func GetUserAppList(uid string) *model.UserAppList { | |||||
var data model.UserAppList | |||||
get, err := db.Db.Where("uuid=?", uid).Get(&data) | |||||
if get == false || err != nil { | |||||
return nil | |||||
} | |||||
return &data | |||||
} |
@@ -0,0 +1,9 @@ | |||||
package model | |||||
type MasterListCfg struct { | |||||
K string `json:"k" xorm:"not null VARCHAR(255)"` | |||||
V string `json:"v" xorm:"TEXT"` | |||||
Memo string `json:"memo" xorm:"VARCHAR(255)"` | |||||
Uid string `json:"uid" xorm:"comment('0是官方') VARCHAR(255)"` | |||||
Id int `json:"id" xorm:"not null pk autoincr INT(11)"` | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package model | |||||
type UserAppList struct { | |||||
Id int `json:"id" xorm:"not null pk autoincr INT(11)"` | |||||
Uuid int `json:"uuid" xorm:"not null comment('masterId') INT(10)"` | |||||
Uid int `json:"uid" xorm:"not null comment('用户ID') INT(10)"` | |||||
AppId int `json:"app_id" xorm:"not null comment('应用ID') INT(10)"` | |||||
PlanId string `json:"plan_id" xorm:"not null default '' comment('套餐ID') VARCHAR(100)"` | |||||
Expire int `json:"expire" xorm:"not null default 0 comment('过期时间') INT(10)"` | |||||
Name string `json:"name" xorm:"not null default '' comment('应用主名称') VARCHAR(32)"` | |||||
Icon string `json:"icon" xorm:"not null default '' comment('应用主图标') VARCHAR(250)"` | |||||
CreateTime int `json:"create_time" xorm:"not null default 0 comment('初次激活时间') INT(10)"` | |||||
RenewTime int `json:"renew_time" xorm:"not null default 0 comment('上次续费时间') INT(10)"` | |||||
Domain string `json:"domain" xorm:"not null default '' comment('域名') index VARCHAR(110)"` | |||||
DomainAlias string `json:"domain_alias" xorm:"not null default '' comment('域名别名') index VARCHAR(110)"` | |||||
Platform string `json:"platform" xorm:"not null default '' comment('平台信息 ios,android,applet') VARCHAR(100)"` | |||||
Info string `json:"info" xorm:"comment('平台名称如ios.name.#ddd;') TEXT"` | |||||
PayMode int `json:"pay_mode" xorm:"not null default 1 comment('付费模式,0授信,1付款') TINYINT(1)"` | |||||
Price float32 `json:"price" xorm:"not null default 0.00 comment('应用价格') FLOAT(10,2)"` | |||||
PricePay float32 `json:"price_pay" xorm:"not null default 0.00 comment('实际付款价格') FLOAT(10,2)"` | |||||
OfficialPrice float32 `json:"official_price" xorm:"not null default 0.00 comment('应用价格') FLOAT(10,2)"` | |||||
OfficialPricePay float32 `json:"official_price_pay" xorm:"not null default 0.00 comment('实际付款价格') FLOAT(10,2)"` | |||||
State int `json:"state" xorm:"not null default 0 comment('0未创建,1正常,2停用,3过期') TINYINT(1)"` | |||||
DeleteAt int `json:"delete_at" xorm:"not null default 0 TINYINT(1)"` | |||||
CustomAndroidCount int `json:"custom_android_count" xorm:"default 0 comment('客户端安卓包名重置次数') INT(11)"` | |||||
CustomIosCount int `json:"custom_ios_count" xorm:"default 0 comment('客户端ios包名重置次数') INT(11)"` | |||||
StoreAndroidCount int `json:"store_android_count" xorm:"default 0 comment('商家端安卓包名重置次数') INT(11)"` | |||||
StoreIosCount int `json:"store_ios_count" xorm:"default 0 comment('商家端ios包名重置次数') INT(11)"` | |||||
SmsPlatform string `json:"sms_platform" xorm:"default 'mob' comment('mob ljioe联江') VARCHAR(255)"` | |||||
IsClose int `json:"is_close" xorm:"default 0 comment('是否关闭') INT(1)"` | |||||
Puid int `json:"puid" xorm:"default 0 comment('') INT(11)"` | |||||
StoreRateInfo string `json:"store_rate_info" xorm:"comment('付呗商品进件费率') TEXT"` | |||||
} |
@@ -0,0 +1,238 @@ | |||||
package e | |||||
const ( | |||||
// 200 因为部分第三方接口不能返回错误头,因此在此定义部分错误 | |||||
ERR_FILE_SAVE = 200001 | |||||
// 400 系列 | |||||
ERR_BAD_REQUEST = 400000 | |||||
ERR_INVALID_ARGS = 400001 | |||||
ERR_API_RESPONSE = 400002 | |||||
ERR_NO_DATA = 400003 | |||||
ERR_MOBILE_NIL = 400004 | |||||
ERR_MOBILE_MATH = 400005 | |||||
ERR_FILE_EXT = 400006 | |||||
ERR_FILE_MAX_SIZE = 400007 | |||||
ERR_SIGN = 400008 | |||||
ERR_PASSWORD_MATH = 400009 | |||||
ERR_PROVIDER_RESPONSE = 400010 | |||||
ERR_AES_ENCODE = 400011 | |||||
ERR_ADMIN_API = 400012 | |||||
ERR_QINIUAPI_RESPONSE = 400013 | |||||
ERR_URL_TURNCHAIN = 400014 | |||||
ERR_TOKEN_EXPIRE = 401010 | |||||
// 401 未授权 | |||||
ERR_UNAUTHORIZED = 401000 | |||||
ERR_NOT_AUTH = 401001 | |||||
ERR_SMS_AUTH = 401002 | |||||
ERR_TOKEN_AUTH = 401003 | |||||
ERR_TOKEN_FORMAT = 401004 | |||||
ERR_TOKEN_GEN = 401005 | |||||
ERR_CACHE_SET = 401006 | |||||
// 403 禁止 | |||||
ERR_FORBIDEN = 403000 | |||||
ERR_PLATFORM = 403001 | |||||
ERR_MOBILE_EXIST = 403002 | |||||
ERR_USER_NO_EXIST = 403003 | |||||
ERR_MOBILE_NO_EXIST = 403004 | |||||
ERR_FORBIDEN_VALID = 403005 | |||||
ERR_RELATE_ERR = 403006 | |||||
ERR_REPEAT_RELATE = 403007 | |||||
ERR_MOB_FORBIDEN = 403008 | |||||
ERR_MOB_SMS_NO_AVA = 403009 | |||||
ERR_USER_IS_REG = 403010 | |||||
ERR_MASTER_ID = 403011 | |||||
ERR_CASH_OUT_TIME = 403012 | |||||
ERR_CASH_OUT_FEE = 403013 | |||||
ERR_CASH_OUT_USER_NOT_FOUND = 403014 | |||||
ERR_CASH_OUT_FAIL = 403015 | |||||
ERR_CASH_OUT_TIMES = 403016 | |||||
ERR_CASH_OUT_MINI = 403017 | |||||
ERR_CASH_OUT_MUT = 403018 | |||||
ERR_CASH_OUT_NOT_DECIMAL = 403019 | |||||
ERR_CASH_OUT_NOT_DAY_AVA = 403020 | |||||
ERR_USER_LEVEL_PAY_CHECK_TASK_NO_DONE = 403021 | |||||
ERR_USER_LEVEL_PAY_CHECK_NO_CROSS = 403022 | |||||
ERR_USER_LEVEL_ORD_EXP = 403023 | |||||
ERR_IS_BIND_THIRDPARTY = 403024 | |||||
ERR_USER_LEVEL_UPDATE_CHECK_TASK_NO_DONE = 403025 | |||||
ERR_USER_LEVEL_UPDATE_CHECK_NOT_FOUND_ORDER = 403026 | |||||
ERR_USER_LEVEL_UPDATE_REPEAT = 403027 | |||||
ERR_USER_NO_ACTIVE = 403028 | |||||
ERR_USER_IS_BAN = 403029 | |||||
ERR_ALIPAY_SETTING = 403030 | |||||
ERR_ALIPAY_ORDERTYPE = 403031 | |||||
ERR_CLIPBOARD_UNSUP = 403032 | |||||
ERR_SYSUNION_CONFIG = 403033 | |||||
ERR_WECAHT_MINI = 403034 | |||||
ERR_WECAHT_MINI_CACHE = 403035 | |||||
ERR_WECAHT_MINI_DECODE = 403036 | |||||
ERR_WECHAT_MINI_ACCESSTOKEN = 403037 | |||||
ERR_CURRENT_VIP_LEVEL_AUDITING = 403038 | |||||
ERR_LEVEL_RENEW_SHOULD_KEEP_CURRENT = 403039 | |||||
ERR_LEVEL_UPGRADE_APPLY_AUDITTING = 403040 | |||||
ERR_LEVEL_TASK_PAY_TYPE = 403041 | |||||
ERR_BALANCE_NOT_ENOUGH = 403042 | |||||
ERR_ADMIN_PUSH = 403043 | |||||
ERR_PLAN = 403044 | |||||
ERR_MOB_CONFIG = 403045 | |||||
ERR_BAlANCE_PAY_ORDERTYPE = 403046 | |||||
ERR_PHONE_EXISTED = 403047 | |||||
ERR_NOT_RESULT = 403048 | |||||
ERR_REVIEW = 403049 | |||||
ERR_USER_LEVEL_HAS_PAID = 403050 | |||||
ERR_USER_BIND_OWN = 403051 | |||||
ERR_PARENTUID_ERR = 403052 | |||||
ERR_USER_DEL = 403053 | |||||
ERR_SEARCH_ERR = 403054 | |||||
ERR_LEVEL_REACH_TOP = 403055 | |||||
ERR_USER_CHECK_ERR = 403056 | |||||
ERR_PASSWORD_ERR = 403057 | |||||
// 404 | |||||
ERR_USER_NOTFOUND = 404001 | |||||
ERR_SUP_NOTFOUND = 404002 | |||||
ERR_LEVEL_MAP = 404003 | |||||
ERR_MOD_NOTFOUND = 404004 | |||||
ERR_CLIPBOARD_PARSE = 404005 | |||||
ERR_NOT_FAN = 404006 | |||||
ERR_USER_LEVEL = 404007 | |||||
ERR_LACK_PAY_CFG = 404008 | |||||
ERR_NOT_LEVEL_TASK = 404009 | |||||
ERR_ITEM_NOT_FOUND = 404010 | |||||
ERR_WX_CHECKFILE_NOTFOUND = 404011 | |||||
// 429 请求频繁 | |||||
ERR_TOO_MANY_REQUESTS = 429000 | |||||
// 500 系列 | |||||
ERR = 500000 | |||||
ERR_UNMARSHAL = 500001 | |||||
ERR_UNKNOWN = 500002 | |||||
ERR_SMS = 500003 | |||||
ERR_ARKID_REGISTER = 500004 | |||||
ERR_ARKID_WHITELIST = 500005 | |||||
ERR_ARKID_LOGIN = 500006 | |||||
ERR_CFG = 500007 | |||||
ERR_DB_ORM = 500008 | |||||
ERR_CFG_CACHE = 500009 | |||||
ERR_ZHIMENG_CONVERT_ERR = 500010 | |||||
ERR_ALIPAY_ERR = 500011 | |||||
ERR_ALIPAY_ORDER_ERR = 500012 | |||||
ERR_PAY_ERR = 500013 | |||||
ERR_IS_BIND_THIRDOTHER = 500014 | |||||
) | |||||
var MsgFlags = map[int]string{ | |||||
// 200 | |||||
ERR_FILE_SAVE: "文件保存失败", | |||||
// 400 | |||||
ERR_TOKEN_EXPIRE: "Token过期", | |||||
ERR_BAD_REQUEST: "请求失败", | |||||
ERR_INVALID_ARGS: "请求参数错误", | |||||
ERR_API_RESPONSE: "API错误", | |||||
ERR_QINIUAPI_RESPONSE: "七牛请求API错误", | |||||
ERR_URL_TURNCHAIN: "转链失败", | |||||
ERR_NO_DATA: "暂无数据", | |||||
ERR_MOBILE_NIL: "电话号码不能为空", | |||||
ERR_MOBILE_MATH: "电话号码输入有误", | |||||
ERR_FILE_MAX_SIZE: "文件上传大小超限", | |||||
ERR_FILE_EXT: "文件类型不支持", | |||||
ERR_SIGN: "签名校验失败", | |||||
ERR_PROVIDER_RESPONSE: "提供商接口错误", | |||||
ERR_AES_ENCODE: "加解密错误", | |||||
ERR_ADMIN_API: "后台接口请求失败", | |||||
// 401 | |||||
ERR_NOT_AUTH: "请登录后操作", | |||||
ERR_SMS_AUTH: "验证码过期或无效", | |||||
ERR_UNAUTHORIZED: "验证用户失败", | |||||
ERR_TOKEN_FORMAT: "Token格式不对", | |||||
ERR_TOKEN_GEN: "生成Token失败", | |||||
ERR_CACHE_SET: "生成缓存失败", | |||||
// 403 | |||||
ERR_FORBIDEN: "禁止访问", | |||||
ERR_PLATFORM: "平台不支持", | |||||
ERR_MOBILE_EXIST: "该号码已注册过", | |||||
ERR_USER_NO_EXIST: "用户没有注册或账号密码不正确", | |||||
ERR_PASSWORD_ERR: "输入两次密码不一致", | |||||
ERR_RELATE_ERR: "推荐人不能是自己的粉丝", | |||||
ERR_PARENTUID_ERR: "推荐人不存在", | |||||
ERR_TOKEN_AUTH: "登录信息失效,请重新登录", | |||||
ERR_MOB_SMS_NO_AVA: "短信余额不足或智盟短信配置失败", | |||||
ERR_USER_IS_REG: "用户已注册", | |||||
ERR_MASTER_ID: "找不到对应站长的数据库", | |||||
ERR_CASH_OUT_TIME: "非可提现时间段", | |||||
ERR_CASH_OUT_USER_NOT_FOUND: "收款账号不存在", | |||||
ERR_CASH_OUT_FAIL: "提现失败", | |||||
ERR_CASH_OUT_FEE: "提现金额必须大于手续费", | |||||
ERR_CASH_OUT_TIMES: "当日提现次数已达上线", | |||||
ERR_CASH_OUT_MINI: "申请提现金额未达到最低金额要求", | |||||
ERR_CASH_OUT_MUT: "申请提现金额未达到整数倍要求", | |||||
ERR_CASH_OUT_NOT_DECIMAL: "提现申请金额只能是整数", | |||||
ERR_CASH_OUT_NOT_DAY_AVA: "不在可提现日期范围内", | |||||
ERR_USER_LEVEL_PAY_CHECK_TASK_NO_DONE: "请先完成其他任务", | |||||
ERR_USER_LEVEL_PAY_CHECK_NO_CROSS: "无法跨越升级", | |||||
ERR_USER_LEVEL_ORD_EXP: "付费订单已失效", | |||||
ERR_IS_BIND_THIRDPARTY: "该用户已经绑定了", | |||||
ERR_IS_BIND_THIRDOTHER: "该账号已经被绑定了", | |||||
ERR_USER_LEVEL_UPDATE_CHECK_TASK_NO_DONE: "请完成指定任务", | |||||
ERR_USER_LEVEL_UPDATE_CHECK_NOT_FOUND_ORDER: "没有找到对应的订单", | |||||
ERR_USER_LEVEL_UPDATE_REPEAT: "不允许重复升级", | |||||
ERR_USER_NO_ACTIVE: "账户没激活", | |||||
ERR_USER_IS_BAN: "账户已被冻结", | |||||
ERR_SYSUNION_CONFIG: "联盟设置错误,请检查配置", | |||||
ERR_WECAHT_MINI: "小程序响应错误,请检查小程序配置", | |||||
ERR_WECAHT_MINI_CACHE: "获取小程序缓存失败", | |||||
ERR_WECAHT_MINI_DECODE: "小程序解密失败", | |||||
ERR_WECHAT_MINI_ACCESSTOKEN: "无法获取accesstoekn", | |||||
ERR_CURRENT_VIP_LEVEL_AUDITING: "当前等级正在审核中", | |||||
ERR_LEVEL_RENEW_SHOULD_KEEP_CURRENT: "续费只能在当前等级续费", | |||||
ERR_LEVEL_UPGRADE_APPLY_AUDITTING: "已有申请正在审核中,暂时不能申请", | |||||
ERR_LEVEL_TASK_PAY_TYPE: "任务付费类型错误", | |||||
ERR_BALANCE_NOT_ENOUGH: "余额不足", | |||||
ERR_ADMIN_PUSH: "后台MOB推送错误", | |||||
ERR_PLAN: "分拥方案出错", | |||||
ERR_MOB_CONFIG: "Mob 配置错误", | |||||
ERR_BAlANCE_PAY_ORDERTYPE: "无效余额支付订单类型", | |||||
ERR_PHONE_EXISTED: "手机号码已存在", | |||||
ERR_NOT_RESULT: "已加载完毕", | |||||
ERR_REVIEW: "审核模板错误", | |||||
ERR_USER_LEVEL_HAS_PAID: "该等级已经付过款", | |||||
// 404 | |||||
ERR_USER_NOTFOUND: "用户不存在", | |||||
ERR_USER_DEL: "账号被删除,如有疑问请联系客服", | |||||
ERR_SUP_NOTFOUND: "上级用户不存在", | |||||
ERR_LEVEL_MAP: "无等级映射关系", | |||||
ERR_MOD_NOTFOUND: "没有找到对应模块", | |||||
ERR_CLIPBOARD_PARSE: "无法解析剪切板内容", | |||||
ERR_NOT_FAN: "没有粉丝", | |||||
ERR_CLIPBOARD_UNSUP: "不支持该平台", | |||||
ERR_USER_LEVEL: "该等级已不存在", | |||||
ERR_LACK_PAY_CFG: "支付配置不完整", | |||||
ERR_NOT_LEVEL_TASK: "等级任务查找错误", | |||||
ERR_ITEM_NOT_FOUND: "找不到对应商品", | |||||
ERR_WX_CHECKFILE_NOTFOUND: "找不到微信校验文件", | |||||
ERR_USER_BIND_OWN: "不能填写自己的邀请码", | |||||
// 429 | |||||
ERR_TOO_MANY_REQUESTS: "请求频繁,请稍后重试", | |||||
// 500 内部错误 | |||||
ERR: "接口错误", | |||||
ERR_SMS: "短信发送出错", | |||||
ERR_CFG: "服务器配置错误", | |||||
ERR_UNMARSHAL: "JSON解码错误", | |||||
ERR_UNKNOWN: "未知错误", | |||||
ERR_ARKID_LOGIN: "登录失败", | |||||
ERR_MOBILE_NO_EXIST: "该用户未设定手机号", | |||||
ERR_FORBIDEN_VALID: "验证码错误", | |||||
ERR_CFG_CACHE: "获取配置缓存失败", | |||||
ERR_DB_ORM: "数据操作失败", | |||||
ERR_REPEAT_RELATE: "重复关联", | |||||
ERR_ZHIMENG_CONVERT_ERR: "智盟转链失败", | |||||
ERR_MOB_FORBIDEN: "Mob调用失败", | |||||
ERR_ALIPAY_ERR: "支付宝参数错误", | |||||
ERR_ALIPAY_SETTING: "请在后台正确配置支付宝", | |||||
ERR_ALIPAY_ORDERTYPE: "无效支付宝订单类型", | |||||
ERR_ALIPAY_ORDER_ERR: "订单创建错误", | |||||
ERR_PAY_ERR: "未找到支付方式", | |||||
ERR_SEARCH_ERR: "暂无该分类商品", | |||||
ERR_LEVEL_REACH_TOP: "已经是最高等级", | |||||
ERR_USER_CHECK_ERR: "校验失败", | |||||
} |
@@ -0,0 +1,72 @@ | |||||
package e | |||||
import ( | |||||
"fmt" | |||||
"path" | |||||
"runtime" | |||||
) | |||||
type E struct { | |||||
Code int // 错误码 | |||||
msg string // 报错代码 | |||||
st string // 堆栈信息 | |||||
} | |||||
func NewErrCode(code int) error { | |||||
if msg, ok := MsgFlags[code]; ok { | |||||
return E{code, msg, stack(3)} | |||||
} | |||||
return E{ERR_UNKNOWN, "unknown", stack(3)} | |||||
} | |||||
func NewErr(code int, msg string) error { | |||||
return E{code, msg, stack(3)} | |||||
} | |||||
func NewErrf(code int, msg string, args ...interface{}) error { | |||||
return E{code, fmt.Sprintf(msg, args), stack(3)} | |||||
} | |||||
func (e E) Error() string { | |||||
return e.msg | |||||
} | |||||
func stack(skip int) string { | |||||
stk := make([]uintptr, 32) | |||||
str := "" | |||||
l := runtime.Callers(skip, stk[:]) | |||||
for i := 0; i < l; i++ { | |||||
f := runtime.FuncForPC(stk[i]) | |||||
name := f.Name() | |||||
file, line := f.FileLine(stk[i]) | |||||
str += fmt.Sprintf("\n%-30s[%s:%d]", name, path.Base(file), line) | |||||
} | |||||
return str | |||||
} | |||||
// ErrorIsAccountBan is 检查这个账号是否被禁用的错误 | |||||
func ErrorIsAccountBan(e error) bool { | |||||
err, ok := e.(E) | |||||
if ok && err.Code == 403029 { | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
// ErrorIsAccountNoActive is 检查这个账号是否被禁用的错误 | |||||
func ErrorIsAccountNoActive(e error) bool { | |||||
err, ok := e.(E) | |||||
if ok && err.Code == 403028 { | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
// ErrorIsUserDel is 检查这个账号是否被删除 | |||||
func ErrorIsUserDel(e error) bool { | |||||
err, ok := e.(E) | |||||
if ok && err.Code == 403053 { | |||||
return true | |||||
} | |||||
return false | |||||
} |
@@ -0,0 +1,110 @@ | |||||
package e | |||||
import ( | |||||
"applet/app/utils" | |||||
"encoding/json" | |||||
"net/http" | |||||
"github.com/gin-gonic/gin" | |||||
"applet/app/utils/logx" | |||||
) | |||||
// GetMsg get error information based on Code | |||||
// 因为这里code是自己控制的, 因此没考虑报错信息 | |||||
func GetMsg(code int) (int, string) { | |||||
if msg, ok := MsgFlags[code]; ok { | |||||
return code / 1000, msg | |||||
} | |||||
if http.StatusText(code) == "" { | |||||
code = 200 | |||||
} | |||||
return code, MsgFlags[ERR_BAD_REQUEST] | |||||
} | |||||
// 成功输出, fields 是额外字段, 与code, msg同级 | |||||
func OutSuc(c *gin.Context, data interface{}, fields map[string]interface{}) { | |||||
res := gin.H{ | |||||
"code": 1, | |||||
"msg": "ok", | |||||
"data": data, | |||||
} | |||||
if fields != nil { | |||||
for k, v := range fields { | |||||
res[k] = v | |||||
} | |||||
} | |||||
if utils.GetApiVersion(c) > 0 { //加了签名校验只返回加密的字符串 | |||||
jsonData, _ := json.Marshal(res) | |||||
str := utils.ResultAes(c, jsonData) | |||||
c.Writer.WriteString(str) | |||||
} else { | |||||
c.AbortWithStatusJSON(200, res) | |||||
} | |||||
} | |||||
func OutSucPure(c *gin.Context, data interface{}, fields map[string]interface{}) { | |||||
res := gin.H{ | |||||
"code": 1, | |||||
"msg": "ok", | |||||
"data": data, | |||||
} | |||||
if fields != nil { | |||||
for k, v := range fields { | |||||
res[k] = v | |||||
} | |||||
} | |||||
c.Abort() | |||||
c.PureJSON(200, res) | |||||
} | |||||
// 错误输出 | |||||
func OutErr(c *gin.Context, code int, err ...interface{}) { | |||||
statusCode, msg := GetMsg(code) | |||||
if len(err) > 0 && err[0] != nil { | |||||
e := err[0] | |||||
switch v := e.(type) { | |||||
case E: | |||||
statusCode = v.Code / 1000 | |||||
msg = v.Error() | |||||
logx.Error(v.msg + ": " + v.st) // 记录堆栈信息 | |||||
case error: | |||||
logx.Error(v) | |||||
break | |||||
case string: | |||||
msg = v | |||||
case int: | |||||
if _, ok := MsgFlags[v]; ok { | |||||
msg = MsgFlags[v] | |||||
} | |||||
} | |||||
} | |||||
if utils.GetApiVersion(c) > 0 { //加了签名校验只返回加密的字符串 | |||||
jsonData, _ := json.Marshal(gin.H{ | |||||
"code": code, | |||||
"msg": msg, | |||||
"data": []struct{}{}, | |||||
}) | |||||
str := utils.ResultAes(c, jsonData) | |||||
if code > 100000 { | |||||
code = int(utils.FloatFormat(float64(code/1000), 0)) | |||||
} | |||||
c.Status(500) | |||||
c.Writer.WriteString(str) | |||||
} else { | |||||
c.AbortWithStatusJSON(statusCode, gin.H{ | |||||
"code": code, | |||||
"msg": msg, | |||||
"data": []struct{}{}, | |||||
}) | |||||
} | |||||
} | |||||
// 重定向 | |||||
func OutRedirect(c *gin.Context, code int, loc string) { | |||||
if code < 301 || code > 308 { | |||||
code = 303 | |||||
} | |||||
c.Redirect(code, loc) | |||||
c.Abort() | |||||
} |
@@ -0,0 +1,8 @@ | |||||
package e | |||||
func SetCache(cacheTime int64) map[string]interface{} { | |||||
if cacheTime == 0 { | |||||
return map[string]interface{}{"cache_time": cacheTime} | |||||
} | |||||
return map[string]interface{}{"cache_time": cacheTime} | |||||
} |
@@ -0,0 +1,67 @@ | |||||
package enum | |||||
type QrcodeBatchState int32 | |||||
const ( | |||||
QrcodeBatchStateForUseIng = 1 | |||||
QrcodeBatchStateForUseAlready = 2 | |||||
QrcodeBatchStateForExpire = 3 | |||||
QrcodeBatchStateForCancel = 4 | |||||
) | |||||
func (gt QrcodeBatchState) String() string { | |||||
switch gt { | |||||
case QrcodeBatchStateForUseIng: | |||||
return "使用中" | |||||
case QrcodeBatchStateForUseAlready: | |||||
return "使用完" | |||||
case QrcodeBatchStateForExpire: | |||||
return "已过期" | |||||
case QrcodeBatchStateForCancel: | |||||
return "已作废" | |||||
default: | |||||
return "未知" | |||||
} | |||||
} | |||||
type QrcodeWithBatchRecordsSate int32 | |||||
const ( | |||||
QrcodeWithBatchRecordsStateForWait = 1 | |||||
QrcodeWithBatchRecordsStateForAlready = 2 | |||||
QrcodeWithBatchRecordsStateForExpire = 3 | |||||
QrcodeWithBatchRecordsStateForCancel = 4 | |||||
) | |||||
func (gt QrcodeWithBatchRecordsSate) String() string { | |||||
switch gt { | |||||
case QrcodeWithBatchRecordsStateForWait: | |||||
return "待使用" | |||||
case QrcodeWithBatchRecordsStateForAlready: | |||||
return "已使用" | |||||
case QrcodeWithBatchRecordsStateForExpire: | |||||
return "已过期" | |||||
case QrcodeWithBatchRecordsStateForCancel: | |||||
return "已作废" | |||||
default: | |||||
return "未知" | |||||
} | |||||
} | |||||
type QrcodeSate int32 | |||||
const ( | |||||
QrcodeSateAllowUse = 1 | |||||
QrcodeSateAllowNotUse = 2 | |||||
) | |||||
func (gt QrcodeSate) String() string { | |||||
switch gt { | |||||
case QrcodeSateAllowUse: | |||||
return "可使用" | |||||
case QrcodeSateAllowNotUse: | |||||
return "不可用" | |||||
default: | |||||
return "未知" | |||||
} | |||||
} |
@@ -0,0 +1,28 @@ | |||||
package enum | |||||
type SysCfg string | |||||
const ( | |||||
WxMchApiV3Key = "wx_mch_api_v3_key" | |||||
WxMchCertificateSerialNumber = "wx_mch_certificate_serial_number" | |||||
WxMchId = "wx_mch_id" | |||||
WxOfficialAccountAppId = "wx_official_account_app_id" | |||||
WxOfficialAccountAppSecret = "wx_official_account_app_secret" | |||||
) | |||||
func (gt SysCfg) String() string { | |||||
switch gt { | |||||
case WxMchApiV3Key: | |||||
return "微信商户APIv3密钥" | |||||
case WxMchCertificateSerialNumber: | |||||
return "微信商户证书序列号" | |||||
case WxMchId: | |||||
return "微信商户号" | |||||
case WxOfficialAccountAppId: | |||||
return "微信公众号appId" | |||||
case WxOfficialAccountAppSecret: | |||||
return "微信公众号appSecret" | |||||
default: | |||||
return "未知" | |||||
} | |||||
} |
@@ -0,0 +1,19 @@ | |||||
package enum | |||||
type WxOfficialAccountRequest string | |||||
const ( | |||||
GetAccessToken = "cgi-bin/token" | |||||
QrcodeCreate = "cgi-bin/qrcode/create" | |||||
) | |||||
func (gt WxOfficialAccountRequest) String() string { | |||||
switch gt { | |||||
case GetAccessToken: | |||||
return "获取 Access token" | |||||
case QrcodeCreate: | |||||
return "生成带参二维码" | |||||
default: | |||||
return "未知" | |||||
} | |||||
} |
@@ -0,0 +1,173 @@ | |||||
package md | |||||
const ZhiosLianlianProductEsIndex = "zhios_lianlian_product" | |||||
const ZhiosLianlianProductEsIndexProd = "zhios_lianlian_product_prod" | |||||
const ZhiosLianlianProductEsMapping = ` | |||||
{ | |||||
"settings" : { | |||||
"number_of_shards" : 2, | |||||
"number_of_replicas" : 1 | |||||
}, | |||||
"mappings":{ | |||||
"properties":{ | |||||
"id":{ | |||||
"type": "integer" | |||||
}, | |||||
"locationIds":{ | |||||
"type": "keyword" | |||||
}, | |||||
"shopKey":{ | |||||
"type": "keyword" | |||||
}, | |||||
"locationId":{ | |||||
"type": "integer" | |||||
}, | |||||
"productTitle":{ | |||||
"type": "text", | |||||
"analyzer": "ik_smart" | |||||
}, | |||||
"faceImg":{ | |||||
"type": "keyword" | |||||
}, | |||||
"address":{ | |||||
"type": "keyword" | |||||
}, | |||||
"tel":{ | |||||
"type": "keyword" | |||||
}, | |||||
"endTime":{ | |||||
"type": "integer" | |||||
}, | |||||
"beginTime":{ | |||||
"type": "integer" | |||||
}, | |||||
"validEndDate":{ | |||||
"type": "integer" | |||||
}, | |||||
"validBeginDate":{ | |||||
"type": "integer" | |||||
}, | |||||
"singleMin":{ | |||||
"type": "integer" | |||||
}, | |||||
"singleMax":{ | |||||
"type": "integer" | |||||
}, | |||||
"channelStock":{ | |||||
"type": "integer" | |||||
}, | |||||
"stock":{ | |||||
"type": "integer" | |||||
}, | |||||
"saleAmount":{ | |||||
"type": "integer" | |||||
}, | |||||
"itemStock":{ | |||||
"type": "integer" | |||||
}, | |||||
"bookingType":{ | |||||
"type": "integer" | |||||
}, | |||||
"bookingBeginDate":{ | |||||
"type": "integer" | |||||
}, | |||||
"bookingShowAddress":{ | |||||
"type": "integer" | |||||
}, | |||||
"orderShowIdCard":{ | |||||
"type": "integer" | |||||
}, | |||||
"orderShowDate":{ | |||||
"type": "integer" | |||||
}, | |||||
"bookingText":{ | |||||
"type": "keyword" | |||||
}, | |||||
"attention":{ | |||||
"type": "keyword" | |||||
}, | |||||
"soldOutTime":{ | |||||
"type": "integer" | |||||
}, | |||||
"isSoldOut":{ | |||||
"type": "integer" | |||||
}, | |||||
"city":{ | |||||
"type": "text", | |||||
"analyzer": "ik_smart" | |||||
}, | |||||
"cityCode":{ | |||||
"type": "keyword" | |||||
}, | |||||
"latitude":{ | |||||
"type": "double" | |||||
}, | |||||
"longitude":{ | |||||
"type": "double" | |||||
}, | |||||
"bookingShowPostTime":{ | |||||
"type": "integer" | |||||
}, | |||||
"posterUrl":{ | |||||
"type": "keyword" | |||||
}, | |||||
"items":{ | |||||
"type": "keyword" | |||||
}, | |||||
"shops":{ | |||||
"type": "keyword" | |||||
}, | |||||
"categoryPath":{ | |||||
"type": "keyword" | |||||
}, | |||||
"categoryName":{ | |||||
"type": "keyword" | |||||
}, | |||||
"productCategoryId":{ | |||||
"type": "integer" | |||||
}, | |||||
"firstCategoryId":{ | |||||
"type": "integer" | |||||
}, | |||||
"isReFund":{ | |||||
"type": "integer" | |||||
}, | |||||
"contractId":{ | |||||
"type": "integer" | |||||
}, | |||||
"qualificationsList":{ | |||||
"type": "keyword" | |||||
}, | |||||
"releaseTime":{ | |||||
"type": "integer" | |||||
}, | |||||
"channelMallPosterImg":{ | |||||
"type": "keyword" | |||||
}, | |||||
"codeDelay":{ | |||||
"type": "integer" | |||||
}, | |||||
"name":{ | |||||
"type": "keyword" | |||||
}, | |||||
"price":{ | |||||
"type": "keyword" | |||||
}, | |||||
"costPrice":{ | |||||
"type": "keyword" | |||||
}, | |||||
"lianlianPrice":{ | |||||
"type": "keyword" | |||||
}, | |||||
"productSequence":{ | |||||
"type": "integer" | |||||
}, | |||||
"ecommerce":{ | |||||
"type": "integer" | |||||
}, | |||||
"location":{ | |||||
"type": "geo_point" | |||||
} | |||||
} | |||||
} | |||||
}` |
@@ -0,0 +1,38 @@ | |||||
package md | |||||
const ZhiosRealNewsData = "zhios_real_news_data" | |||||
const ZhiosRealNewsDataField = ` | |||||
{ | |||||
"settings" : { | |||||
"number_of_shards" : 2, | |||||
"number_of_replicas" : 1 | |||||
}, | |||||
"mappings":{ | |||||
"properties":{ | |||||
"id":{ | |||||
"type": "integer" | |||||
}, | |||||
"key":{ | |||||
"type": "keyword" | |||||
}, | |||||
"cate_id":{ | |||||
"type": "keyword" | |||||
}, | |||||
"images":{ | |||||
"type": "keyword" | |||||
}, | |||||
"platform":{ | |||||
"type": "keyword" | |||||
}, | |||||
"content":{ | |||||
"type": "keyword" | |||||
}, | |||||
"old_content":{ | |||||
"type": "keyword" | |||||
}, | |||||
"start_time":{ | |||||
"type": "integer" | |||||
} | |||||
} | |||||
} | |||||
}` |
@@ -0,0 +1,105 @@ | |||||
package md | |||||
const ZhiosTikTokTaskEsIndex = "zhios_tik_tok_task" | |||||
const ZhiosTikTokTaskDetailEsIndex = "zhios_tik_tok_task_detail" | |||||
const ZhiosTikTokTaskEsMapping = ` | |||||
{ | |||||
"settings" : { | |||||
"number_of_shards" : 2, | |||||
"number_of_replicas" : 1 | |||||
}, | |||||
"mappings":{ | |||||
"properties":{ | |||||
"task_ids":{ | |||||
"type": "text" | |||||
} | |||||
} | |||||
} | |||||
}` | |||||
const ZhiosTikTokTaskDetailEsMapping = ` | |||||
{ | |||||
"settings" : { | |||||
"number_of_shards" : 2, | |||||
"number_of_replicas" : 1 | |||||
}, | |||||
"mappings":{ | |||||
"properties":{ | |||||
"appid":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_start_time":{ | |||||
"type": "integer" | |||||
}, | |||||
"task_end_time":{ | |||||
"type": "integer" | |||||
}, | |||||
"page_type":{ | |||||
"type": "integer" | |||||
}, | |||||
"start_page":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_desc":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_icon":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_name":{ | |||||
"type": "keyword" | |||||
}, | |||||
"anchor_title":{ | |||||
"type": "keyword" | |||||
}, | |||||
"status":{ | |||||
"type": "integer" | |||||
}, | |||||
"task_tags":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_settle_type":{ | |||||
"type": "integer" | |||||
}, | |||||
"task_id":{ | |||||
"type": "keyword" | |||||
}, | |||||
"refer_ma_captures":{ | |||||
"type": "keyword" | |||||
}, | |||||
"refer_video_captures":{ | |||||
"type": "keyword" | |||||
}, | |||||
"refer_videos":{ | |||||
"type": "keyword" | |||||
}, | |||||
"refer_gids":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_refund_period":{ | |||||
"type": "integer" | |||||
}, | |||||
"payment_allocate_ratio":{ | |||||
"type": "integer" | |||||
}, | |||||
"platform_address_app":{ | |||||
"type": "keyword" | |||||
}, | |||||
"platform_address_web":{ | |||||
"type": "keyword" | |||||
}, | |||||
"reject_reason":{ | |||||
"type": "keyword" | |||||
}, | |||||
"task_type":{ | |||||
"type": "integer" | |||||
}, | |||||
"video_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"oriented_talent_rel_list":{ | |||||
"type": "text" | |||||
} | |||||
} | |||||
} | |||||
}` |
@@ -0,0 +1,147 @@ | |||||
package md | |||||
const ZhiosTpdarenEsIndex = "zhios_tpdaren" | |||||
const ZhiosTpdarenEsIndexNew = "zhios_tpdaren_new" | |||||
const ZhiosTpdarenEsMapping = ` | |||||
{ | |||||
"settings" : { | |||||
"number_of_shards" : 2, | |||||
"number_of_replicas" : 1 | |||||
}, | |||||
"mappings":{ | |||||
"properties":{ | |||||
"id":{ | |||||
"type": "integer" | |||||
}, | |||||
"is_hot":{ | |||||
"type": "integer" | |||||
}, | |||||
"video_type":{ | |||||
"type": "keyword" | |||||
}, | |||||
"appid":{ | |||||
"type": "keyword" | |||||
}, | |||||
"description":{ | |||||
"type": "keyword" | |||||
}, | |||||
"episode":{ | |||||
"type": "integer" | |||||
}, | |||||
"channel_theater_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"douyin_theater_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"kuaishou_theater_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"update_time":{ | |||||
"type": "integer" | |||||
}, | |||||
"image_url":{ | |||||
"type": "keyword" | |||||
}, | |||||
"is_end":{ | |||||
"type": "integer" | |||||
}, | |||||
"pay_episode":{ | |||||
"type": "integer" | |||||
}, | |||||
"material":{ | |||||
"type": "keyword" | |||||
}, | |||||
"online_time":{ | |||||
"type": "keyword" | |||||
}, | |||||
"time":{ | |||||
"type": "integer" | |||||
}, | |||||
"type":{ | |||||
"type": "keyword" | |||||
}, | |||||
"title":{ | |||||
"type": "keyword" | |||||
}, | |||||
"data":{ | |||||
"type": "text" | |||||
} | |||||
} | |||||
} | |||||
}` | |||||
const ZhiosTpdarenNewEsMapping = ` | |||||
{ | |||||
"settings" : { | |||||
"number_of_shards" : 2, | |||||
"number_of_replicas" : 1 | |||||
}, | |||||
"mappings":{ | |||||
"properties":{ | |||||
"id":{ | |||||
"type": "keyword" | |||||
}, | |||||
"is_close":{ | |||||
"type": "integer" | |||||
}, | |||||
"is_hot":{ | |||||
"type": "integer" | |||||
}, | |||||
"video_type":{ | |||||
"type": "keyword" | |||||
}, | |||||
"cate":{ | |||||
"type": "keyword" | |||||
}, | |||||
"appid":{ | |||||
"type": "keyword" | |||||
}, | |||||
"description":{ | |||||
"type": "keyword" | |||||
}, | |||||
"episode":{ | |||||
"type": "integer" | |||||
}, | |||||
"channel_theater_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"douyin_theater_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"kuaishou_theater_id":{ | |||||
"type": "integer" | |||||
}, | |||||
"update_time":{ | |||||
"type": "integer" | |||||
}, | |||||
"image_url":{ | |||||
"type": "keyword" | |||||
}, | |||||
"is_end":{ | |||||
"type": "integer" | |||||
}, | |||||
"pay_episode":{ | |||||
"type": "integer" | |||||
}, | |||||
"material":{ | |||||
"type": "keyword" | |||||
}, | |||||
"online_time":{ | |||||
"type": "keyword" | |||||
}, | |||||
"time":{ | |||||
"type": "integer" | |||||
}, | |||||
"type":{ | |||||
"type": "keyword" | |||||
}, | |||||
"title":{ | |||||
"type": "keyword" | |||||
}, | |||||
"data":{ | |||||
"type": "text" | |||||
} | |||||
} | |||||
} | |||||
}` |
@@ -0,0 +1,27 @@ | |||||
package auth | |||||
import ( | |||||
"time" | |||||
"github.com/dgrijalva/jwt-go" | |||||
) | |||||
// TokenExpireDuration is jwt 过期时间 | |||||
const TokenExpireDuration = time.Hour * 4380 | |||||
const RefreshTokenExpireDuration = time.Hour * 6 | |||||
var Secret = []byte("zyos_zhimeng") | |||||
var RSecret = []byte("zyos_zhimeng_refresh_token") | |||||
// JWTUser 如果想要保存更多信息,都可以添加到这个结构体中 | |||||
type JWTUser struct { | |||||
AdmId int `json:"adm_id"` | |||||
Username string `json:"username"` | |||||
jwt.StandardClaims | |||||
} | |||||
// JWTMaster 如果想要保存更多信息,都可以添加到这个结构体中 | |||||
type JWTMaster struct { | |||||
MasterId string `json:"master_id"` | |||||
jwt.StandardClaims | |||||
} |
@@ -0,0 +1,40 @@ | |||||
package haodanku | |||||
import ( | |||||
"applet/app/utils" | |||||
) | |||||
type HdkReal struct { | |||||
WireId string `json:"wire_id"` | |||||
CateId []string `json:"cate_id"` | |||||
Images []string `json:"images"` | |||||
Platform string `json:"platform"` | |||||
Starttime interface{} `json:"starttime"` | |||||
Endtime string `json:"endtime"` | |||||
List []struct { | |||||
Content string `json:"content,omitempty"` | |||||
ItemId string `json:"item_id,omitempty"` | |||||
} `json:"list"` | |||||
} | |||||
// 好单库详情接口 | |||||
func HaodankuRealTime(topic, pageId, pageSize, selectTime string) (res []HdkReal, err error) { | |||||
params := map[string]string{ | |||||
"version": "v2.1.0", | |||||
"min_size": pageSize, | |||||
"min_id": pageId, | |||||
} | |||||
if topic != "" { | |||||
params["type"] = topic | |||||
} | |||||
resp, err := SendReq("http://v2.api.haodanku.com/wire_report", params) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
var featuredList struct { | |||||
Data []HdkReal `json:"data"` | |||||
} | |||||
utils.Unserialize(resp, &featuredList) | |||||
res = featuredList.Data | |||||
return res, nil | |||||
} |
@@ -0,0 +1,16 @@ | |||||
package qiniu | |||||
import ( | |||||
"github.com/qiniu/api.v7/v7/auth" | |||||
"github.com/qiniu/api.v7/v7/storage" | |||||
) | |||||
func BucketCreate() error { | |||||
mac := auth.New(AK, SK) | |||||
cfg := storage.Config{ | |||||
// 是否使用https域名进行资源管理 | |||||
UseHTTPS: false, | |||||
} | |||||
bucketManager := storage.NewBucketManager(mac, &cfg) | |||||
return bucketManager.CreateBucket("", storage.RIDHuanan) | |||||
} |
@@ -0,0 +1,18 @@ | |||||
package qiniu | |||||
import ( | |||||
"github.com/qiniu/api.v7/v7/auth" | |||||
"github.com/qiniu/api.v7/v7/storage" | |||||
) | |||||
func BucketDelete(bucketName string) error { | |||||
mac := auth.New(AK, SK) | |||||
cfg := storage.Config{ | |||||
// 是否使用https域名进行资源管理 | |||||
UseHTTPS: false, | |||||
} | |||||
bucketManager := storage.NewBucketManager(mac, &cfg) | |||||
return bucketManager.DropBucket(bucketName) | |||||
} |
@@ -0,0 +1,18 @@ | |||||
package qiniu | |||||
import ( | |||||
"github.com/qiniu/api.v7/v7/auth" | |||||
"github.com/qiniu/api.v7/v7/storage" | |||||
) | |||||
func BucketGetDomain(bucketName string) (string, error) { | |||||
mac := auth.New(AK, SK) | |||||
cfg := storage.Config{UseHTTPS: false} | |||||
bucketManager := storage.NewBucketManager(mac, &cfg) | |||||
b, err := bucketManager.ListBucketDomains(bucketName) | |||||
if err != nil { | |||||
return "", err | |||||
} | |||||
return b[0].Domain, nil | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package qiniu | |||||
import "applet/app/utils" | |||||
var ( | |||||
AK = "MmxNdai23egjNUHjdzEVaTPdPCIbWzENz9BQuak3" | |||||
SK = "mElaFlM9O16rXp-ihoQdJ9KOH56naKm3MoyQBA59" | |||||
BUCKET = "dev-fnuoos" // 桶子名称 | |||||
BUCKET_SCHEME = "http" | |||||
BUCKET_REGION = "up-z2.qiniup.com" | |||||
Expires uint64 = 3600 | |||||
) | |||||
func Init(ak, sk, bucket, region, scheme string) { | |||||
AK, SK, BUCKET, BUCKET_REGION, BUCKET_SCHEME = ak, sk, bucket, region, scheme | |||||
} | |||||
func Sign(t string) string { | |||||
return utils.Md5(AK + SK + t) | |||||
} |
@@ -0,0 +1,54 @@ | |||||
package qiniu | |||||
import ( | |||||
"applet/app/md" | |||||
"applet/app/utils" | |||||
"time" | |||||
"github.com/qiniu/api.v7/v7/auth/qbox" | |||||
_ "github.com/qiniu/api.v7/v7/conf" | |||||
"github.com/qiniu/api.v7/v7/storage" | |||||
) | |||||
// 请求图片上传地址信息 | |||||
func ReqImgUpload(f *md.FileCallback, callbackUrl string) interface{} { | |||||
if ext := utils.FileExt(f.FileName); ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "gif" || ext == "bmp" || ext == "webp" { | |||||
f.Width = "$(imageInfo.width)" | |||||
f.Height = "$(imageInfo.height)" | |||||
} | |||||
f.Provider = "qiniu" | |||||
f.FileSize = "$(fsize)" | |||||
f.Hash = "$(etag)" | |||||
f.Bucket = "$(bucket)" | |||||
f.Mime = "$(mimeType)" | |||||
f.Time = utils.Int64ToStr(time.Now().Unix()) | |||||
f.Sign = Sign(f.Time) | |||||
putPolicy := storage.PutPolicy{ | |||||
Scope: BUCKET + ":" + f.FileName, // 使用覆盖方式时候必须请求里面有key,否则报错 | |||||
Expires: Expires, | |||||
ForceSaveKey: true, | |||||
SaveKey: f.FileName, | |||||
MimeLimit: "image/*", // 只允许上传图片 | |||||
CallbackURL: callbackUrl, | |||||
CallbackBody: utils.SerializeStr(f), | |||||
CallbackBodyType: "application/json", | |||||
} | |||||
return &struct { | |||||
Method string `json:"method"` | |||||
Key string `json:"key"` | |||||
Host string `json:"host"` | |||||
Token string `json:"token"` | |||||
}{Key: f.FileName, Method: "POST", Host: BUCKET_SCHEME + "://" + BUCKET_REGION, Token: putPolicy.UploadToken(qbox.NewMac(AK, SK))} | |||||
} | |||||
/* | |||||
form表单上传 | |||||
地址 : http://upload-z2.qiniup.com | |||||
header | |||||
- Content-Type : multipart/form-data | |||||
body : | |||||
- key : 文件名 | |||||
- token : 生成token | |||||
- file : 待上传文件 | |||||
*/ |
@@ -0,0 +1,33 @@ | |||||
package validate | |||||
import ( | |||||
"applet/app/e" | |||||
"applet/app/utils" | |||||
"applet/app/utils/logx" | |||||
"encoding/json" | |||||
"fmt" | |||||
"github.com/go-playground/validator/v10" | |||||
) | |||||
func HandleValidateErr(err error) error { | |||||
switch err.(type) { | |||||
case *json.UnmarshalTypeError: | |||||
return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误") | |||||
case validator.ValidationErrors: | |||||
errs := err.(validator.ValidationErrors) | |||||
transMsgMap := errs.Translate(utils.ValidatorTrans) // utils.ValidatorTrans \app\utils\validator_err_trans.go::ValidatorTransInit初始化获得 | |||||
transMsgOne := transMsgMap[GetOneKeyOfMapString(transMsgMap)] | |||||
return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne) | |||||
default: | |||||
_ = logx.Error(err) | |||||
return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err)) | |||||
} | |||||
} | |||||
// GetOneKeyOfMapString 取出Map的一个key | |||||
func GetOneKeyOfMapString(collection map[string]string) string { | |||||
for k := range collection { | |||||
return k | |||||
} | |||||
return "" | |||||
} |
@@ -0,0 +1,15 @@ | |||||
package md | |||||
const ( | |||||
// 文件缓存的key | |||||
KEY_CFG_FILE_PVD = "file_provider" // 文件供应商 | |||||
KEY_CFG_FILE_BUCKET = "file_bucket" | |||||
KEY_CFG_FILE_REGION = "file_bucket_region" | |||||
KEY_CFG_FILE_HOST = "file_bucket_host" | |||||
KEY_CFG_FILE_SCHEME = "file_bucket_scheme" | |||||
KEY_CFG_FILE_AK = "file_access_key" | |||||
KEY_CFG_FILE_SK = "file_secret_key" | |||||
KEY_CFG_FILE_MAX_SIZE = "file_user_upload_max_size" | |||||
KEY_CFG_FILE_EXT = "file_ext" | |||||
KEY_CFG_FILE_AVATAR_THUMBNAIL = "file_avatar_thumbnail" // 默认头像缩略图参数,宽高120px,格式webp. | |||||
) |
@@ -0,0 +1,54 @@ | |||||
package md | |||||
// 用户拥有上传权限的目录, 目录ID | |||||
const ( | |||||
FILE_DIR_FEEDBACK = "feedback" | |||||
FILE_DIR_AVATAR = "avatar" | |||||
FILE_DIR_QRCODE = "qrcode" | |||||
FILE_DIR_STYLE = "style" | |||||
) | |||||
var ( | |||||
FileUserDir = map[string]string{ | |||||
FILE_DIR_FEEDBACK: "4", // 用户反馈 | |||||
FILE_DIR_AVATAR: "5", // 用户头像 | |||||
FILE_DIR_QRCODE: "6", // 用户微信二维码 | |||||
FILE_DIR_STYLE: "7", // 用户样式 | |||||
} | |||||
) | |||||
// 文件回调信息 | |||||
type FileCallback struct { | |||||
Uid string `json:"uid"` | |||||
DirId string `json:"dir_id"` | |||||
Provider string `json:"provider"` // 供应商 | |||||
FileName string `json:"fname"` // 原文件名 | |||||
FileSize string `json:"fsize"` | |||||
Hash string `json:"hash"` | |||||
Bucket string `json:"bucket"` | |||||
Mime string `json:"mime"` | |||||
Width string `json:"w,omitempty"` | |||||
Height string `json:"h,omitempty"` | |||||
Time string `json:"time"` // 默认一个小时内要上传完毕,否则超时 | |||||
Sign string `json:"sign"` // 签名 | |||||
} | |||||
type FileList struct { | |||||
Path string `json:"path"` | |||||
DirId int `json:"dir_id"` | |||||
FileName string `json:"f_name"` // 显示名称 | |||||
StgName string `json:"stg_name"` // 存储名字 | |||||
Ext string `json:"ext"` // 后缀名, png,jpg等 | |||||
FileSize string `json:"f_size"` | |||||
Provider string `json:"provider"` // 存储供应商 | |||||
Hash string `json:"hash"` | |||||
Bucket string `json:"bucket"` | |||||
Width int `json:"w"` | |||||
Height int `json:"h"` | |||||
Mime string `json:"mime"` | |||||
IsAdm bool `json:"is_adm"` //是否管理后台上传 | |||||
IsDir bool `json:"is_dir"` //是否文件夹 | |||||
CreateAt int `json:"create_at"` | |||||
Url string `json:"url"` | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package md | |||||
// 缓存key统一管理 | |||||
const ( | |||||
AdminJwtTokenKey = "%s:admin_jwt_token:%s" // jwt, 占位符:ip, admin:id | |||||
JwtTokenCacheTime = 3600 * 24 * 365 | |||||
CfgCacheTime = 86400 | |||||
AppCfgCacheKey = "one_item_one_code:%s" // 占位符: key的第一个字母 | |||||
WxOfficialAccountCacheKey = "wx_official_account" // 占位符: key的第一个字母 | |||||
ZhimengIsUseCacheKey = "zhimeng_is_use_cache" //授权码 | |||||
ZhimengAuthorizationCodeCacheKey = "zhimeng_authorization_code_cache" //授权码 | |||||
ZhimengAuthorizationCodeCacheTime = 60 | |||||
ZhimengDataRequestCacheKey = "%s:zhimeng_data_request_cache:%s:%s" //请求缓存(masterId:big_data_screen_data_request_cache:uri:md5(body参数)) | |||||
) |
@@ -0,0 +1,10 @@ | |||||
package md | |||||
type LoginReq struct { | |||||
UserName string `json:"username" binding:"required" label:"登录账号"` | |||||
PassWord string `json:"password" binding:"required" label:"登录密码"` | |||||
} | |||||
type LoginResponse struct { | |||||
Token string `json:"token"` | |||||
} |
@@ -0,0 +1,4 @@ | |||||
package md | |||||
var OrdTypeMap = map[string]string{"video": "短剧", "adv": "广告"} | |||||
var VideoTypeMap = map[string]string{"channel": "视频号", "douyin": "抖音", "kuaishou": "快手"} |
@@ -0,0 +1,31 @@ | |||||
package md | |||||
const ( | |||||
QrcodeTotalNums = 100000 | |||||
) | |||||
type QrcodeBatchListReq struct { | |||||
Page int `json:"page"` | |||||
Limit int `json:"limit"` | |||||
} | |||||
type QrcodeBatchAddReq struct { | |||||
Name string `json:"name"` | |||||
ExpireDate string `json:"expire_date"` | |||||
List []QrcodeBatchAddReqList `json:"list"` | |||||
Memo string `json:"memo"` | |||||
} | |||||
type QrcodeBatchAddReqList struct { | |||||
Num int `json:"num"` | |||||
Amount string `json:"amount"` | |||||
} | |||||
type QrcodeBatchAddReqListDetail struct { | |||||
Num int `json:"num"` | |||||
WaitUseNum int `json:"wait_use_num"` | |||||
UsedNum int `json:"used_num"` | |||||
ExpiredNum int `json:"expired_num"` | |||||
CancelNum int `json:"cancel_num"` | |||||
Amount string `json:"amount"` | |||||
} |
@@ -0,0 +1,9 @@ | |||||
package md | |||||
type SetSysCfgReq struct { | |||||
WxMchApiV3Key string `json:"wx_mch_api_v3_key" label:"微信商户APIv3密钥"` | |||||
WxMchCertificateSerialNumber string `json:"wx_mch_certificate_serial_number" label:"微信商户证书序列号"` | |||||
WxMchId string `json:"wx_mch_id" label:"微信商户号"` | |||||
WxOfficialAccountAppId string `json:"wx_official_account_app_id" label:"微信公众号appId"` | |||||
WxOfficialAccountAppSecret string `json:"wx_official_account_app_secret" label:"微信公众号appSecret"` | |||||
} |
@@ -0,0 +1,14 @@ | |||||
package md | |||||
const WxOfficialAccountRequestBaseUrl = "https://api.weixin.qq.com/" | |||||
type CreateTokenResp struct { | |||||
AccessToken string `json:"access_token"` | |||||
ExpiresIn int64 `json:"expires_in"` | |||||
} | |||||
type CreateQrcodeResp struct { | |||||
Ticket string `json:"ticket"` | |||||
ExpireSeconds int64 `json:"expire_seconds"` | |||||
Url string `json:"url"` | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package mw | |||||
import ( | |||||
"time" | |||||
"github.com/gin-gonic/gin" | |||||
"go.uber.org/zap" | |||||
"applet/app/utils/logx" | |||||
) | |||||
// access log | |||||
func AccessLog(c *gin.Context) { | |||||
start := time.Now() | |||||
c.Next() | |||||
cost := time.Since(start) | |||||
logx.Info(c.Request.URL.Path) | |||||
logger := &zap.Logger{} | |||||
logger.Info(c.Request.URL.Path, | |||||
zap.Int("status", c.Writer.Status()), | |||||
zap.String("method", c.Request.Method), | |||||
zap.String("path", c.Request.URL.Path), | |||||
zap.String("query", c.Request.URL.RawQuery), | |||||
zap.String("ip", c.ClientIP()), | |||||
zap.String("user-agent", c.Request.UserAgent()), | |||||
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), | |||||
zap.Duration("cost", cost), | |||||
) | |||||
} |
@@ -0,0 +1,10 @@ | |||||
package mw | |||||
import ( | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// 检查权限, 签名等等 | |||||
func Auth(c *gin.Context) { | |||||
} |
@@ -0,0 +1,51 @@ | |||||
package mw | |||||
import ( | |||||
"applet/app/e" | |||||
"applet/app/utils" | |||||
"errors" | |||||
"github.com/dgrijalva/jwt-go" | |||||
"strings" | |||||
"time" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// AuthJWT is jwt middleware | |||||
func AuthJWT(c *gin.Context) { | |||||
//获取请求头中的Authorization | |||||
authHeader := c.Request.Header.Get("Authorization") | |||||
if authHeader == "" { | |||||
e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 不能为空")) | |||||
return | |||||
} | |||||
//拆分Authorization字段获取token字符串 | |||||
parts := strings.SplitN(authHeader, " ", 2) | |||||
if !(len(parts) == 2 && parts[0] == "Bearer") { | |||||
e.OutErr(c, e.ERR_TOKEN_FORMAT, errors.New("token 格式不对")) | |||||
return | |||||
} | |||||
//验证token字符串 | |||||
claim, err := utils.ParseToken(parts[1]) | |||||
if err != nil { | |||||
v, _ := err.(*jwt.ValidationError) | |||||
if v.Errors == jwt.ValidationErrorExpired { | |||||
e.OutErr(c, e.ERR_TOKEN_EXPIRE, errors.New("token 过期已失效")) | |||||
return | |||||
} | |||||
e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 验证失败")) | |||||
return | |||||
} | |||||
//过期判断 | |||||
if time.Now().Unix() > claim.ExpiresAt { | |||||
e.OutErr(c, e.ERR_TOKEN_EXPIRE, errors.New("token 过期已失效")) | |||||
return | |||||
} | |||||
//设置上下文信息 | |||||
c.Set("master_id", claim.MasterId) | |||||
c.Next() | |||||
} |
@@ -0,0 +1,30 @@ | |||||
package mw | |||||
import ( | |||||
"errors" | |||||
"net/http" | |||||
"strconv" | |||||
"github.com/afex/hystrix-go/hystrix" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// 熔断器, 此组件需要在gin.Recovery中间之前进行调用, 否则可能会导致panic时候, 无法recovery, 正确顺序如下 | |||||
//r.Use(BreakerWrapper) | |||||
//r.Use(gin.Recovery()) | |||||
func Breaker(c *gin.Context) { | |||||
name := c.Request.Method + "-" + c.Request.RequestURI | |||||
hystrix.Do(name, func() error { | |||||
c.Next() | |||||
statusCode := c.Writer.Status() | |||||
if statusCode >= http.StatusInternalServerError { | |||||
return errors.New("status code " + strconv.Itoa(statusCode)) | |||||
} | |||||
return nil | |||||
}, func(e error) error { | |||||
if e == hystrix.ErrCircuitOpen { | |||||
c.String(http.StatusAccepted, "请稍后重试") //todo 修改报错方法 | |||||
} | |||||
return e | |||||
}) | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package mw | |||||
import ( | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// 修改传过来的头部字段 | |||||
func ChangeHeader(c *gin.Context) { | |||||
appvserison := c.GetHeader("AppVersionName") | |||||
if appvserison == "" { | |||||
appvserison = c.GetHeader("app_version_name") | |||||
} | |||||
if appvserison != "" { | |||||
c.Request.Header.Add("app_version_name", appvserison) | |||||
} | |||||
c.Next() | |||||
} |
@@ -0,0 +1,34 @@ | |||||
package mw | |||||
import ( | |||||
"applet/app/e" | |||||
"applet/app/utils" | |||||
"bytes" | |||||
"fmt" | |||||
"github.com/gin-gonic/gin" | |||||
"io/ioutil" | |||||
) | |||||
// CheckSign is 中间件 用来检查签名 | |||||
func CheckSign(c *gin.Context) { | |||||
bools := utils.SignCheck(c) | |||||
if bools == false { | |||||
e.OutErr(c, 400, e.NewErr(400, "签名校验错误,请求失败")) | |||||
return | |||||
} | |||||
c.Next() | |||||
} | |||||
func CheckBody(c *gin.Context) { | |||||
if utils.GetApiVersion(c) > 0 { | |||||
body, _ := ioutil.ReadAll(c.Request.Body) | |||||
fmt.Println(string(body)) | |||||
if string(body) != "" { | |||||
str := utils.ResultAesDecrypt(c, string(body)) | |||||
if str != "" { | |||||
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(str))) | |||||
} | |||||
} | |||||
} | |||||
c.Next() | |||||
} |
@@ -0,0 +1,11 @@ | |||||
package mw | |||||
import ( | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// 检查设备等, 把头部信息下放到hdl可以获取 | |||||
func Checker(c *gin.Context) { | |||||
// 校验平台支持 | |||||
c.Next() | |||||
} |
@@ -0,0 +1,29 @@ | |||||
package mw | |||||
import ( | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// cors跨域 | |||||
func Cors(c *gin.Context) { | |||||
// 放行所有OPTIONS方法 | |||||
if c.Request.Method == "OPTIONS" { | |||||
c.AbortWithStatus(204) | |||||
return | |||||
} | |||||
origin := c.Request.Header.Get("Origin") // 请求头部 | |||||
if origin != "" { | |||||
c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问来源域 | |||||
c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求 | |||||
// header的类型 | |||||
c.Header("Access-Control-Allow-Headers", "Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma,X-Mx-ReqToken") | |||||
// 允许跨域设置,可以返回其他子段 | |||||
// 跨域关键设置 让浏览器可以解析 | |||||
c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") | |||||
c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒 | |||||
c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true | |||||
c.Set("Content-Type", "Application/json") // 设置返回格式是json | |||||
} | |||||
c.Next() | |||||
} |
@@ -0,0 +1,136 @@ | |||||
package mw | |||||
import ( | |||||
"crypto/sha1" | |||||
"encoding/base64" | |||||
"errors" | |||||
"io" | |||||
"github.com/dchest/uniuri" | |||||
"github.com/gin-contrib/sessions" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// csrf,xsrf检查 | |||||
const ( | |||||
csrfSecret = "csrfSecret" | |||||
csrfSalt = "csrfSalt" | |||||
csrfToken = "csrfToken" | |||||
) | |||||
var defaultIgnoreMethods = []string{"GET", "HEAD", "OPTIONS"} | |||||
var defaultErrorFunc = func(c *gin.Context) { | |||||
panic(errors.New("CSRF token mismatch")) | |||||
} | |||||
var defaultTokenGetter = func(c *gin.Context) string { | |||||
r := c.Request | |||||
if t := r.FormValue("_csrf"); len(t) > 0 { | |||||
return t | |||||
} else if t := r.URL.Query().Get("_csrf"); len(t) > 0 { | |||||
return t | |||||
} else if t := r.Header.Get("X-CSRF-TOKEN"); len(t) > 0 { | |||||
return t | |||||
} else if t := r.Header.Get("X-XSRF-TOKEN"); len(t) > 0 { | |||||
return t | |||||
} | |||||
return "" | |||||
} | |||||
// Options stores configurations for a CSRF middleware. | |||||
type Options struct { | |||||
Secret string | |||||
IgnoreMethods []string | |||||
ErrorFunc gin.HandlerFunc | |||||
TokenGetter func(c *gin.Context) string | |||||
} | |||||
func tokenize(secret, salt string) string { | |||||
h := sha1.New() | |||||
io.WriteString(h, salt+"-"+secret) | |||||
hash := base64.URLEncoding.EncodeToString(h.Sum(nil)) | |||||
return hash | |||||
} | |||||
func inArray(arr []string, value string) bool { | |||||
inarr := false | |||||
for _, v := range arr { | |||||
if v == value { | |||||
inarr = true | |||||
break | |||||
} | |||||
} | |||||
return inarr | |||||
} | |||||
// Middleware validates CSRF token. | |||||
func Middleware(options Options) gin.HandlerFunc { | |||||
ignoreMethods := options.IgnoreMethods | |||||
errorFunc := options.ErrorFunc | |||||
tokenGetter := options.TokenGetter | |||||
if ignoreMethods == nil { | |||||
ignoreMethods = defaultIgnoreMethods | |||||
} | |||||
if errorFunc == nil { | |||||
errorFunc = defaultErrorFunc | |||||
} | |||||
if tokenGetter == nil { | |||||
tokenGetter = defaultTokenGetter | |||||
} | |||||
return func(c *gin.Context) { | |||||
session := sessions.Default(c) | |||||
c.Set(csrfSecret, options.Secret) | |||||
if inArray(ignoreMethods, c.Request.Method) { | |||||
c.Next() | |||||
return | |||||
} | |||||
salt, ok := session.Get(csrfSalt).(string) | |||||
if !ok || len(salt) == 0 { | |||||
errorFunc(c) | |||||
return | |||||
} | |||||
token := tokenGetter(c) | |||||
if tokenize(options.Secret, salt) != token { | |||||
errorFunc(c) | |||||
return | |||||
} | |||||
c.Next() | |||||
} | |||||
} | |||||
// GetToken returns a CSRF token. | |||||
func GetToken(c *gin.Context) string { | |||||
session := sessions.Default(c) | |||||
secret := c.MustGet(csrfSecret).(string) | |||||
if t, ok := c.Get(csrfToken); ok { | |||||
return t.(string) | |||||
} | |||||
salt, ok := session.Get(csrfSalt).(string) | |||||
if !ok { | |||||
salt = uniuri.New() | |||||
session.Set(csrfSalt, salt) | |||||
session.Save() | |||||
} | |||||
token := tokenize(secret, salt) | |||||
c.Set(csrfToken, token) | |||||
return token | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package mw | |||||
import ( | |||||
"fmt" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// DB is 中间件 用来检查master_id是否有对应的数据库engine | |||||
func DB(c *gin.Context) { | |||||
fmt.Println(c.Request.Header) | |||||
masterID := c.GetHeader("master_id") | |||||
fmt.Println("master_id", masterID) | |||||
if masterID == "" { | |||||
fmt.Println("not found master_id found MasterId start") | |||||
masterID = c.GetHeader("MasterId") | |||||
fmt.Println("MasterId", masterID) | |||||
// if masterID still emtpy | |||||
} | |||||
fmt.Println("master_id", masterID) | |||||
c.Set("mid", masterID) | |||||
c.Next() | |||||
} |
@@ -0,0 +1,18 @@ | |||||
package mw | |||||
import ( | |||||
"applet/app/e" | |||||
"applet/app/utils" | |||||
"fmt" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
func InitReqValidator(c *gin.Context) { | |||||
// 初始化翻译器 | |||||
if err := utils.ValidatorTransInitZh(); err != nil { | |||||
e.OutErr(c, e.ERR, fmt.Sprintf("init trans failed, err:%v\n", err)) | |||||
return | |||||
} | |||||
c.Next() | |||||
} |
@@ -0,0 +1,58 @@ | |||||
package mw | |||||
import ( | |||||
"bytes" | |||||
"io/ioutil" | |||||
"github.com/gin-gonic/gin" | |||||
"applet/app/utils" | |||||
"applet/app/utils/cache" | |||||
) | |||||
// 限流器 | |||||
func Limiter(c *gin.Context) { | |||||
limit := 100 // 限流次数 | |||||
ttl := 1 // 限流过期时间 | |||||
ip := c.ClientIP() | |||||
// 读取token或者ip | |||||
token := c.GetHeader("Authorization") | |||||
// 判断是否已经超出限额次数 | |||||
method := c.Request.Method | |||||
host := c.Request.Host | |||||
uri := c.Request.URL.String() | |||||
buf := make([]byte, 2048) | |||||
num, _ := c.Request.Body.Read(buf) | |||||
body := buf[:num] | |||||
// Write body back | |||||
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) | |||||
Md5 := utils.Md5(ip + token + method + host + uri + string(body)) | |||||
if cache.Exists(Md5) { | |||||
c.AbortWithStatusJSON(429, gin.H{ | |||||
"code": 429, | |||||
"msg": "don't repeat the request", | |||||
"data": struct{}{}, | |||||
}) | |||||
return | |||||
} | |||||
// 2s后没返回自动释放 | |||||
go cache.SetEx(Md5, "0", ttl) | |||||
key := "LIMITER_" + ip | |||||
reqs, _ := cache.GetInt(key) | |||||
if reqs >= limit { | |||||
c.AbortWithStatusJSON(429, gin.H{ | |||||
"code": 429, | |||||
"msg": "too many requests", | |||||
"data": struct{}{}, | |||||
}) | |||||
return | |||||
} | |||||
if reqs > 0 { | |||||
go cache.Incr(key) | |||||
} else { | |||||
go cache.SetEx(key, 1, ttl) | |||||
} | |||||
c.Next() | |||||
go cache.Del(Md5) | |||||
} |
@@ -0,0 +1,57 @@ | |||||
package mw | |||||
import ( | |||||
"net" | |||||
"net/http" | |||||
"net/http/httputil" | |||||
"os" | |||||
"runtime/debug" | |||||
"strings" | |||||
"github.com/gin-gonic/gin" | |||||
"go.uber.org/zap" | |||||
) | |||||
func Recovery(logger *zap.Logger, stack bool) gin.HandlerFunc { | |||||
return func(c *gin.Context) { | |||||
defer func() { | |||||
if err := recover(); err != nil { | |||||
var brokenPipe bool | |||||
if ne, ok := err.(*net.OpError); ok { | |||||
if se, ok := ne.Err.(*os.SyscallError); ok { | |||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { | |||||
brokenPipe = true | |||||
} | |||||
} | |||||
} | |||||
httpRequest, _ := httputil.DumpRequest(c.Request, false) | |||||
if brokenPipe { | |||||
logger.Error(c.Request.URL.Path, | |||||
zap.Any("error", err), | |||||
zap.String("request", string(httpRequest)), | |||||
) | |||||
// If the connection is dead, we can't write a status to it. | |||||
c.Error(err.(error)) | |||||
c.Abort() | |||||
return | |||||
} | |||||
if stack { | |||||
logger.Error("[Recovery from panic]", | |||||
zap.Any("error", err), | |||||
zap.String("request", string(httpRequest)), | |||||
zap.String("stack", string(debug.Stack())), | |||||
) | |||||
} else { | |||||
logger.Error("[Recovery from panic]", | |||||
zap.Any("error", err), | |||||
zap.String("request", string(httpRequest)), | |||||
) | |||||
} | |||||
c.AbortWithStatus(http.StatusInternalServerError) | |||||
} | |||||
}() | |||||
c.Next() | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
package mw | |||||
import ( | |||||
"applet/app/e" | |||||
"applet/app/md" | |||||
"applet/app/utils" | |||||
"applet/app/utils/cache" | |||||
"bytes" | |||||
"fmt" | |||||
"github.com/gin-gonic/gin" | |||||
"io/ioutil" | |||||
) | |||||
// 自己实现一个type gin.ResponseWriter interface | |||||
type responseWriter struct { | |||||
gin.ResponseWriter | |||||
b *bytes.Buffer | |||||
} | |||||
// 重写Write([]byte) (int, error) | |||||
func (w responseWriter) Write(b []byte) (int, error) { | |||||
//向一个bytes.buffer中再写一份数据 | |||||
w.b.Write(b) | |||||
//完成gin.Context.Writer.Write()原有功能 | |||||
return w.ResponseWriter.Write(b) | |||||
} | |||||
// RequestCache is cache middleware | |||||
func RequestCache(c *gin.Context) { | |||||
tempMasterId, _ := c.Get("master_id") | |||||
masterId := tempMasterId.(string) | |||||
uri := c.Request.RequestURI | |||||
md5Params := dealBodyParams(c) | |||||
cacheKey := fmt.Sprintf(md.ZhimengDataRequestCacheKey, masterId, uri, md5Params) | |||||
//自己实现一个type gin.ResponseWriter | |||||
writer := responseWriter{ | |||||
c.Writer, | |||||
bytes.NewBuffer([]byte{}), | |||||
} | |||||
isUse, _ := cache.GetInt(md.ZhimengIsUseCacheKey) | |||||
var res = map[string]interface{}{} | |||||
cache.GetJson(cacheKey, &res) | |||||
if res["data"] != nil && isUse == 1 { | |||||
e.OutSuc(c, res["data"], nil) | |||||
return | |||||
} | |||||
c.Writer = writer | |||||
c.Next() | |||||
} | |||||
func dealBodyParams(c *gin.Context) (md5Params string) { | |||||
body, err := c.GetRawData() | |||||
if err != nil { | |||||
panic(err) | |||||
} | |||||
fmt.Println(">>>>>>>>>>string<<<<<<<<<<<", string(body)) | |||||
md5Params = utils.Md5(string(body)) | |||||
//TODO::把读过的字节流重新放到body | |||||
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) | |||||
return | |||||
} |
@@ -0,0 +1,40 @@ | |||||
package router | |||||
import ( | |||||
"applet/app/cfg" | |||||
"applet/app/mw" | |||||
"github.com/gin-gonic/gin" | |||||
) | |||||
// 初始化路由 | |||||
func Init() *gin.Engine { | |||||
// debug, release, test 项目阶段 | |||||
mode := "release" | |||||
if cfg.Debug { | |||||
mode = "debug" | |||||
} | |||||
gin.SetMode(mode) | |||||
//创建一个新的启动器 | |||||
r := gin.New() | |||||
r.Use(mw.ChangeHeader) | |||||
// 是否打印访问日志, 在非正式环境都打印 | |||||
if mode != "release" { | |||||
r.Use(gin.Logger()) | |||||
} | |||||
r.Use(gin.Recovery()) | |||||
// r.Use(mw.Limiter) | |||||
//r.LoadHTMLGlob("static/html/*") | |||||
r.GET("/favicon.ico", func(c *gin.Context) { | |||||
c.Status(204) | |||||
}) | |||||
r.NoRoute(func(c *gin.Context) { | |||||
c.JSON(404, gin.H{"code": 404, "msg": "page not found", "data": []struct{}{}}) | |||||
}) | |||||
r.NoMethod(func(c *gin.Context) { | |||||
c.JSON(405, gin.H{"code": 405, "msg": "method not allowed", "data": []struct{}{}}) | |||||
}) | |||||
r.Use(mw.Cors) | |||||
return r | |||||
} |
@@ -0,0 +1,89 @@ | |||||
package task | |||||
import ( | |||||
"applet/app/db" | |||||
taskMd "applet/app/task/md" | |||||
"applet/app/utils/logx" | |||||
"github.com/robfig/cron/v3" | |||||
"time" | |||||
) | |||||
var ( | |||||
timer *cron.Cron | |||||
jobs = map[string]func(){} | |||||
baseEntryId cron.EntryID | |||||
entryIds []cron.EntryID | |||||
ch = make(chan int, 30) | |||||
workerNum = 15 // 智盟跟单并发数量 | |||||
) | |||||
func Init() { | |||||
// 初始化任务列表 | |||||
initTasks() | |||||
var err error | |||||
timer = cron.New() | |||||
// reload为初始化数据库方法 | |||||
if baseEntryId, err = timer.AddFunc("@every 15m", reload); err != nil { | |||||
_ = logx.Fatal(err) | |||||
} | |||||
} | |||||
func Run() { | |||||
reload() | |||||
timer.Start() | |||||
_ = logx.Info("auto tasks running...") | |||||
} | |||||
func reload() { | |||||
// 重新初始化数据库 | |||||
sysCfgDb := db.SysCfgDb{} | |||||
sysCfgDb.Set() | |||||
cronList, _ := sysCfgDb.SysCfgGetCron() | |||||
if cronList != nil { | |||||
// 删除原有所有任务 | |||||
if len(entryIds) > 0 { | |||||
for _, v := range entryIds { | |||||
if v != baseEntryId { | |||||
timer.Remove(v) | |||||
} | |||||
} | |||||
entryIds = nil | |||||
} | |||||
var ( | |||||
entryId cron.EntryID | |||||
err error | |||||
) | |||||
for _, vv := range *cronList { | |||||
if _, ok := jobs[vv.K]; ok && vv.V != "" { | |||||
// fmt.Println(vv.Val) | |||||
if entryId, err = timer.AddFunc(vv.V, doTask(vv.K)); err == nil { | |||||
entryIds = append(entryIds, entryId) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
func doTask(fnName string) func() { | |||||
return func() { | |||||
begin := time.Now().Local() | |||||
jobs[fnName]() | |||||
end := time.Now().Local() | |||||
logx.Infof( | |||||
" AutoTask <%s> started at <%s>, ended at <%s> duration <%s>", | |||||
fnName, | |||||
begin.Format("2006-01-02 15:04:05.000"), | |||||
end.Format("2006-01-02 15:04:05.000"), | |||||
time.Duration(end.UnixNano()-begin.UnixNano()).String(), | |||||
) | |||||
} | |||||
} | |||||
// 增加自动任务队列 | |||||
func initTasks() { | |||||
jobs[taskMd.DataCollectCronHaodankuRealNews] = taskDataCollectCronHaodankuRealNews // | |||||
} |
@@ -0,0 +1,5 @@ | |||||
package md | |||||
const ( | |||||
DataCollectCronHaodankuRealNews = "data_collect_cron_haodanku_real_news" | |||||
) |
@@ -0,0 +1,12 @@ | |||||
package md | |||||
type RealNewsData struct { | |||||
Id int `json:"id"` | |||||
Key string `json:"key"` | |||||
CateId string `json:"cate_id"` | |||||
Images string `json:"images"` | |||||
Platform string `json:"platform"` | |||||
Content string `json:"content"` | |||||
OldContent string `json:"old_content"` | |||||
StartTime int64 `json:"start_time"` | |||||
} |
@@ -0,0 +1,137 @@ | |||||
package svc | |||||
import ( | |||||
"applet/app/db" | |||||
"applet/app/es/md" | |||||
"applet/app/lib/haodanku" | |||||
md2 "applet/app/task/md" | |||||
"applet/app/utils" | |||||
"code.fnuoos.com/go_rely_warehouse/zyos_go_es.git/es" | |||||
md3 "code.fnuoos.com/go_rely_warehouse/zyos_go_third_party_api.git/md" | |||||
"code.fnuoos.com/go_rely_warehouse/zyos_go_third_party_api.git/zhimeng" | |||||
"fmt" | |||||
"github.com/syyongx/php2go" | |||||
"strings" | |||||
"time" | |||||
) | |||||
func DataCollectCronHaodankuRealNews() { | |||||
key := md.ZhiosRealNewsData | |||||
//es.CreateIndexIfNotExists(key, md.ZhiosRealNewsDataField) | |||||
ids := []int{10001, 10002, 10003, 10004} | |||||
size := 100 | |||||
now := time.Now() | |||||
fmt.Println("============", time.Since(now)) | |||||
sysCfgDb := db.SysCfgDb{} | |||||
sysCfgDb.Set() | |||||
third_zm_app_key := sysCfgDb.SysCfgGetOneData("third_zm_app_key") | |||||
third_zm_app_secret := sysCfgDb.SysCfgGetOneData("third_zm_app_secret") | |||||
for _, v := range ids { | |||||
for i := 1; i <= 10; i++ { | |||||
tianMaoChaoZhiData, err := haodanku.HaodankuRealTime(utils.IntToStr(v), utils.IntToStr(i), utils.IntToStr(size), "") | |||||
if err != nil { | |||||
return | |||||
} | |||||
if tianMaoChaoZhiData == nil { | |||||
continue | |||||
} | |||||
platformMap := map[string]string{ | |||||
"淘宝": "taobao", | |||||
"天猫": "taobao", | |||||
"京东": "jd", | |||||
} | |||||
for _, item := range tianMaoChaoZhiData { | |||||
var tmp = md2.RealNewsData{ | |||||
Id: utils.StrToInt(item.WireId), | |||||
CateId: utils.SerializeStr(item.CateId), | |||||
Images: utils.SerializeStr(item.Images), | |||||
Platform: item.Platform, | |||||
StartTime: utils.AnyToInt64(item.Starttime), | |||||
OldContent: utils.SerializeStr(item.List), | |||||
} | |||||
tmp.Key = php2go.Md5("hdk_real_news_" + item.WireId) | |||||
var uniqueId = tmp.Key | |||||
doc, _ := es.FirstDoc(key, uniqueId) | |||||
if doc == nil { | |||||
goodsList := make([]map[string]string, 0) | |||||
for k1, v1 := range item.List { | |||||
if item.Platform == "1" { | |||||
item.Platform = "淘宝" | |||||
} | |||||
if item.Platform == "2" { | |||||
item.Platform = "京东" | |||||
} | |||||
if v1.ItemId != "" { | |||||
tmp1 := map[string]string{ | |||||
"content": "去购买>\n", | |||||
"gid": v1.ItemId, | |||||
"is_click": "1", | |||||
"platform_type": platformMap[item.Platform], | |||||
"url": "", | |||||
} | |||||
if item.Platform == "京东" && strings.Contains(v1.ItemId, "http") { | |||||
itemId := "" | |||||
if strings.Contains(v1.ItemId, "jd.") { | |||||
time.Sleep(time.Microsecond * 10) // 等待500毫秒 | |||||
//gid = decode | |||||
postData := make(map[string]interface{}) | |||||
// 配置智盟 appkey 和 sk | |||||
postData["appkey"] = third_zm_app_key | |||||
postData["secret_key"] = third_zm_app_secret | |||||
postData["size"] = "2" | |||||
postData["p"] = "1" | |||||
postData["is_url"] = "1" | |||||
postData["keyword"] = v1.ItemId | |||||
jdData, err := zhimeng.NewZhiMengJingDongSDK(). | |||||
SelectFunction("new_getgoods"). | |||||
WithArgs(postData). | |||||
Result() | |||||
if err == nil { | |||||
itemInfo := &md3.MoreDetailResponse{} | |||||
if err := jdData.ToStruct(itemInfo); err == nil { | |||||
itemId = itemInfo.GID | |||||
if itemInfo.GID == "" || itemInfo.GID == "100038437991" { | |||||
itemId = "" | |||||
} | |||||
} | |||||
} else { | |||||
itemId = "" | |||||
} | |||||
} | |||||
if itemId != "" { | |||||
tmp1["gid"] = itemId | |||||
} else { | |||||
tmp1["gid"] = "" | |||||
tmp1["is_click"] = "0" | |||||
tmp1["content"] = "\n" + v1.ItemId + "\n" | |||||
} | |||||
} | |||||
goodsList = append(goodsList, tmp1) | |||||
} else { | |||||
tmp1 := map[string]string{ | |||||
"content": v1.Content, | |||||
"gid": "", | |||||
"is_click": "0", | |||||
"platform_type": "", | |||||
"url": "", | |||||
} | |||||
if k1 > 0 { | |||||
tmp1["content"] = "\n" + tmp1["content"] | |||||
} | |||||
goodsList = append(goodsList, tmp1) | |||||
} | |||||
} | |||||
tmp.Content = utils.SerializeStr(goodsList) | |||||
createDocRet, err := es.CreateDoc(key, uniqueId, tmp) | |||||
if err != nil { | |||||
return | |||||
} | |||||
fmt.Printf("CreateDoc ==> %+v \n\n", createDocRet) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
fmt.Println("==========================end", time.Since(now)) | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package task | |||||
import ( | |||||
"applet/app/task/svc" | |||||
"math/rand" | |||||
"time" | |||||
) | |||||
func taskDataCollectCronHaodankuRealNews() { | |||||
for { | |||||
if len(ch) > workerNum { | |||||
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) | |||||
} else { | |||||
goto START | |||||
} | |||||
} | |||||
START: | |||||
ch <- 1 | |||||
svc.DataCollectCronHaodankuRealNews() | |||||
<-ch | |||||
} |
@@ -0,0 +1,123 @@ | |||||
package utils | |||||
import ( | |||||
"bytes" | |||||
"crypto/aes" | |||||
"crypto/cipher" | |||||
"fmt" | |||||
) | |||||
func AesEncrypt(rawData, key []byte) ([]byte, error) { | |||||
block, err := aes.NewCipher(key) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
blockSize := block.BlockSize() | |||||
rawData = PKCS5Padding(rawData, blockSize) | |||||
// rawData = ZeroPadding(rawData, block.BlockSize()) | |||||
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) | |||||
encrypted := make([]byte, len(rawData)) | |||||
// 根据CryptBlocks方法的说明,如下方式初始化encrypted也可以 | |||||
// encrypted := rawData | |||||
blockMode.CryptBlocks(encrypted, rawData) | |||||
return encrypted, nil | |||||
} | |||||
func AesDecrypt(encrypted, key []byte) ([]byte, error) { | |||||
block, err := aes.NewCipher(key) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
blockSize := block.BlockSize() | |||||
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) | |||||
rawData := make([]byte, len(encrypted)) | |||||
// rawData := encrypted | |||||
blockMode.CryptBlocks(rawData, encrypted) | |||||
rawData = PKCS5UnPadding(rawData) | |||||
// rawData = ZeroUnPadding(rawData) | |||||
return rawData, nil | |||||
} | |||||
func ZeroPadding(cipherText []byte, blockSize int) []byte { | |||||
padding := blockSize - len(cipherText)%blockSize | |||||
padText := bytes.Repeat([]byte{0}, padding) | |||||
return append(cipherText, padText...) | |||||
} | |||||
func ZeroUnPadding(rawData []byte) []byte { | |||||
length := len(rawData) | |||||
unPadding := int(rawData[length-1]) | |||||
return rawData[:(length - unPadding)] | |||||
} | |||||
func PKCS5Padding(cipherText []byte, blockSize int) []byte { | |||||
padding := blockSize - len(cipherText)%blockSize | |||||
padText := bytes.Repeat([]byte{byte(padding)}, padding) | |||||
return append(cipherText, padText...) | |||||
} | |||||
func PKCS5UnPadding(rawData []byte) []byte { | |||||
length := len(rawData) | |||||
// 去掉最后一个字节 unPadding 次 | |||||
unPadding := int(rawData[length-1]) | |||||
return rawData[:(length - unPadding)] | |||||
} | |||||
// 填充0 | |||||
func zeroFill(key *string) { | |||||
l := len(*key) | |||||
if l != 16 && l != 24 && l != 32 { | |||||
if l < 16 { | |||||
*key = *key + fmt.Sprintf("%0*d", 16-l, 0) | |||||
} else if l < 24 { | |||||
*key = *key + fmt.Sprintf("%0*d", 24-l, 0) | |||||
} else if l < 32 { | |||||
*key = *key + fmt.Sprintf("%0*d", 32-l, 0) | |||||
} else { | |||||
*key = string([]byte(*key)[:32]) | |||||
} | |||||
} | |||||
} | |||||
type AesCrypt struct { | |||||
Key []byte | |||||
Iv []byte | |||||
} | |||||
func (a *AesCrypt) Encrypt(data []byte) ([]byte, error) { | |||||
aesBlockEncrypt, err := aes.NewCipher(a.Key) | |||||
if err != nil { | |||||
println(err.Error()) | |||||
return nil, err | |||||
} | |||||
content := pKCS5Padding(data, aesBlockEncrypt.BlockSize()) | |||||
cipherBytes := make([]byte, len(content)) | |||||
aesEncrypt := cipher.NewCBCEncrypter(aesBlockEncrypt, a.Iv) | |||||
aesEncrypt.CryptBlocks(cipherBytes, content) | |||||
return cipherBytes, nil | |||||
} | |||||
func (a *AesCrypt) Decrypt(src []byte) (data []byte, err error) { | |||||
decrypted := make([]byte, len(src)) | |||||
var aesBlockDecrypt cipher.Block | |||||
aesBlockDecrypt, err = aes.NewCipher(a.Key) | |||||
if err != nil { | |||||
println(err.Error()) | |||||
return nil, err | |||||
} | |||||
aesDecrypt := cipher.NewCBCDecrypter(aesBlockDecrypt, a.Iv) | |||||
aesDecrypt.CryptBlocks(decrypted, src) | |||||
return pKCS5Trimming(decrypted), nil | |||||
} | |||||
func pKCS5Padding(cipherText []byte, blockSize int) []byte { | |||||
padding := blockSize - len(cipherText)%blockSize | |||||
padText := bytes.Repeat([]byte{byte(padding)}, padding) | |||||
return append(cipherText, padText...) | |||||
} | |||||
func pKCS5Trimming(encrypt []byte) []byte { | |||||
padding := encrypt[len(encrypt)-1] | |||||
return encrypt[:len(encrypt)-int(padding)] | |||||
} |
@@ -0,0 +1,69 @@ | |||||
package utils | |||||
import ( | |||||
"applet/app/lib/auth" | |||||
"errors" | |||||
"time" | |||||
"github.com/dgrijalva/jwt-go" | |||||
) | |||||
// GenToken 生成JWT | |||||
func GenToken(masterId string) (aToken, rToken string, err error) { | |||||
// 创建一个我们自己的声明 | |||||
c := auth.JWTMaster{ | |||||
masterId, | |||||
jwt.StandardClaims{ | |||||
ExpiresAt: time.Now().Add(auth.TokenExpireDuration).Unix(), // 过期时间 | |||||
Issuer: "zyos_zhimeng", // 签发人 | |||||
}, | |||||
} | |||||
// 使用指定的签名方法创建签名对象 | |||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) | |||||
// 使用指定的secret签名并获得完整的编码后的字符串token | |||||
aToken, err = token.SignedString(auth.Secret) | |||||
if err != nil { | |||||
return "", "", err | |||||
} | |||||
// rToken 生成 | |||||
rc := auth.JWTMaster{ | |||||
masterId, | |||||
jwt.StandardClaims{ | |||||
ExpiresAt: time.Now().Add(auth.RefreshTokenExpireDuration).Unix(), // 过期时间 | |||||
Issuer: "zyos_zhimeng", // 签发人 | |||||
}, | |||||
} | |||||
rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, rc).SignedString(auth.RSecret) | |||||
return | |||||
} | |||||
// ParseToken 解析JWT | |||||
func ParseToken(tokenString string) (*auth.JWTMaster, error) { | |||||
// 解析token | |||||
token, err := jwt.ParseWithClaims(tokenString, &auth.JWTMaster{}, func(token *jwt.Token) (i interface{}, err error) { | |||||
return auth.Secret, nil | |||||
}) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if claims, ok := token.Claims.(*auth.JWTMaster); ok && token.Valid { // 校验token | |||||
return claims, nil | |||||
} | |||||
return nil, errors.New("invalid token") | |||||
} | |||||
// ParseRToken 解析JWT | |||||
func ParseRToken(tokenString string) (*auth.JWTMaster, error) { | |||||
// 解析token | |||||
token, err := jwt.ParseWithClaims(tokenString, &auth.JWTMaster{}, func(token *jwt.Token) (i interface{}, err error) { | |||||
return auth.RSecret, nil | |||||
}) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
if claims, ok := token.Claims.(*auth.JWTMaster); ok && token.Valid { // 校验token | |||||
return claims, nil | |||||
} | |||||
return nil, errors.New("invalid token") | |||||
} |
@@ -0,0 +1,95 @@ | |||||
package utils | |||||
import ( | |||||
"encoding/base64" | |||||
"fmt" | |||||
) | |||||
const ( | |||||
Base64Std = iota | |||||
Base64Url | |||||
Base64RawStd | |||||
Base64RawUrl | |||||
) | |||||
func Base64StdEncode(str interface{}) string { | |||||
return Base64Encode(str, Base64Std) | |||||
} | |||||
func Base64StdDecode(str interface{}) string { | |||||
return Base64Decode(str, Base64Std) | |||||
} | |||||
func Base64UrlEncode(str interface{}) string { | |||||
return Base64Encode(str, Base64Url) | |||||
} | |||||
func Base64UrlDecode(str interface{}) string { | |||||
return Base64Decode(str, Base64Url) | |||||
} | |||||
func Base64RawStdEncode(str interface{}) string { | |||||
return Base64Encode(str, Base64RawStd) | |||||
} | |||||
func Base64RawStdDecode(str interface{}) string { | |||||
return Base64Decode(str, Base64RawStd) | |||||
} | |||||
func Base64RawUrlEncode(str interface{}) string { | |||||
return Base64Encode(str, Base64RawUrl) | |||||
} | |||||
func Base64RawUrlDecode(str interface{}) string { | |||||
return Base64Decode(str, Base64RawUrl) | |||||
} | |||||
func Base64Encode(str interface{}, encode int) string { | |||||
newEncode := base64Encode(encode) | |||||
if newEncode == nil { | |||||
return "" | |||||
} | |||||
switch v := str.(type) { | |||||
case string: | |||||
return newEncode.EncodeToString([]byte(v)) | |||||
case []byte: | |||||
return newEncode.EncodeToString(v) | |||||
} | |||||
return newEncode.EncodeToString([]byte(fmt.Sprint(str))) | |||||
} | |||||
func Base64Decode(str interface{}, encode int) string { | |||||
var err error | |||||
var b []byte | |||||
newEncode := base64Encode(encode) | |||||
if newEncode == nil { | |||||
return "" | |||||
} | |||||
switch v := str.(type) { | |||||
case string: | |||||
b, err = newEncode.DecodeString(v) | |||||
case []byte: | |||||
b, err = newEncode.DecodeString(string(v)) | |||||
default: | |||||
return "" | |||||
} | |||||
if err != nil { | |||||
return "" | |||||
} | |||||
return string(b) | |||||
} | |||||
func base64Encode(encode int) *base64.Encoding { | |||||
switch encode { | |||||
case Base64Std: | |||||
return base64.StdEncoding | |||||
case Base64Url: | |||||
return base64.URLEncoding | |||||
case Base64RawStd: | |||||
return base64.RawStdEncoding | |||||
case Base64RawUrl: | |||||
return base64.RawURLEncoding | |||||
default: | |||||
return nil | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
package utils | |||||
import "reflect" | |||||
// 检验一个值是否为空 | |||||
func Empty(val interface{}) bool { | |||||
v := reflect.ValueOf(val) | |||||
switch v.Kind() { | |||||
case reflect.String, reflect.Array: | |||||
return v.Len() == 0 | |||||
case reflect.Map, reflect.Slice: | |||||
return v.Len() == 0 || v.IsNil() | |||||
case reflect.Bool: | |||||
return !v.Bool() | |||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||||
return v.Int() == 0 | |||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |||||
return v.Uint() == 0 | |||||
case reflect.Float32, reflect.Float64: | |||||
return v.Float() == 0 | |||||
case reflect.Interface, reflect.Ptr: | |||||
return v.IsNil() | |||||
} | |||||
return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface()) | |||||
} |
@@ -0,0 +1,422 @@ | |||||
package cache | |||||
import ( | |||||
"errors" | |||||
"fmt" | |||||
"strconv" | |||||
"time" | |||||
) | |||||
const ( | |||||
redisPassword = "sanhu" | |||||
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,107 @@ | |||||
package cache | |||||
import ( | |||||
"fmt" | |||||
"time" | |||||
) | |||||
var c Cache | |||||
type Cache interface { | |||||
// get cached value by key. | |||||
Get(key string) interface{} | |||||
// GetMulti is a batch version of Get. | |||||
GetMulti(keys []string) []interface{} | |||||
// set cached value with key and expire time. | |||||
Put(key string, val interface{}, timeout time.Duration) error | |||||
// delete cached value by key. | |||||
Delete(key string) error | |||||
// increase cached int value by key, as a counter. | |||||
Incr(key string) error | |||||
// decrease cached int value by key, as a counter. | |||||
Decr(key string) error | |||||
// check if cached value exists or not. | |||||
IsExist(key string) bool | |||||
// clear all cache. | |||||
ClearAll() error | |||||
// start gc routine based on config string settings. | |||||
StartAndGC(config string) error | |||||
} | |||||
// Instance is a function create a new Cache Instance | |||||
type Instance func() Cache | |||||
var adapters = make(map[string]Instance) | |||||
// Register makes a cache adapter available by the adapter name. | |||||
// If Register is called twice with the same name or if driver is nil, | |||||
// it panics. | |||||
func Register(name string, adapter Instance) { | |||||
if adapter == nil { | |||||
panic("cache: Register adapter is nil") | |||||
} | |||||
if _, ok := adapters[name]; ok { | |||||
panic("cache: Register called twice for adapter " + name) | |||||
} | |||||
adapters[name] = adapter | |||||
} | |||||
// NewCache Create a new cache driver by adapter name and config string. | |||||
// config need to be correct JSON as string: {"interval":360}. | |||||
// it will start gc automatically. | |||||
func NewCache(adapterName, config string) (adapter Cache, err error) { | |||||
instanceFunc, ok := adapters[adapterName] | |||||
if !ok { | |||||
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) | |||||
return | |||||
} | |||||
adapter = instanceFunc() | |||||
err = adapter.StartAndGC(config) | |||||
if err != nil { | |||||
adapter = nil | |||||
} | |||||
return | |||||
} | |||||
func InitCache(adapterName, config string) (err error) { | |||||
instanceFunc, ok := adapters[adapterName] | |||||
if !ok { | |||||
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) | |||||
return | |||||
} | |||||
c = instanceFunc() | |||||
err = c.StartAndGC(config) | |||||
if err != nil { | |||||
c = nil | |||||
} | |||||
return | |||||
} | |||||
func Get(key string) interface{} { | |||||
return c.Get(key) | |||||
} | |||||
func GetMulti(keys []string) []interface{} { | |||||
return c.GetMulti(keys) | |||||
} | |||||
func Put(key string, val interface{}, ttl time.Duration) error { | |||||
return c.Put(key, val, ttl) | |||||
} | |||||
func Delete(key string) error { | |||||
return c.Delete(key) | |||||
} | |||||
func Incr(key string) error { | |||||
return c.Incr(key) | |||||
} | |||||
func Decr(key string) error { | |||||
return c.Decr(key) | |||||
} | |||||
func IsExist(key string) bool { | |||||
return c.IsExist(key) | |||||
} | |||||
func ClearAll() error { | |||||
return c.ClearAll() | |||||
} | |||||
func StartAndGC(cfg string) error { | |||||
return c.StartAndGC(cfg) | |||||
} |
@@ -0,0 +1,86 @@ | |||||
package cache | |||||
import ( | |||||
"fmt" | |||||
"strconv" | |||||
) | |||||
// GetString convert interface to string. | |||||
func GetString(v interface{}) string { | |||||
switch result := v.(type) { | |||||
case string: | |||||
return result | |||||
case []byte: | |||||
return string(result) | |||||
default: | |||||
if v != nil { | |||||
return fmt.Sprint(result) | |||||
} | |||||
} | |||||
return "" | |||||
} | |||||
// GetInt convert interface to int. | |||||
func GetInt(v interface{}) int { | |||||
switch result := v.(type) { | |||||
case int: | |||||
return result | |||||
case int32: | |||||
return int(result) | |||||
case int64: | |||||
return int(result) | |||||
default: | |||||
if d := GetString(v); d != "" { | |||||
value, _ := strconv.Atoi(d) | |||||
return value | |||||
} | |||||
} | |||||
return 0 | |||||
} | |||||
// GetInt64 convert interface to int64. | |||||
func GetInt64(v interface{}) int64 { | |||||
switch result := v.(type) { | |||||
case int: | |||||
return int64(result) | |||||
case int32: | |||||
return int64(result) | |||||
case int64: | |||||
return result | |||||
default: | |||||
if d := GetString(v); d != "" { | |||||
value, _ := strconv.ParseInt(d, 10, 64) | |||||
return value | |||||
} | |||||
} | |||||
return 0 | |||||
} | |||||
// GetFloat64 convert interface to float64. | |||||
func GetFloat64(v interface{}) float64 { | |||||
switch result := v.(type) { | |||||
case float64: | |||||
return result | |||||
default: | |||||
if d := GetString(v); d != "" { | |||||
value, _ := strconv.ParseFloat(d, 64) | |||||
return value | |||||
} | |||||
} | |||||
return 0 | |||||
} | |||||
// GetBool convert interface to bool. | |||||
func GetBool(v interface{}) bool { | |||||
switch result := v.(type) { | |||||
case bool: | |||||
return result | |||||
default: | |||||
if d := GetString(v); d != "" { | |||||
value, _ := strconv.ParseBool(d) | |||||
return value | |||||
} | |||||
} | |||||
return false | |||||
} |
@@ -0,0 +1,241 @@ | |||||
package cache | |||||
import ( | |||||
"bytes" | |||||
"crypto/md5" | |||||
"encoding/gob" | |||||
"encoding/hex" | |||||
"encoding/json" | |||||
"fmt" | |||||
"io" | |||||
"io/ioutil" | |||||
"os" | |||||
"path/filepath" | |||||
"reflect" | |||||
"strconv" | |||||
"time" | |||||
) | |||||
// FileCacheItem is basic unit of file cache adapter. | |||||
// it contains data and expire time. | |||||
type FileCacheItem struct { | |||||
Data interface{} | |||||
LastAccess time.Time | |||||
Expired time.Time | |||||
} | |||||
// FileCache Config | |||||
var ( | |||||
FileCachePath = "cache" // cache directory | |||||
FileCacheFileSuffix = ".bin" // cache file suffix | |||||
FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files. | |||||
FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever. | |||||
) | |||||
// FileCache is cache adapter for file storage. | |||||
type FileCache struct { | |||||
CachePath string | |||||
FileSuffix string | |||||
DirectoryLevel int | |||||
EmbedExpiry int | |||||
} | |||||
// NewFileCache Create new file cache with no config. | |||||
// the level and expiry need set in method StartAndGC as config string. | |||||
func NewFileCache() Cache { | |||||
// return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} | |||||
return &FileCache{} | |||||
} | |||||
// StartAndGC will start and begin gc for file cache. | |||||
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0} | |||||
func (fc *FileCache) StartAndGC(config string) error { | |||||
var cfg map[string]string | |||||
json.Unmarshal([]byte(config), &cfg) | |||||
if _, ok := cfg["CachePath"]; !ok { | |||||
cfg["CachePath"] = FileCachePath | |||||
} | |||||
if _, ok := cfg["FileSuffix"]; !ok { | |||||
cfg["FileSuffix"] = FileCacheFileSuffix | |||||
} | |||||
if _, ok := cfg["DirectoryLevel"]; !ok { | |||||
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) | |||||
} | |||||
if _, ok := cfg["EmbedExpiry"]; !ok { | |||||
cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10) | |||||
} | |||||
fc.CachePath = cfg["CachePath"] | |||||
fc.FileSuffix = cfg["FileSuffix"] | |||||
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"]) | |||||
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"]) | |||||
fc.Init() | |||||
return nil | |||||
} | |||||
// Init will make new dir for file cache if not exist. | |||||
func (fc *FileCache) Init() { | |||||
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle | |||||
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle | |||||
} | |||||
} | |||||
// get cached file name. it's md5 encoded. | |||||
func (fc *FileCache) getCacheFileName(key string) string { | |||||
m := md5.New() | |||||
io.WriteString(m, key) | |||||
keyMd5 := hex.EncodeToString(m.Sum(nil)) | |||||
cachePath := fc.CachePath | |||||
switch fc.DirectoryLevel { | |||||
case 2: | |||||
cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4]) | |||||
case 1: | |||||
cachePath = filepath.Join(cachePath, keyMd5[0:2]) | |||||
} | |||||
if ok, _ := exists(cachePath); !ok { // todo : error handle | |||||
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle | |||||
} | |||||
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)) | |||||
} | |||||
// Get value from file cache. | |||||
// if non-exist or expired, return empty string. | |||||
func (fc *FileCache) Get(key string) interface{} { | |||||
fileData, err := FileGetContents(fc.getCacheFileName(key)) | |||||
if err != nil { | |||||
return "" | |||||
} | |||||
var to FileCacheItem | |||||
GobDecode(fileData, &to) | |||||
if to.Expired.Before(time.Now()) { | |||||
return "" | |||||
} | |||||
return to.Data | |||||
} | |||||
// GetMulti gets values from file cache. | |||||
// if non-exist or expired, return empty string. | |||||
func (fc *FileCache) GetMulti(keys []string) []interface{} { | |||||
var rc []interface{} | |||||
for _, key := range keys { | |||||
rc = append(rc, fc.Get(key)) | |||||
} | |||||
return rc | |||||
} | |||||
// Put value into file cache. | |||||
// timeout means how long to keep this file, unit of ms. | |||||
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever. | |||||
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error { | |||||
gob.Register(val) | |||||
item := FileCacheItem{Data: val} | |||||
if timeout == FileCacheEmbedExpiry { | |||||
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years | |||||
} else { | |||||
item.Expired = time.Now().Add(timeout) | |||||
} | |||||
item.LastAccess = time.Now() | |||||
data, err := GobEncode(item) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return FilePutContents(fc.getCacheFileName(key), data) | |||||
} | |||||
// Delete file cache value. | |||||
func (fc *FileCache) Delete(key string) error { | |||||
filename := fc.getCacheFileName(key) | |||||
if ok, _ := exists(filename); ok { | |||||
return os.Remove(filename) | |||||
} | |||||
return nil | |||||
} | |||||
// Incr will increase cached int value. | |||||
// fc value is saving forever unless Delete. | |||||
func (fc *FileCache) Incr(key string) error { | |||||
data := fc.Get(key) | |||||
var incr int | |||||
if reflect.TypeOf(data).Name() != "int" { | |||||
incr = 0 | |||||
} else { | |||||
incr = data.(int) + 1 | |||||
} | |||||
fc.Put(key, incr, FileCacheEmbedExpiry) | |||||
return nil | |||||
} | |||||
// Decr will decrease cached int value. | |||||
func (fc *FileCache) Decr(key string) error { | |||||
data := fc.Get(key) | |||||
var decr int | |||||
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { | |||||
decr = 0 | |||||
} else { | |||||
decr = data.(int) - 1 | |||||
} | |||||
fc.Put(key, decr, FileCacheEmbedExpiry) | |||||
return nil | |||||
} | |||||
// IsExist check value is exist. | |||||
func (fc *FileCache) IsExist(key string) bool { | |||||
ret, _ := exists(fc.getCacheFileName(key)) | |||||
return ret | |||||
} | |||||
// ClearAll will clean cached files. | |||||
// not implemented. | |||||
func (fc *FileCache) ClearAll() error { | |||||
return nil | |||||
} | |||||
// check file exist. | |||||
func exists(path string) (bool, error) { | |||||
_, err := os.Stat(path) | |||||
if err == nil { | |||||
return true, nil | |||||
} | |||||
if os.IsNotExist(err) { | |||||
return false, nil | |||||
} | |||||
return false, err | |||||
} | |||||
// FileGetContents Get bytes to file. | |||||
// if non-exist, create this file. | |||||
func FileGetContents(filename string) (data []byte, e error) { | |||||
return ioutil.ReadFile(filename) | |||||
} | |||||
// FilePutContents Put bytes to file. | |||||
// if non-exist, create this file. | |||||
func FilePutContents(filename string, content []byte) error { | |||||
return ioutil.WriteFile(filename, content, os.ModePerm) | |||||
} | |||||
// GobEncode Gob encodes file cache item. | |||||
func GobEncode(data interface{}) ([]byte, error) { | |||||
buf := bytes.NewBuffer(nil) | |||||
enc := gob.NewEncoder(buf) | |||||
err := enc.Encode(data) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return buf.Bytes(), err | |||||
} | |||||
// GobDecode Gob decodes file cache item. | |||||
func GobDecode(data []byte, to *FileCacheItem) error { | |||||
buf := bytes.NewBuffer(data) | |||||
dec := gob.NewDecoder(buf) | |||||
return dec.Decode(&to) | |||||
} | |||||
func init() { | |||||
Register("file", NewFileCache) | |||||
} |
@@ -0,0 +1,239 @@ | |||||
package cache | |||||
import ( | |||||
"encoding/json" | |||||
"errors" | |||||
"sync" | |||||
"time" | |||||
) | |||||
var ( | |||||
// DefaultEvery means the clock time of recycling the expired cache items in memory. | |||||
DefaultEvery = 60 // 1 minute | |||||
) | |||||
// MemoryItem store memory cache item. | |||||
type MemoryItem struct { | |||||
val interface{} | |||||
createdTime time.Time | |||||
lifespan time.Duration | |||||
} | |||||
func (mi *MemoryItem) isExpire() bool { | |||||
// 0 means forever | |||||
if mi.lifespan == 0 { | |||||
return false | |||||
} | |||||
return time.Now().Sub(mi.createdTime) > mi.lifespan | |||||
} | |||||
// MemoryCache is Memory cache adapter. | |||||
// it contains a RW locker for safe map storage. | |||||
type MemoryCache struct { | |||||
sync.RWMutex | |||||
dur time.Duration | |||||
items map[string]*MemoryItem | |||||
Every int // run an expiration check Every clock time | |||||
} | |||||
// NewMemoryCache returns a new MemoryCache. | |||||
func NewMemoryCache() Cache { | |||||
cache := MemoryCache{items: make(map[string]*MemoryItem)} | |||||
return &cache | |||||
} | |||||
// Get cache from memory. | |||||
// if non-existed or expired, return nil. | |||||
func (bc *MemoryCache) Get(name string) interface{} { | |||||
bc.RLock() | |||||
defer bc.RUnlock() | |||||
if itm, ok := bc.items[name]; ok { | |||||
if itm.isExpire() { | |||||
return nil | |||||
} | |||||
return itm.val | |||||
} | |||||
return nil | |||||
} | |||||
// GetMulti gets caches from memory. | |||||
// if non-existed or expired, return nil. | |||||
func (bc *MemoryCache) GetMulti(names []string) []interface{} { | |||||
var rc []interface{} | |||||
for _, name := range names { | |||||
rc = append(rc, bc.Get(name)) | |||||
} | |||||
return rc | |||||
} | |||||
// Put cache to memory. | |||||
// if lifespan is 0, it will be forever till restart. | |||||
func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error { | |||||
bc.Lock() | |||||
defer bc.Unlock() | |||||
bc.items[name] = &MemoryItem{ | |||||
val: value, | |||||
createdTime: time.Now(), | |||||
lifespan: lifespan, | |||||
} | |||||
return nil | |||||
} | |||||
// Delete cache in memory. | |||||
func (bc *MemoryCache) Delete(name string) error { | |||||
bc.Lock() | |||||
defer bc.Unlock() | |||||
if _, ok := bc.items[name]; !ok { | |||||
return errors.New("key not exist") | |||||
} | |||||
delete(bc.items, name) | |||||
if _, ok := bc.items[name]; ok { | |||||
return errors.New("delete key error") | |||||
} | |||||
return nil | |||||
} | |||||
// Incr increase cache counter in memory. | |||||
// it supports int,int32,int64,uint,uint32,uint64. | |||||
func (bc *MemoryCache) Incr(key string) error { | |||||
bc.RLock() | |||||
defer bc.RUnlock() | |||||
itm, ok := bc.items[key] | |||||
if !ok { | |||||
return errors.New("key not exist") | |||||
} | |||||
switch itm.val.(type) { | |||||
case int: | |||||
itm.val = itm.val.(int) + 1 | |||||
case int32: | |||||
itm.val = itm.val.(int32) + 1 | |||||
case int64: | |||||
itm.val = itm.val.(int64) + 1 | |||||
case uint: | |||||
itm.val = itm.val.(uint) + 1 | |||||
case uint32: | |||||
itm.val = itm.val.(uint32) + 1 | |||||
case uint64: | |||||
itm.val = itm.val.(uint64) + 1 | |||||
default: | |||||
return errors.New("item val is not (u)int (u)int32 (u)int64") | |||||
} | |||||
return nil | |||||
} | |||||
// Decr decrease counter in memory. | |||||
func (bc *MemoryCache) Decr(key string) error { | |||||
bc.RLock() | |||||
defer bc.RUnlock() | |||||
itm, ok := bc.items[key] | |||||
if !ok { | |||||
return errors.New("key not exist") | |||||
} | |||||
switch itm.val.(type) { | |||||
case int: | |||||
itm.val = itm.val.(int) - 1 | |||||
case int64: | |||||
itm.val = itm.val.(int64) - 1 | |||||
case int32: | |||||
itm.val = itm.val.(int32) - 1 | |||||
case uint: | |||||
if itm.val.(uint) > 0 { | |||||
itm.val = itm.val.(uint) - 1 | |||||
} else { | |||||
return errors.New("item val is less than 0") | |||||
} | |||||
case uint32: | |||||
if itm.val.(uint32) > 0 { | |||||
itm.val = itm.val.(uint32) - 1 | |||||
} else { | |||||
return errors.New("item val is less than 0") | |||||
} | |||||
case uint64: | |||||
if itm.val.(uint64) > 0 { | |||||
itm.val = itm.val.(uint64) - 1 | |||||
} else { | |||||
return errors.New("item val is less than 0") | |||||
} | |||||
default: | |||||
return errors.New("item val is not int int64 int32") | |||||
} | |||||
return nil | |||||
} | |||||
// IsExist check cache exist in memory. | |||||
func (bc *MemoryCache) IsExist(name string) bool { | |||||
bc.RLock() | |||||
defer bc.RUnlock() | |||||
if v, ok := bc.items[name]; ok { | |||||
return !v.isExpire() | |||||
} | |||||
return false | |||||
} | |||||
// ClearAll will delete all cache in memory. | |||||
func (bc *MemoryCache) ClearAll() error { | |||||
bc.Lock() | |||||
defer bc.Unlock() | |||||
bc.items = make(map[string]*MemoryItem) | |||||
return nil | |||||
} | |||||
// StartAndGC start memory cache. it will check expiration in every clock time. | |||||
func (bc *MemoryCache) StartAndGC(config string) error { | |||||
var cf map[string]int | |||||
json.Unmarshal([]byte(config), &cf) | |||||
if _, ok := cf["interval"]; !ok { | |||||
cf = make(map[string]int) | |||||
cf["interval"] = DefaultEvery | |||||
} | |||||
dur := time.Duration(cf["interval"]) * time.Second | |||||
bc.Every = cf["interval"] | |||||
bc.dur = dur | |||||
go bc.vacuum() | |||||
return nil | |||||
} | |||||
// check expiration. | |||||
func (bc *MemoryCache) vacuum() { | |||||
bc.RLock() | |||||
every := bc.Every | |||||
bc.RUnlock() | |||||
if every < 1 { | |||||
return | |||||
} | |||||
for { | |||||
<-time.After(bc.dur) | |||||
if bc.items == nil { | |||||
return | |||||
} | |||||
if keys := bc.expiredKeys(); len(keys) != 0 { | |||||
bc.clearItems(keys) | |||||
} | |||||
} | |||||
} | |||||
// expiredKeys returns key list which are expired. | |||||
func (bc *MemoryCache) expiredKeys() (keys []string) { | |||||
bc.RLock() | |||||
defer bc.RUnlock() | |||||
for key, itm := range bc.items { | |||||
if itm.isExpire() { | |||||
keys = append(keys, key) | |||||
} | |||||
} | |||||
return | |||||
} | |||||
// clearItems removes all the items which key in keys. | |||||
func (bc *MemoryCache) clearItems(keys []string) { | |||||
bc.Lock() | |||||
defer bc.Unlock() | |||||
for _, key := range keys { | |||||
delete(bc.items, key) | |||||
} | |||||
} | |||||
func init() { | |||||
Register("memory", NewMemoryCache) | |||||
} |
@@ -0,0 +1,408 @@ | |||||
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 | |||||
} | |||||
func LPushMax(key string, data ...interface{}) (interface{}, error) { | |||||
// set | |||||
return Do("LPUSH", key, data) | |||||
} |
@@ -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,322 @@ | |||||
package 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 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 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) | |||||
} |
@@ -0,0 +1,19 @@ | |||||
package utils | |||||
import ( | |||||
"crypto/md5" | |||||
"encoding/base64" | |||||
"fmt" | |||||
) | |||||
func GetMd5(raw []byte) string { | |||||
h := md5.New() | |||||
h.Write(raw) | |||||
return fmt.Sprintf("%x", h.Sum(nil)) | |||||
} | |||||
func GetBase64Md5(raw []byte) string { | |||||
h := md5.New() | |||||
h.Write(raw) | |||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)) | |||||
} |
@@ -0,0 +1,209 @@ | |||||
package 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,25 @@ | |||||
package utils | |||||
import ( | |||||
"fmt" | |||||
"os" | |||||
"strconv" | |||||
"time" | |||||
) | |||||
func Debug(args ...interface{}) { | |||||
s := "" | |||||
l := len(args) | |||||
if l < 1 { | |||||
fmt.Println("please input some data") | |||||
os.Exit(0) | |||||
} | |||||
i := 1 | |||||
for _, v := range args { | |||||
s += fmt.Sprintf("【"+strconv.Itoa(i)+"】: %#v\n", v) | |||||
i++ | |||||
} | |||||
s = "******************** 【DEBUG - " + time.Now().Format("2006-01-02 15:04:05") + "】 ********************\n" + s + "******************** 【DEBUG - END】 ********************\n" | |||||
fmt.Println(s) | |||||
os.Exit(0) | |||||
} |
@@ -0,0 +1,37 @@ | |||||
package utils | |||||
func RemoveDuplicateString(elms []string) []string { | |||||
res := make([]string, 0, len(elms)) | |||||
temp := map[string]struct{}{} | |||||
for _, item := range elms { | |||||
if _, ok := temp[item]; !ok { | |||||
temp[item] = struct{}{} | |||||
res = append(res, item) | |||||
} | |||||
} | |||||
return res | |||||
} | |||||
func RemoveDuplicateInt(elms []int) []int { | |||||
res := make([]int, 0, len(elms)) | |||||
temp := map[int]struct{}{} | |||||
for _, item := range elms { | |||||
if _, ok := temp[item]; !ok { | |||||
temp[item] = struct{}{} | |||||
res = append(res, item) | |||||
} | |||||
} | |||||
return res | |||||
} | |||||
func RemoveDuplicateInt64(elms []int64) []int64 { | |||||
res := make([]int64, 0, len(elms)) | |||||
temp := map[int64]struct{}{} | |||||
for _, item := range elms { | |||||
if _, ok := temp[item]; !ok { | |||||
temp[item] = struct{}{} | |||||
res = append(res, item) | |||||
} | |||||
} | |||||
return res | |||||
} |
@@ -0,0 +1,22 @@ | |||||
package 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+".log", 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,29 @@ | |||||
package utils | |||||
import "os" | |||||
// 判断所给路径文件、文件夹是否存在 | |||||
func Exists(path string) bool { | |||||
_, err := os.Stat(path) //os.Stat获取文件信息 | |||||
if err != nil { | |||||
if os.IsExist(err) { | |||||
return true | |||||
} | |||||
return false | |||||
} | |||||
return true | |||||
} | |||||
// 判断所给路径是否为文件夹 | |||||
func IsDir(path string) bool { | |||||
s, err := os.Stat(path) | |||||
if err != nil { | |||||
return false | |||||
} | |||||
return s.IsDir() | |||||
} | |||||
// 判断所给路径是否为文件 | |||||
func IsFile(path string) bool { | |||||
return !IsDir(path) | |||||
} |
@@ -0,0 +1,59 @@ | |||||
package utils | |||||
import ( | |||||
"math" | |||||
) | |||||
func CouponFormat(data string) string { | |||||
switch data { | |||||
case "0.00", "0", "": | |||||
return "" | |||||
default: | |||||
return Int64ToStr(FloatToInt64(StrToFloat64(data))) | |||||
} | |||||
} | |||||
func CommissionFormat(data string) string { | |||||
if StrToFloat64(data) > 0 { | |||||
return data | |||||
} | |||||
return "" | |||||
} | |||||
func HideString(src string, hLen int) string { | |||||
str := []rune(src) | |||||
if hLen == 0 { | |||||
hLen = 4 | |||||
} | |||||
hideStr := "" | |||||
for i := 0; i < hLen; i++ { | |||||
hideStr += "*" | |||||
} | |||||
hideLen := len(str) / 2 | |||||
showLen := len(str) - hideLen | |||||
if hideLen == 0 || showLen == 0 { | |||||
return hideStr | |||||
} | |||||
subLen := showLen / 2 | |||||
if subLen == 0 { | |||||
return string(str[:showLen]) + hideStr | |||||
} | |||||
s := string(str[:subLen]) | |||||
s += hideStr | |||||
s += string(str[len(str)-subLen:]) | |||||
return s | |||||
} | |||||
//SaleCountFormat is 格式化销量 | |||||
func SaleCountFormat(s string) string { | |||||
return s + "已售" | |||||
} | |||||
// 小数格式化 | |||||
func FloatFormat(f float64, i int) float64 { | |||||
if i > 14 { | |||||
return f | |||||
} | |||||
p := math.Pow10(i) | |||||
return float64(int64((f+0.000000000000009)*p)) / p | |||||
} |
@@ -0,0 +1,146 @@ | |||||
package utils | |||||
import ( | |||||
"errors" | |||||
"math" | |||||
"net" | |||||
"net/http" | |||||
"strings" | |||||
) | |||||
func GetIP(r *http.Request) string { | |||||
ip := ClientPublicIP(r) | |||||
if ip == "" { | |||||
ip = ClientIP(r) | |||||
} | |||||
if ip == "" { | |||||
ip = "0000" | |||||
} | |||||
return ip | |||||
} | |||||
// HasLocalIPddr 检测 IP 地址字符串是否是内网地址 | |||||
// Deprecated: 此为一个错误名称错误拼写的函数,计划在将来移除,请使用 HasLocalIPAddr 函数 | |||||
func HasLocalIPddr(ip string) bool { | |||||
return HasLocalIPAddr(ip) | |||||
} | |||||
// HasLocalIPAddr 检测 IP 地址字符串是否是内网地址 | |||||
func HasLocalIPAddr(ip string) bool { | |||||
return HasLocalIP(net.ParseIP(ip)) | |||||
} | |||||
// HasLocalIP 检测 IP 地址是否是内网地址 | |||||
// 通过直接对比ip段范围效率更高,详见:https://github.com/thinkeridea/go-extend/issues/2 | |||||
func HasLocalIP(ip net.IP) bool { | |||||
if ip.IsLoopback() { | |||||
return true | |||||
} | |||||
ip4 := ip.To4() | |||||
if ip4 == nil { | |||||
return false | |||||
} | |||||
return ip4[0] == 10 || // 10.0.0.0/8 | |||||
(ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12 | |||||
(ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16 | |||||
(ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16 | |||||
} | |||||
// ClientIP 尽最大努力实现获取客户端 IP 的算法。 | |||||
// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。 | |||||
func ClientIP(r *http.Request) string { | |||||
ip := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-For"), ",")[0]) | |||||
if ip != "" { | |||||
return ip | |||||
} | |||||
ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")) | |||||
if ip != "" { | |||||
return ip | |||||
} | |||||
if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil { | |||||
return ip | |||||
} | |||||
return "" | |||||
} | |||||
// ClientPublicIP 尽最大努力实现获取客户端公网 IP 的算法。 | |||||
// 解析 X-Real-IP 和 X-Forwarded-For 以便于反向代理(nginx 或 haproxy)可以正常工作。 | |||||
func ClientPublicIP(r *http.Request) string { | |||||
var ip string | |||||
for _, ip = range strings.Split(r.Header.Get("X-Forwarded-For"), ",") { | |||||
if ip = strings.TrimSpace(ip); ip != "" && !HasLocalIPAddr(ip) { | |||||
return ip | |||||
} | |||||
} | |||||
if ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")); ip != "" && !HasLocalIPAddr(ip) { | |||||
return ip | |||||
} | |||||
if ip = RemoteIP(r); !HasLocalIPAddr(ip) { | |||||
return ip | |||||
} | |||||
return "" | |||||
} | |||||
// RemoteIP 通过 RemoteAddr 获取 IP 地址, 只是一个快速解析方法。 | |||||
func RemoteIP(r *http.Request) string { | |||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr) | |||||
return ip | |||||
} | |||||
// IPString2Long 把ip字符串转为数值 | |||||
func IPString2Long(ip string) (uint, error) { | |||||
b := net.ParseIP(ip).To4() | |||||
if b == nil { | |||||
return 0, errors.New("invalid ipv4 format") | |||||
} | |||||
return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24, nil | |||||
} | |||||
// Long2IPString 把数值转为ip字符串 | |||||
func Long2IPString(i uint) (string, error) { | |||||
if i > math.MaxUint32 { | |||||
return "", errors.New("beyond the scope of ipv4") | |||||
} | |||||
ip := make(net.IP, net.IPv4len) | |||||
ip[0] = byte(i >> 24) | |||||
ip[1] = byte(i >> 16) | |||||
ip[2] = byte(i >> 8) | |||||
ip[3] = byte(i) | |||||
return ip.String(), nil | |||||
} | |||||
// IP2Long 把net.IP转为数值 | |||||
func IP2Long(ip net.IP) (uint, error) { | |||||
b := ip.To4() | |||||
if b == nil { | |||||
return 0, errors.New("invalid ipv4 format") | |||||
} | |||||
return uint(b[3]) | uint(b[2])<<8 | uint(b[1])<<16 | uint(b[0])<<24, nil | |||||
} | |||||
// Long2IP 把数值转为net.IP | |||||
func Long2IP(i uint) (net.IP, error) { | |||||
if i > math.MaxUint32 { | |||||
return nil, errors.New("beyond the scope of ipv4") | |||||
} | |||||
ip := make(net.IP, net.IPv4len) | |||||
ip[0] = byte(i >> 24) | |||||
ip[1] = byte(i >> 16) | |||||
ip[2] = byte(i >> 8) | |||||
ip[3] = byte(i) | |||||
return ip, nil | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package utils | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
) | |||||
func JsonMarshal(interface{}) { | |||||
} | |||||
// 不科学计数法 | |||||
func JsonDecode(data []byte, v interface{}) error { | |||||
d := json.NewDecoder(bytes.NewReader(data)) | |||||
d.UseNumber() | |||||
return d.Decode(v) | |||||
} |
@@ -0,0 +1,245 @@ | |||||
package 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 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 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,9 @@ | |||||
package utils | |||||
// GetOneKeyOfMapString 取出Map的一个key | |||||
func GetOneKeyOfMapString(collection map[string]string) string { | |||||
for k := range collection { | |||||
return k | |||||
} | |||||
return "" | |||||
} |
@@ -0,0 +1,341 @@ | |||||
package utils | |||||
import ( | |||||
"encoding/json" | |||||
"fmt" | |||||
"reflect" | |||||
"strconv" | |||||
"strings" | |||||
) | |||||
func Map2Struct(vals map[string]interface{}, dst interface{}) (err error) { | |||||
return Map2StructByTag(vals, dst, "json") | |||||
} | |||||
func Map2StructByTag(vals map[string]interface{}, dst interface{}, structTag string) (err error) { | |||||
defer func() { | |||||
e := recover() | |||||
if e != nil { | |||||
if v, ok := e.(error); ok { | |||||
err = fmt.Errorf("Panic: %v", v.Error()) | |||||
} else { | |||||
err = fmt.Errorf("Panic: %v", e) | |||||
} | |||||
} | |||||
}() | |||||
pt := reflect.TypeOf(dst) | |||||
pv := reflect.ValueOf(dst) | |||||
if pv.Kind() != reflect.Ptr || pv.Elem().Kind() != reflect.Struct { | |||||
return fmt.Errorf("not a pointer of struct") | |||||
} | |||||
var f reflect.StructField | |||||
var ft reflect.Type | |||||
var fv reflect.Value | |||||
for i := 0; i < pt.Elem().NumField(); i++ { | |||||
f = pt.Elem().Field(i) | |||||
fv = pv.Elem().Field(i) | |||||
ft = f.Type | |||||
if f.Anonymous || !fv.CanSet() { | |||||
continue | |||||
} | |||||
tag := f.Tag.Get(structTag) | |||||
name, option := parseTag(tag) | |||||
if name == "-" { | |||||
continue | |||||
} | |||||
if name == "" { | |||||
name = strings.ToLower(f.Name) | |||||
} | |||||
val, ok := vals[name] | |||||
if !ok { | |||||
if option == "required" { | |||||
return fmt.Errorf("'%v' not found", name) | |||||
} | |||||
if len(option) != 0 { | |||||
val = option // default value | |||||
} else { | |||||
//fv.Set(reflect.Zero(ft)) // TODO set zero value or just ignore it? | |||||
continue | |||||
} | |||||
} | |||||
// convert or set value to field | |||||
vv := reflect.ValueOf(val) | |||||
vt := reflect.TypeOf(val) | |||||
if vt.Kind() != reflect.String { | |||||
// try to assign and convert | |||||
if vt.AssignableTo(ft) { | |||||
fv.Set(vv) | |||||
continue | |||||
} | |||||
if vt.ConvertibleTo(ft) { | |||||
fv.Set(vv.Convert(ft)) | |||||
continue | |||||
} | |||||
return fmt.Errorf("value type not match: field=%v(%v) value=%v(%v)", f.Name, ft.Kind(), val, vt.Kind()) | |||||
} | |||||
s := strings.TrimSpace(vv.String()) | |||||
if len(s) == 0 && option == "required" { | |||||
return fmt.Errorf("value of required argument can't not be empty") | |||||
} | |||||
fk := ft.Kind() | |||||
// convert string to value | |||||
if fk == reflect.Ptr && ft.Elem().Kind() == reflect.String { | |||||
fv.Set(reflect.ValueOf(&s)) | |||||
continue | |||||
} | |||||
if fk == reflect.Ptr || fk == reflect.Struct { | |||||
err = convertJsonValue(s, name, fv) | |||||
} else if fk == reflect.Slice { | |||||
err = convertSlice(s, f.Name, ft, fv) | |||||
} else { | |||||
err = convertValue(fk, s, f.Name, fv) | |||||
} | |||||
if err != nil { | |||||
return err | |||||
} | |||||
continue | |||||
} | |||||
return nil | |||||
} | |||||
func Struct2Map(s interface{}) map[string]interface{} { | |||||
return Struct2MapByTag(s, "json") | |||||
} | |||||
func Struct2MapByTag(s interface{}, tagName string) map[string]interface{} { | |||||
t := reflect.TypeOf(s) | |||||
v := reflect.ValueOf(s) | |||||
if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { | |||||
t = t.Elem() | |||||
v = v.Elem() | |||||
} | |||||
if v.Kind() != reflect.Struct { | |||||
return nil | |||||
} | |||||
m := make(map[string]interface{}) | |||||
for i := 0; i < t.NumField(); i++ { | |||||
fv := v.Field(i) | |||||
ft := t.Field(i) | |||||
if !fv.CanInterface() { | |||||
continue | |||||
} | |||||
if ft.PkgPath != "" { // unexported | |||||
continue | |||||
} | |||||
var name string | |||||
var option string | |||||
tag := ft.Tag.Get(tagName) | |||||
if tag != "" { | |||||
ts := strings.Split(tag, ",") | |||||
if len(ts) == 1 { | |||||
name = ts[0] | |||||
} else if len(ts) > 1 { | |||||
name = ts[0] | |||||
option = ts[1] | |||||
} | |||||
if name == "-" { | |||||
continue // skip this field | |||||
} | |||||
if name == "" { | |||||
name = strings.ToLower(ft.Name) | |||||
} | |||||
if option == "omitempty" { | |||||
if isEmpty(&fv) { | |||||
continue // skip empty field | |||||
} | |||||
} | |||||
} else { | |||||
name = strings.ToLower(ft.Name) | |||||
} | |||||
if ft.Anonymous && fv.Kind() == reflect.Ptr && fv.IsNil() { | |||||
continue | |||||
} | |||||
if (ft.Anonymous && fv.Kind() == reflect.Struct) || | |||||
(ft.Anonymous && fv.Kind() == reflect.Ptr && fv.Elem().Kind() == reflect.Struct) { | |||||
// embedded struct | |||||
embedded := Struct2MapByTag(fv.Interface(), tagName) | |||||
for embName, embValue := range embedded { | |||||
m[embName] = embValue | |||||
} | |||||
} else if option == "string" { | |||||
kind := fv.Kind() | |||||
if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 { | |||||
m[name] = strconv.FormatInt(fv.Int(), 10) | |||||
} else if kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 { | |||||
m[name] = strconv.FormatUint(fv.Uint(), 10) | |||||
} else if kind == reflect.Float32 || kind == reflect.Float64 { | |||||
m[name] = strconv.FormatFloat(fv.Float(), 'f', 2, 64) | |||||
} else { | |||||
m[name] = fv.Interface() | |||||
} | |||||
} else { | |||||
m[name] = fv.Interface() | |||||
} | |||||
} | |||||
return m | |||||
} | |||||
func isEmpty(v *reflect.Value) bool { | |||||
k := v.Kind() | |||||
if k == reflect.Bool { | |||||
return v.Bool() == false | |||||
} else if reflect.Int < k && k < reflect.Int64 { | |||||
return v.Int() == 0 | |||||
} else if reflect.Uint < k && k < reflect.Uintptr { | |||||
return v.Uint() == 0 | |||||
} else if k == reflect.Float32 || k == reflect.Float64 { | |||||
return v.Float() == 0 | |||||
} else if k == reflect.Array || k == reflect.Map || k == reflect.Slice || k == reflect.String { | |||||
return v.Len() == 0 | |||||
} else if k == reflect.Interface || k == reflect.Ptr { | |||||
return v.IsNil() | |||||
} | |||||
return false | |||||
} | |||||
func convertSlice(s string, name string, ft reflect.Type, fv reflect.Value) error { | |||||
var err error | |||||
et := ft.Elem() | |||||
if et.Kind() == reflect.Ptr || et.Kind() == reflect.Struct { | |||||
return convertJsonValue(s, name, fv) | |||||
} | |||||
ss := strings.Split(s, ",") | |||||
if len(s) == 0 || len(ss) == 0 { | |||||
return nil | |||||
} | |||||
fs := reflect.MakeSlice(ft, 0, len(ss)) | |||||
for _, si := range ss { | |||||
ev := reflect.New(et).Elem() | |||||
err = convertValue(et.Kind(), si, name, ev) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
fs = reflect.Append(fs, ev) | |||||
} | |||||
fv.Set(fs) | |||||
return nil | |||||
} | |||||
func convertJsonValue(s string, name string, fv reflect.Value) error { | |||||
var err error | |||||
d := StringToSlice(s) | |||||
if fv.Kind() == reflect.Ptr { | |||||
if fv.IsNil() { | |||||
fv.Set(reflect.New(fv.Type().Elem())) | |||||
} | |||||
} else { | |||||
fv = fv.Addr() | |||||
} | |||||
err = json.Unmarshal(d, fv.Interface()) | |||||
if err != nil { | |||||
return fmt.Errorf("invalid json '%v': %v, %v", name, err.Error(), s) | |||||
} | |||||
return nil | |||||
} | |||||
func convertValue(kind reflect.Kind, s string, name string, fv reflect.Value) error { | |||||
if !fv.CanAddr() { | |||||
return fmt.Errorf("can not addr: %v", name) | |||||
} | |||||
if kind == reflect.String { | |||||
fv.SetString(s) | |||||
return nil | |||||
} | |||||
if kind == reflect.Bool { | |||||
switch s { | |||||
case "true": | |||||
fv.SetBool(true) | |||||
case "false": | |||||
fv.SetBool(false) | |||||
case "1": | |||||
fv.SetBool(true) | |||||
case "0": | |||||
fv.SetBool(false) | |||||
default: | |||||
return fmt.Errorf("invalid bool: %v value=%v", name, s) | |||||
} | |||||
return nil | |||||
} | |||||
if reflect.Int <= kind && kind <= reflect.Int64 { | |||||
i, err := strconv.ParseInt(s, 10, 64) | |||||
if err != nil { | |||||
return fmt.Errorf("invalid int: %v value=%v", name, s) | |||||
} | |||||
fv.SetInt(i) | |||||
} else if reflect.Uint <= kind && kind <= reflect.Uint64 { | |||||
i, err := strconv.ParseUint(s, 10, 64) | |||||
if err != nil { | |||||
return fmt.Errorf("invalid int: %v value=%v", name, s) | |||||
} | |||||
fv.SetUint(i) | |||||
} else if reflect.Float32 == kind || kind == reflect.Float64 { | |||||
i, err := strconv.ParseFloat(s, 64) | |||||
if err != nil { | |||||
return fmt.Errorf("invalid float: %v value=%v", name, s) | |||||
} | |||||
fv.SetFloat(i) | |||||
} else { | |||||
// not support or just ignore it? | |||||
// return fmt.Errorf("type not support: field=%v(%v) value=%v(%v)", name, ft.Kind(), val, vt.Kind()) | |||||
} | |||||
return nil | |||||
} | |||||
func parseTag(tag string) (string, string) { | |||||
tags := strings.Split(tag, ",") | |||||
if len(tags) <= 0 { | |||||
return "", "" | |||||
} | |||||
if len(tags) == 1 { | |||||
return tags[0], "" | |||||
} | |||||
return tags[0], tags[1] | |||||
} |
@@ -0,0 +1,12 @@ | |||||
package utils | |||||
import ( | |||||
"crypto/md5" | |||||
"encoding/hex" | |||||
) | |||||
func Md5(str string) string { | |||||
h := md5.New() | |||||
h.Write([]byte(str)) | |||||
return hex.EncodeToString(h.Sum(nil)) | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package qrcode | |||||
import ( | |||||
"image" | |||||
_ "image/jpeg" | |||||
_ "image/png" | |||||
"os" | |||||
"github.com/makiuchi-d/gozxing" | |||||
"github.com/makiuchi-d/gozxing/qrcode" | |||||
) | |||||
func DecodeFile(fi string) (string, error) { | |||||
file, err := os.Open(fi) | |||||
if err != nil { | |||||
return "", err | |||||
} | |||||
img, _, err := image.Decode(file) | |||||
if err != nil { | |||||
return "", err | |||||
} | |||||
// prepare BinaryBitmap | |||||
bmp, err := gozxing.NewBinaryBitmapFromImage(img) | |||||
if err != nil { | |||||
return "", err | |||||
} | |||||
// decode image | |||||
result, err := qrcode.NewQRCodeReader().Decode(bmp, nil) | |||||
if err != nil { | |||||
return "", err | |||||
} | |||||
return result.String(), nil | |||||
} |
@@ -0,0 +1,43 @@ | |||||
package qrcode | |||||
// 生成登录二维码图片, 方便在网页上显示 | |||||
import ( | |||||
"bytes" | |||||
"encoding/base64" | |||||
"image/jpeg" | |||||
"image/png" | |||||
"github.com/boombuler/barcode" | |||||
"github.com/boombuler/barcode/qr" | |||||
) | |||||
func GetJPGBase64(content string, edges ...int) string { | |||||
edgeLen := 300 | |||||
if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { | |||||
edgeLen = edges[0] | |||||
} | |||||
img, _ := qr.Encode(content, qr.L, qr.Unicode) | |||||
img, _ = barcode.Scale(img, edgeLen, edgeLen) | |||||
emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区 | |||||
jpeg.Encode(emptyBuff, img, nil) | |||||
dist := make([]byte, 50000) // 开辟存储空间 | |||||
base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64 | |||||
return "data:image/png;base64," + string(dist) // 输出图片base64(type = []byte) | |||||
} | |||||
func GetPNGBase64(content string, edges ...int) string { | |||||
edgeLen := 300 | |||||
if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { | |||||
edgeLen = edges[0] | |||||
} | |||||
img, _ := qr.Encode(content, qr.L, qr.Unicode) | |||||
img, _ = barcode.Scale(img, edgeLen, edgeLen) | |||||
emptyBuff := bytes.NewBuffer(nil) // 开辟一个新的空buff缓冲区 | |||||
png.Encode(emptyBuff, img) | |||||
dist := make([]byte, 50000) // 开辟存储空间 | |||||
base64.StdEncoding.Encode(dist, emptyBuff.Bytes()) // buff转成base64 | |||||
return string(dist) // 输出图片base64(type = []byte) | |||||
} |
@@ -0,0 +1,85 @@ | |||||
package qrcode | |||||
// 生成登录二维码图片 | |||||
import ( | |||||
"errors" | |||||
"image" | |||||
"image/jpeg" | |||||
"image/png" | |||||
"os" | |||||
"path/filepath" | |||||
"strings" | |||||
"github.com/boombuler/barcode" | |||||
"github.com/boombuler/barcode/qr" | |||||
) | |||||
func SaveJpegFile(filePath, content string, edges ...int) error { | |||||
edgeLen := 300 | |||||
if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { | |||||
edgeLen = edges[0] | |||||
} | |||||
img, _ := qr.Encode(content, qr.L, qr.Unicode) | |||||
img, _ = barcode.Scale(img, edgeLen, edgeLen) | |||||
return writeFile(filePath, img, "jpg") | |||||
} | |||||
func SavePngFile(filePath, content string, edges ...int) error { | |||||
edgeLen := 300 | |||||
if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { | |||||
edgeLen = edges[0] | |||||
} | |||||
img, _ := qr.Encode(content, qr.L, qr.Unicode) | |||||
img, _ = barcode.Scale(img, edgeLen, edgeLen) | |||||
return writeFile(filePath, img, "png") | |||||
} | |||||
func writeFile(filePath string, img image.Image, format string) error { | |||||
if err := createDir(filePath); err != nil { | |||||
return err | |||||
} | |||||
file, err := os.Create(filePath) | |||||
defer file.Close() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
switch strings.ToLower(format) { | |||||
case "png": | |||||
err = png.Encode(file, img) | |||||
break | |||||
case "jpg": | |||||
err = jpeg.Encode(file, img, nil) | |||||
default: | |||||
return errors.New("format not accept") | |||||
} | |||||
if err != nil { | |||||
return err | |||||
} | |||||
return nil | |||||
} | |||||
func createDir(filePath string) error { | |||||
var err error | |||||
// filePath, _ = filepath.Abs(filePath) | |||||
dirPath := filepath.Dir(filePath) | |||||
dirInfo, err := os.Stat(dirPath) | |||||
if err != nil { | |||||
if !os.IsExist(err) { | |||||
err = os.MkdirAll(dirPath, 0777) | |||||
if err != nil { | |||||
return err | |||||
} | |||||
} else { | |||||
return err | |||||
} | |||||
} else { | |||||
if dirInfo.IsDir() { | |||||
return nil | |||||
} | |||||
return errors.New("directory is a file") | |||||
} | |||||
return nil | |||||
} |
@@ -0,0 +1,39 @@ | |||||
package qrcode | |||||
import ( | |||||
"bytes" | |||||
"image/jpeg" | |||||
"image/png" | |||||
"net/http" | |||||
"github.com/boombuler/barcode" | |||||
"github.com/boombuler/barcode/qr" | |||||
) | |||||
func WritePng(w http.ResponseWriter, content string, edges ...int) error { | |||||
edgeLen := 300 | |||||
if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { | |||||
edgeLen = edges[0] | |||||
} | |||||
img, _ := qr.Encode(content, qr.L, qr.Unicode) | |||||
img, _ = barcode.Scale(img, edgeLen, edgeLen) | |||||
buff := bytes.NewBuffer(nil) | |||||
png.Encode(buff, img) | |||||
w.Header().Set("Content-Type", "image/png") | |||||
_, err := w.Write(buff.Bytes()) | |||||
return err | |||||
} | |||||
func WriteJpg(w http.ResponseWriter, content string, edges ...int) error { | |||||
edgeLen := 300 | |||||
if len(edges) > 0 && edges[0] > 100 && edges[0] < 2000 { | |||||
edgeLen = edges[0] | |||||
} | |||||
img, _ := qr.Encode(content, qr.L, qr.Unicode) | |||||
img, _ = barcode.Scale(img, edgeLen, edgeLen) | |||||
buff := bytes.NewBuffer(nil) | |||||
jpeg.Encode(buff, img, nil) | |||||
w.Header().Set("Content-Type", "image/jpg") | |||||
_, err := w.Write(buff.Bytes()) | |||||
return err | |||||
} |