commit 16144daee369844acad6deaac708178e4fd385bf Author: SmartCanteen <> Date: Tue Feb 20 08:31:01 2024 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a8f686 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c5905d1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,34 @@ +# 多重构建,减少镜像大小 +# 构建:使用golang:1.15版本 +FROM golang:1.15 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"] + diff --git a/Dockerfile-task b/Dockerfile-task new file mode 100644 index 0000000..905efe7 --- /dev/null +++ b/Dockerfile-task @@ -0,0 +1,34 @@ +# 多重构建,减少镜像大小 +# 构建:使用golang:1.15版本 +FROM golang:1.15 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_mall_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/zyos_mall_task ./zyos_mall_task + +COPY --from=build /go/release/etc/task.yml /var/zyos/task.yml + +# 启动服务 +CMD ["./zyos_mall_task","-c","/var/zyos/task.yml"] + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e7e30c2 --- /dev/null +++ b/Makefile @@ -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" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca65483 --- /dev/null +++ b/README.md @@ -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 +``` \ No newline at end of file diff --git a/app/cfg/cfg_app.go b/app/cfg/cfg_app.go new file mode 100644 index 0000000..9470942 --- /dev/null +++ b/app/cfg/cfg_app.go @@ -0,0 +1,78 @@ +package cfg + +import ( + "time" +) + +type Config struct { + Debug bool `yaml:"debug"` + Prd bool `yaml:"prd"` + CurlDebug bool `yaml:"curldebug"` + SrvAddr string `yaml:"srv_addr"` + RedisAddr string `yaml:"redis_addr"` + DB DBCfg `yaml:"db"` + Log LogCfg `yaml:"log"` + ArkID ArkIDCfg `yaml:"arkid"` + Admin AdminCfg `yaml:"admin"` + Official OfficialCfg `yaml:"official"` + WxappletFilepath WxappletFilepathCfg `yaml:"wxapplet_filepath"` + Local bool + AppComm AppCommCfg `yaml:"app_comm"` +} + +// 公共模块 +type AppCommCfg struct { + URL string `yaml:"url"` +} + +// OfficialCfg is 官网 + +type OfficialCfg struct { + URL string `yaml:"url"` +} +type WxappletFilepathCfg struct { + URL string `yaml:"url"` +} + +// AdminCfg is 后台接口调用需要 +type AdminCfg struct { + URL string `yaml:"url"` + IURL string `yaml:"iurl"` + AesKey string `yaml:"api_aes_key"` + AesIV string `yaml:"api_aes_iv"` + Host string `yaml:"host"` +} + +type ArkIDCfg struct { + Admin string `yaml:"admin"` + AdminPassword string `yaml:"admin_password"` + Url string `yaml:"url` +} + +//数据库配置结构体 +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"` +} diff --git a/app/cfg/cfg_cache_key.go b/app/cfg/cfg_cache_key.go new file mode 100644 index 0000000..c091909 --- /dev/null +++ b/app/cfg/cfg_cache_key.go @@ -0,0 +1,3 @@ +package cfg + +// 统一管理缓存 diff --git a/app/cfg/init_cache.go b/app/cfg/init_cache.go new file mode 100644 index 0000000..873657f --- /dev/null +++ b/app/cfg/init_cache.go @@ -0,0 +1,9 @@ +package cfg + +import ( + "applet/app/utils/cache" +) + +func InitCache() { + cache.NewRedis(RedisAddr) +} diff --git a/app/cfg/init_cfg.go b/app/cfg/init_cfg.go new file mode 100644 index 0000000..d12e74f --- /dev/null +++ b/app/cfg/init_cfg.go @@ -0,0 +1,60 @@ +package cfg + +import ( + "flag" + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +//配置文件数据,全局变量 +var ( + Debug bool + Prd bool + CurlDebug bool + SrvAddr string + RedisAddr string + DB *DBCfg + Log *LogCfg + ArkID *ArkIDCfg + Admin *AdminCfg + Official *OfficialCfg + WxappletFilepath *WxappletFilepathCfg + Local bool + AppComm *AppCommCfg +) + +//初始化配置文件,将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 + Local = conf.Local + CurlDebug = conf.CurlDebug + DB = &conf.DB + Log = &conf.Log + ArkID = &conf.ArkID + RedisAddr = conf.RedisAddr + SrvAddr = conf.SrvAddr + Admin = &conf.Admin + Official = &conf.Official + WxappletFilepath = &conf.WxappletFilepath + AppComm = &conf.AppComm +} diff --git a/app/cfg/init_log.go b/app/cfg/init_log.go new file mode 100644 index 0000000..0f31eb5 --- /dev/null +++ b/app/cfg/init_log.go @@ -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, + }) +} diff --git a/app/cfg/init_task.go b/app/cfg/init_task.go new file mode 100644 index 0000000..d54079e --- /dev/null +++ b/app/cfg/init_task.go @@ -0,0 +1,45 @@ +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 + Log = &conf.Log + Admin = &conf.Admin + RedisAddr = conf.RedisAddr + Local = conf.Local + AppComm = &conf.AppComm +} + +var MemCache mc.Cache + +func InitMemCache() { + var err error + MemCache, err = mc.NewCache("memory", `{"interval":60}`) + if err != nil { + logx.Fatal(err.Error()) + } +} diff --git a/app/db/db.go b/app/db/db.go new file mode 100644 index 0000000..00630e8 --- /dev/null +++ b/app/db/db.go @@ -0,0 +1,112 @@ +package db + +import ( + "database/sql" + "fmt" + "os" + "time" + + _ "github.com/go-sql-driver/mysql" //必须导入mysql驱动,否则会panic + "xorm.io/xorm" + "xorm.io/xorm/log" + + "applet/app/cfg" + "applet/app/utils/logx" +) + +var Db *xorm.Engine + +//根据DB配置文件初始化数据库 +func InitDB(c *cfg.DBCfg) error { + var ( + err error + f *os.File + ) + //创建Orm引擎 + 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 * time.Second) //设置最长连接时间 + Db.SetMaxOpenConns(c.MaxOpenConns) //设置最大打开连接数 + Db.SetMaxIdleConns(c.MaxIdleConns) //设置连接池的空闲数大小 + if err = Db.Ping(); err != nil { //尝试ping数据库 + return err + } + if c.ShowLog { //根据配置文件设置日志 + Db.ShowSQL(true) //设置是否打印sql + Db.Logger().SetLevel(0) //设置日志等级 + //修改日志文件存放路径文件名是%s.log + path := fmt.Sprintf(c.Path, c.Name) + f, err = os.OpenFile(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 +} + +/********************************************* 公用方法 *********************************************/ + +// 数据批量插入 +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 +} + +// UpdateComm common update +func UpdateComm(Db *xorm.Engine, id interface{}, model interface{}) (int64, error) { + row, err := Db.ID(id).Update(model) + return row, err +} + +// InsertComm common insert +func InsertComm(Db *xorm.Engine, model interface{}) (int64, error) { + row, err := Db.InsertOne(model) + return row, err +} + +// ExecuteOriginalSql 执行原生sql +func ExecuteOriginalSql(session *xorm.Session, sql string) (sql.Result, error) { + result, err := session.Exec(sql) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + return result, nil +} + +// 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 +} + +// InsertCommWithSession common insert +func InsertCommWithSession(session *xorm.Session, model interface{}) (int64, error) { + row, err := session.InsertOne(model) + return row, err +} diff --git a/app/db/db_capital_pool.go b/app/db/db_capital_pool.go new file mode 100644 index 0000000..319fb8a --- /dev/null +++ b/app/db/db_capital_pool.go @@ -0,0 +1,16 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +//UserProfileFindByArkID is get userprofile by arkid +func CapitalPoolByIsUse(Db *xorm.Engine) (*model.CapitalPool, error) { + var m model.CapitalPool + if has, err := Db.Where("is_use = 1").Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} diff --git a/app/db/db_cloud_bundle.go b/app/db/db_cloud_bundle.go new file mode 100644 index 0000000..2182d4a --- /dev/null +++ b/app/db/db_cloud_bundle.go @@ -0,0 +1,40 @@ +package db + +import ( + "applet/app/db/model" + "errors" + + "xorm.io/xorm" +) + +// GetCloudBundleByVersion is 根据版本 获取打包记录 +func GetCloudBundleByVersion(Db *xorm.Engine, appverison string, os int) (*model.CloudBundle, error) { + m := new(model.CloudBundle) + has, err := Db.Where("version = ? and os = ?", appverison, os).Get(m) + if err != nil { + return nil, err + } + if !has { + return nil, errors.New("not Found") + } + return m, nil +} + +// GetCloudBundleByVersionPlatform is 根据版本\os 获取打包记录 +func GetCloudBundleByVersionPlatform(Db *xorm.Engine, appverison string, platform string) (*model.CloudBundle, error) { + m := new(model.CloudBundle) + var tag int + if platform == "ios" { + tag = 2 + } else { + tag = 1 + } + has, err := Db.Where("version = ? and os=?", appverison, tag).Get(m) + if err != nil { + return nil, err + } + if !has { + return nil, errors.New("not Found") + } + return m, nil +} diff --git a/app/db/db_file.go b/app/db/db_file.go new file mode 100644 index 0000000..b9ae8e8 --- /dev/null +++ b/app/db/db_file.go @@ -0,0 +1,33 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +// 通过文件名目录与文件名查找文件 +func FileGetByPFidAndName(Db *xorm.Engine, dirId, fname string) (*model.SysFile, error) { + var f model.SysFile + if has, err := Db.Where("parent_fid = ? AND show_name = ?", dirId, fname).Get(&f); !has || err != nil { + return nil, logx.Warn(err) + } + return &f, nil +} + +// 插入文件信息 +func FileInsert(Db *xorm.Engine, f *model.SysFile) error { + if _, err := Db.InsertOne(f); err != nil { + return logx.Warn(err) + } + return nil +} + +// 文件信息更新 +func FileUpdate(Db *xorm.Engine, f *model.SysFile) error { + if _, err := Db.Where("`fid` = ?", f.Fid).Update(f); err != nil { + return logx.Warn(err) + } + return nil +} diff --git a/app/db/db_regional_agent_base.go b/app/db/db_regional_agent_base.go new file mode 100644 index 0000000..6e517cf --- /dev/null +++ b/app/db/db_regional_agent_base.go @@ -0,0 +1,43 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +type AgentBase struct { + Session *xorm.Session + AgentBase *model.RegionalAgentBase +} + +func NewAgentBase(session *xorm.Session, agentBase *model.RegionalAgentBase) *AgentBase { + return &AgentBase{ + Session: session, + AgentBase: agentBase, + } +} + +func (a AgentBase) GetAgentBaseInfo() bool { + isHas, err := a.Session.Get(a.AgentBase) + if err != nil { + return false + } + if !isHas { + return false + } + return true +} + +// GetCountByRegionalAgentBase 通过传入的参数查询数据(单条) +func GetCountByRegionalAgentBase(Db *xorm.Engine) (*model.RegionalAgentBase, error) { + var m model.RegionalAgentBase + get, err := Db.Get(&m) + if err != nil { + return &m, err + } + if !get { + return &m, logx.Warn("Not found") + } + return &m, nil +} diff --git a/app/db/db_sys_cfg.go b/app/db/db_sys_cfg.go new file mode 100644 index 0000000..75fd914 --- /dev/null +++ b/app/db/db_sys_cfg.go @@ -0,0 +1,46 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +// 系统配置get +func SysCfgGetAll(Db *xorm.Engine) (*[]model.SysCfg, error) { + var cfgList []model.SysCfg + if err := Db.Cols("key,val,memo").Find(&cfgList); err != nil { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +// 获取一条记录 +func SysCfgGetOne(Db *xorm.Engine, key string) (*model.SysCfg, error) { + var cfgList model.SysCfg + if has, err := Db.Where("`key`=?", key).Get(&cfgList); err != nil || has == false { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +// 返回最后插入id +func SysCfgInsert(Db *xorm.Engine, key, val, memo string) bool { + cfg := model.SysCfg{Key: key, Val: val, Memo: memo} + _, err := Db.InsertOne(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} + +func SysCfgUpdate(Db *xorm.Engine, key, val, memo string) bool { + cfg := model.SysCfg{Key: key, Val: val, Memo: memo} + _, err := Db.Where("`key`=?", key).Cols("val,memo").Update(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} diff --git a/app/db/db_sys_mod.go b/app/db/db_sys_mod.go new file mode 100644 index 0000000..6281913 --- /dev/null +++ b/app/db/db_sys_mod.go @@ -0,0 +1,476 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils" + "applet/app/utils/logx" + "errors" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/tidwall/gjson" + "xorm.io/xorm" +) + +// 返回所有, 不管是否显示 +func SysModFindAll(Db *xorm.Engine) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.Find(&m); err != nil { + return nil, err + } + return &m, nil +} + +// 查找主模块数据 +func SysModFindMain(Db *xorm.Engine) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.Where("mod_pid = 0 AND state = 1 AND position = 'base'"). + Asc("sort"). + Find(&m); err != nil { + return nil, err + } + return &m, nil +} + +// 用父ID查找子模块数据 +func SysModFindByPId(c *gin.Context, Db *xorm.Engine, id int) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.Where("state = 1").Where("mod_pid = ?", id). + Asc("sort"). + Find(&m); err != nil { + return nil, err + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + var ms []model.SysModule + modname_list := []string{"product", "search_result_taobao_item", "hot_rank_tab_view"} + for _, item := range *mm.(*[]model.SysModule) { + if strings.Contains(item.Data, "tmall") == false && item.ModName == "product_detail_title" { + item.Data = strings.Replace(item.Data, "\"platform_css\":[", "\"platform_css\":[{\"name\":\"天猫\",\"type\":\"tmall\",\"text_color\":\"#FFFFFF\",\"bg_color\":\"#FF4242\"},", 1) + } + if strings.Contains(item.Data, "tmall") == false && utils.InArr(item.ModName, modname_list) { + item.Data = strings.Replace(item.Data, "{\"index\":\"6\",\"type\":\"kaola\",\"platform_name\":\"考拉\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", "{\"index\":\"6\",\"type\":\"kaola\",\"platform_name\":\"考拉\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"},{\"index\":\"7\",\"type\":\"tmall\",\"platform_name\":\"天猫\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", 1) + item.Data = strings.Replace(item.Data, "{\"type\":\"kaola\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", "{\"type\":\"kaola\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"},{\"type\":\"tmall\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", 1) + } + if strings.Contains(item.Data, "优惠卷") { + item.Data = strings.Replace(item.Data, "优惠卷", "优惠券", -1) + } + ms = append(ms, item) + } + return &ms, nil +} + +// 用父ID查找子模块数据 +func SysModFindByPIds(c *gin.Context, Db *xorm.Engine, ids ...int) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.In("mod_pid", ids).Where("state = 1"). + Asc("sort"). + Find(&m); err != nil { + return nil, err + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + var ms []model.SysModule + for _, item := range *mm.(*[]model.SysModule) { + //数据里面 + if strings.Contains(item.Data, "tmall") == false && item.ModName == "product" { + item.Data = strings.Replace(item.Data, "{\"index\":\"6\",\"type\":\"kaola\",\"platform_name\":\"考拉\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", "{\"index\":\"6\",\"type\":\"kaola\",\"platform_name\":\"考拉\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"},{\"index\":\"7\",\"type\":\"tmall\",\"platform_name\":\"天猫\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", 1) + } + item = SysModDataByReplace(c, item) + ms = append(ms, item) + } + + return &ms, nil +} + +// 用IDS找对应模块数据 +func SysModFindByIds(Db *xorm.Engine, ids ...int) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.In("mod_id", ids).Where("state = 1"). + Cols("mod_id,mod_pid,mod_name,position,skip_identifier,title,subtitle,url,margin,aspect_ratio,icon,img,font_color,bg_img,bg_color,bg_color_t,badge,path,data,sort"). + Asc("sort").Find(&m); err != nil { + return nil, err + } + + return &m, nil +} + +// ID查找对应模块 +func SysModFindById(c *gin.Context, Db *xorm.Engine, id string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND mod_id = ?", id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + + return mm.(*model.SysModule), nil +} + +// SysModFindByTmpId is 根据模板 +func SysModFindByTmpId(c *gin.Context, Db *xorm.Engine, id string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND template_id = ?", id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil +} + +// Name查找对应模块 +func SysModFindByName(c *gin.Context, Db *xorm.Engine, name string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND mod_name = ?", name). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil +} + +// SysModFindByName is Name查找对应模块 +func SysModFindByNames(names ...string) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.In("mod_name", names).Where("state = 1"). + Cols("mod_id,mod_pid,mod_name,position,skip_identifier,title,subtitle,url,margin,aspect_ratio,icon,img,font_color,bg_img,bg_color,bg_color_t,badge,path,data,sort"). + Find(&m); err != nil { + return nil, err + } + return &m, nil +} + +// SysModFindByPosition is 根据位置查找对应模块 +func SysModFindByPosition(Db *xorm.Engine, positions ...string) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.In("position", positions).Where("state = 1").Find(&m); err != nil { + return nil, err + } + return &m, nil +} + +// 根据跳转标识 查找对应模块 +func SysModFindBySkipIdentifier(c *gin.Context, Db *xorm.Engine, name string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND skip_identifier = ?", name). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil +} + +// 根据跳转标识和位置 查找对应模块list +func SysModFindBySkipIdentifierAndPosition(c *gin.Context, Db *xorm.Engine, name string, position string) (*[]model.SysModule, error) { + var m []model.SysModule + if err := Db.Where("state = 1 AND skip_identifier = ? AND position = ?", name, position). + Cols("mod_id,mod_pid,mod_name,position,skip_identifier,title,subtitle,url,margin,aspect_ratio,icon,img,font_color,bg_img,bg_color,bg_color_t,badge,path,data,sort"). + Asc("sort").Find(&m); err != nil { + return nil, err + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*[]model.SysModule), nil +} + +// SysModFindByTemplateIDAndSkip is 根据模板id 查找对应模块 +func SysModFindByTemplateIDAndSkip(Db *xorm.Engine, id interface{}, skip string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND template_id = ? AND skip_identifier = ?", id, skip). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// SysModFindByTemplateIDAndPID is 根据模板id 和pid =0 查找父模块 +func SysModFindByTemplateIDAndPID(Db *xorm.Engine, id interface{}, pid interface{}) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_pid = ?", id, pid). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + + return &m, nil +} + +// SysModFindByTemplateIDAndModName is 根据模板id 和mod name 查找模块 +func SysModFindByTemplateIDAndModName(Db *xorm.Engine, id interface{}, modName string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = ?", id, modName). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// SysModFindNavIsUsed 查找正在使用的底部导航栏模板 +func SysModFindNavIsUsedByPlatform(c *gin.Context, Db *xorm.Engine, platform string) (*model.SysModule, error) { + var ( + tm model.SysTemplate + m model.SysModule + ) + switch platform { + case "ios": + if has, err := Db.Where("is_use = 1 AND type = 'bottom' AND platform = 2 "). + Cols("id,uid,name,is_use,is_system"). + Get(&tm); err != nil || has == false { + return nil, logx.Warn(err) + } + appvserison := SysCfgGet(c, "ios_audit_version") + if c.GetHeader("app_version_name") == appvserison && c.GetHeader("app_version_name") != "" { + m, err := GetCloudBundleByVersion(Db, appvserison, 2) + if err != nil { + return nil, logx.Warn(err) + } + tm.Id = int(gjson.Get(m.TemplateDuringAudit, "bottom").Int()) + } + + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = 'bottom_nav'", tm.Id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil + case "android": + has, err := Db.Where("is_use = 1 AND type = 'bottom' AND platform = 2 ").Cols("id,uid,name,is_use,is_system").Get(&tm) + if err != nil || has == false { + return nil, logx.Warn(err) + } + appvserison := SysCfgGet(c, "android_audit_version") + if appvserison != "" && c.GetHeader("app_version_name") == appvserison { + m, err := GetCloudBundleByVersion(Db, appvserison, 1) + if err != nil { + return nil, logx.Warn(err) + } + tm.Id = int(gjson.Get(m.TemplateDuringAudit, "bottom").Int()) + } + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = 'bottom_nav'", tm.Id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil + case "wx_applet", "wap": + if has, err := Db.Where("is_use = 1 AND type = 'bottom' AND platform = 4 "). + Cols("id,uid,name,is_use,is_system"). + Get(&tm); err != nil || has == false { + return nil, logx.Warn(err) + } + appvserison := SysCfgGet(c, "mp_audit_version") + // fmt.Println("header:", c.Request.Header) + if c.GetHeader("AppVersionName") == appvserison && c.GetHeader("AppVersionName") != "" { + m, err := SysCfgGetOne(Db, "mp_audit_template") + if err != nil { + return nil, logx.Warn(err) + } + tm.Id = int(gjson.Get(m.Val, "bottom").Int()) + } + // fmt.Println("template_id", tm.Id) + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = 'bottom_nav'", tm.Id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil + case "baidu_applet": + if has, err := Db.Where("is_use = 1 AND type = 'bottom' AND platform = 4 "). + Cols("id,uid,name,is_use,is_system"). + Get(&tm); err != nil || has == false { + return nil, logx.Warn(err) + } + appvserison := SysCfgGet(c, "baidu_audit_version") + if appvserison != "" && c.GetHeader("app_version_name") == appvserison { + m, err := SysCfgGetOne(Db, "baidu_audit_template") + if err != nil { + return nil, logx.Warn(err) + } + tm.Id = int(gjson.Get(m.Val, "bottom").Int()) + } + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = 'bottom_nav'", tm.Id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil + case "toutiao_applet": + if has, err := Db.Where("is_use = 1 AND type = 'bottom' AND platform = 4 "). + Cols("id,uid,name,is_use,is_system"). + Get(&tm); err != nil || has == false { + return nil, logx.Warn(err) + } + appvserison := SysCfgGet(c, "tt_audit_version") + if appvserison != "" && c.GetHeader("app_version_name") == appvserison { + m, err := SysCfgGetOne(Db, "tt_audit_template") + if err != nil { + return nil, logx.Warn(err) + } + tm.Id = int(gjson.Get(m.Val, "bottom").Int()) + } + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = 'bottom_nav'", tm.Id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil + case "alipay_applet": + if has, err := Db.Where("is_use = 1 AND type = 'bottom' AND platform = 4 "). + Cols("id,uid,name,is_use,is_system"). + Get(&tm); err != nil || has == false { + return nil, logx.Warn(err) + } + appvserison := SysCfgGet(c, "zfb_audit_version") + if appvserison != "" && c.GetHeader("app_version_name") == appvserison { + m, err := SysCfgGetOne(Db, "zfb_audit_template") + if err != nil { + return nil, logx.Warn(err) + } + tm.Id = int(gjson.Get(m.Val, "bottom").Int()) + } + if has, err := Db.Where("state = 1 AND template_id = ? AND mod_name = 'bottom_nav'", tm.Id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil + default: + return &m, errors.New("Platform not support") + } + +} + +// SysModFindBySkipIdentifierAndModName is 根据mod_name和位置 查找对应模块 +func SysModFindBySkipIdentifierAndModName(c *gin.Context, Db *xorm.Engine, name string, modName string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND skip_identifier = ? AND mod_name = ?", name, modName).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil +} + +// SysModFindByModName is 根据mod_name和位置 查找对应模块 +func SysModFindByModName(c *gin.Context, Db *xorm.Engine, modName string) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND mod_name = ?", modName).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil +} + +// 根据跳转标识和平台类型查找 +func SysModFindBySkipIdentifierAndPlatform(c *gin.Context, Db *xorm.Engine, name string, platform int) (*model.SysModule, error) { + var m model.SysModule + if has, err := Db.Where("state = 1 AND skip_identifier = ? AND platform = ?", name, platform).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.(*model.SysModule), nil +} + +//公共处理modData的链接 +func SysModDataByReplace(c *gin.Context, mod model.SysModule) model.SysModule { + //替换链接的一些参数 + if strings.Contains(mod.Data, "[replace_APP_URL]") { + mod.Data = strings.Replace(mod.Data, "[replace_APP_URL]", c.GetString("domain_wap_base"), -1) + } + if strings.Contains(mod.Data, "[replace_masterId]") { + mod.Data = strings.Replace(mod.Data, "[replace_masterId]", c.GetString("mid"), -1) + } + if strings.Contains(mod.Data, "优惠卷") { + mod.Data = strings.Replace(mod.Data, "优惠卷", "优惠券", -1) + } + if strings.Contains(mod.Data, "[replace_uid]") { + token := c.GetHeader("Authorization") + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if len(parts) == 2 && parts[0] == "Bearer" { + // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 + mc, _ := utils.ParseToken(parts[1]) + mod.Data = strings.Replace(mod.Data, "[replace_uid]", strconv.Itoa(mc.UID), -1) + } + } + //if strings.Contains(mod.Data, "\"child_category_id") && strings.Contains(mod.Data, "\"category_id") { + // //如果存在这两个字段,要换一下 + // mod.Data = strings.ReplaceAll(mod.Data, "\"category_id", "\"null_category_id") + // mod.Data = strings.ReplaceAll(mod.Data, "\"child_category_id", "\"category_id") + //} + return mod +} + +//公共处理modData的链接 +func SysModDataByReplaceSecond(c *gin.Context, mod *model.SysModule) *model.SysModule { + //替换链接的一些参数 + if strings.Contains(mod.Data, "[replace_APP_URL]") { + mod.Data = strings.Replace(mod.Data, "[replace_APP_URL]", c.GetString("domain_wap_base"), -1) + } + if strings.Contains(mod.Data, "[replace_masterId]") { + mod.Data = strings.Replace(mod.Data, "[replace_masterId]", c.GetString("mid"), -1) + } + if strings.Contains(mod.Data, "优惠卷") { + mod.Data = strings.Replace(mod.Data, "优惠卷", "优惠券", -1) + } + if strings.Contains(mod.Data, "[replace_uid]") { + token := c.GetHeader("Authorization") + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if len(parts) == 2 && parts[0] == "Bearer" { + // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 + mc, _ := utils.ParseToken(parts[1]) + mod.Data = strings.Replace(mod.Data, "[replace_uid]", strconv.Itoa(mc.UID), -1) + } + } + //if strings.Contains(mod.Data, "\"child_category_id") && strings.Contains(mod.Data, "\"category_id") { + // //如果存在这两个字段,要换一下 + // mod.Data = strings.ReplaceAll(mod.Data, "\"category_id", "\"null_category_id") + // mod.Data = strings.ReplaceAll(mod.Data, "\"child_category_id", "\"category_id") + //} + return mod +} diff --git a/app/db/db_sys_mod_format_img.go b/app/db/db_sys_mod_format_img.go new file mode 100644 index 0000000..6675f7a --- /dev/null +++ b/app/db/db_sys_mod_format_img.go @@ -0,0 +1,493 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/md" + "applet/app/utils" + "applet/app/utils/cache" + "applet/app/utils/logx" + "encoding/json" + "errors" + "fmt" + "github.com/syyongx/php2go" + "github.com/tidwall/gjson" + "regexp" + "strings" + "xorm.io/xorm" + + "github.com/gin-gonic/gin" +) + +//处理多眼导航数据 激励广告过滤 +func MultiNavFormat(c *gin.Context, mod model.SysModule) model.SysModule { + var data = mod.Data + var listStyle = gjson.Get(data, "list_style").String() + + var multiNav []md.MultiNav + if err := json.Unmarshal([]byte(listStyle), &multiNav); err != nil { + return mod + } + + userLevelWeight := c.GetString("user_level_weight") + + var list []md.MultiNav + for _, v := range multiNav { + + if v.SkipIdentifier == "pub.flutter.incentive_ad" && v.Data.Conditions == "1" { + var levelWeight = 0 + if v.Data.LevelWeight != "" && v.Data.LevelWeight != "0" { + levelWeight = utils.StrToInt(v.Data.LevelWeight) + } + //如果用户等级大于等于设置等级 就显示 + if utils.StrToInt(userLevelWeight) >= levelWeight && c.GetString("user_login") == "1" { + list = append(list, v) + } + } else { + list = append(list, v) + } + } + b, err := json.Marshal(list) + if err != nil { + return mod + } + if b != nil { + mod.Data = strings.Replace(mod.Data, listStyle, string(b), -1) + } + return mod +} + +func sysModFormat(c *gin.Context, m interface{}) (interface{}, error) { + var mods []model.SysModule + protocol := SysCfgGet(c, "file_bucket_scheme") + domain := SysCfgGet(c, "file_bucket_host") + //fmt.Println(protocol, domain) + if protocol == "" || domain == "" { + return nil, errors.New("System configuration error, object storage protocol and domain name not found") + } + modname_list := []string{"product", "search_result_taobao_item", "hot_rank_tab_view"} + switch m.(type) { + case *[]model.SysModule: + ms := m.(*[]model.SysModule) + for _, item := range *ms { + replaceList := reformatPng(item.Data) + l := removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, php2go.Rawurlencode(s)) + item.Data = strings.ReplaceAll(item.Data, ss, `"`+newVal+`"`) + } + replaceList = reformatJPG(item.Data) + l = removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, php2go.Rawurlencode(s)) + item.Data = strings.Replace(item.Data, ss, `"`+newVal+`"`, -1) + } + replaceList = reformatGIF(item.Data) + l = removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, php2go.Rawurlencode(s)) + item.Data = strings.Replace(item.Data, ss, `"`+newVal+`"`, -1) + } + if strings.Contains(item.Data, "tmall") == false && item.ModName == "product_detail_title" { + item.Data = strings.Replace(item.Data, "\"platform_css\":[", "\"platform_css\":[{\"name\":\"天猫\",\"type\":\"tmall\",\"text_color\":\"#FFFFFF\",\"bg_color\":\"#FF4242\"},", 1) + } + if strings.Contains(item.Data, "tmall") == false && utils.InArr(item.ModName, modname_list) { + item.Data = strings.Replace(item.Data, "{\"index\":\"6\",\"type\":\"kaola\",\"platform_name\":\"考拉\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", "{\"index\":\"6\",\"type\":\"kaola\",\"platform_name\":\"考拉\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"},{\"index\":\"7\",\"type\":\"tmall\",\"platform_name\":\"天猫\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", 1) + item.Data = strings.Replace(item.Data, "{\"type\":\"kaola\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", "{\"type\":\"kaola\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"},{\"type\":\"tmall\",\"provider_name_color\":\"#FFFFFF\",\"provider_bg_color\":\"#FF4242\"}", 1) + } + + item = SysModDataByReplace(c, item) + if item.ModName == "multi_nav" { + item = MultiNavFormat(c, item) + } + mods = append(mods, item) + } + return &mods, nil + case *model.SysModule: + m := m.(*model.SysModule) + repalceList := reformatPng(m.Data) + l := removeDuplicateElement(repalceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + if skipHTTPPng(s) { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + new := fmt.Sprintf(`%s://%s/%s`, protocol, domain, php2go.Rawurlencode(s)) + + m.Data = strings.Replace(m.Data, ss, `"`+new+`"`, -1) + + } + repalceList = reformatJPG(m.Data) + l = removeDuplicateElement(repalceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + new := fmt.Sprintf("%s://%s/%s", protocol, domain, php2go.Rawurlencode(s)) + m.Data = strings.Replace(m.Data, ss, `"`+new+`"`, -1) + + } + repalceList = reformatGIF(m.Data) + l = removeDuplicateElement(repalceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + new := fmt.Sprintf("%s://%s/%s", protocol, domain, php2go.Rawurlencode(s)) + m.Data = strings.Replace(m.Data, ss, `"`+new+`"`, -1) + + } + m = SysModDataByReplaceSecond(c, m) + return m, nil + case []*model.UserLevel: + ms := m.([]*model.UserLevel) + for _, item := range ms { + replaceList := reformatPng(item.CssSet) + l := removeDuplicateElement(replaceList) + for _, s := range l { + // png + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + item.CssSet = strings.Replace(item.CssSet, ss, `"`+newVal+`"`, -1) + // jpg + replaceList = reformatJPG(item.CssSet) + l = removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + item.CssSet = strings.Replace(item.CssSet, ss, `"`+newVal+`"`, -1) + + } + } + } + return ms, nil + case []*model.SysPushUser: + ms := m.([]*model.SysPushUser) + for _, item := range ms { + replaceList := reformatPng(item.SendData) + l := removeDuplicateElement(replaceList) + for _, s := range l { + // png + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + item.SendData = strings.Replace(item.SendData, ss, `"`+newVal+`"`, -1) + // jpg + replaceList = reformatJPG(item.SendData) + l = removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + newVal := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + item.SendData = strings.Replace(item.SendData, ss, `"`+newVal+`"`, -1) + + } + } + } + return ms, nil + case *model.SysPushUser: + m := m.(*model.SysPushUser) + repalceList := reformatPng(m.SendData) + l := removeDuplicateElement(repalceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + new := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + m.SendData = strings.Replace(m.SendData, ss, `"`+new+`"`, -1) + + } + repalceList = reformatJPG(m.SendData) + l = removeDuplicateElement(repalceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + new := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + m.SendData = strings.Replace(m.SendData, ss, `"`+new+`"`, -1) + + } + repalceList = reformatGIF(m.SendData) + l = removeDuplicateElement(repalceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + new := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + m.SendData = strings.Replace(m.SendData, ss, `"`+new+`"`, -1) + } + return m, nil + default: + return nil, nil + } +} + +func ReformatStr(str string, c *gin.Context) string { + protocol := SysCfgGet(c, "file_bucket_scheme") + domain := SysCfgGet(c, "file_bucket_host") + + // PNG + replaceList := reformatPng(str) + l := removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + new := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + if skipHTTPPng(new) { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + str = strings.Replace(str, ss, `"`+new+`"`, -1) + } + + // JPG + replaceList = reformatJPG(str) + l = removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + new := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + if skipHTTPPng(new) { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + str = strings.Replace(str, ss, `"`+new+`"`, -1) + } + + // GIF + replaceList = reformatGIF(str) + l = removeDuplicateElement(replaceList) + for _, s := range l { + if strings.Contains(s, "http") { + continue + } + s = strings.ReplaceAll(s, `\`, "") + s = php2go.Rawurlencode(s) + new := fmt.Sprintf("%s://%s/%s", protocol, domain, s) + if skipHTTPPng(new) { + continue + } + ss := s + s = strings.ReplaceAll(s, `"`, "") + str = strings.Replace(str, ss, `"`+new+`"`, -1) + } + + return str +} + +// 正则匹配 +func reformatPng(data string) []string { + re, _ := regexp.Compile(`"([^\"]*.png")`) + list := re.FindAllString(data, -1) + return list +} + +// +func skipHTTPPng(data string) bool { + re, _ := regexp.Compile(`(http|https):\/\/([^\"]*.png)`) + return re.MatchString(data) +} + +// 正则匹配 +func reformatJPG(data string) []string { + re, _ := regexp.Compile(`"([^\"]*.jpg")`) + list := re.FindAllString(data, -1) + return list +} + +// 正则匹配 +func reformatGIF(data string) []string { + re, _ := regexp.Compile(`"([^\"]*.gif")`) + list := re.FindAllString(data, -1) + return list +} + +func removeDuplicateElement(addrs []string) []string { + result := make([]string, 0, len(addrs)) + temp := map[string]int{} + i := 1 + for _, item := range addrs { + if _, ok := temp[item]; !ok { + temp[item] = i + result = append(result, item) + continue + } + temp[item] = temp[item] + 1 + } + // fmt.Println(temp) + return result +} + +//单条记录获取 +func SysCfgGet(c *gin.Context, key string) string { + res := SysCfgFind(c, key) + //fmt.Println(res) + if _, ok := res[key]; !ok { + return "" + } + return res[key] +} + +//单条记录获取DB +func SysCfgGetWithDb(eg *xorm.Engine, masterId string, HKey string) string { + cacheKey := fmt.Sprintf(md.AppCfgCacheKey, masterId, HKey[0:1]) + get, err := cache.HGetString(cacheKey, HKey) + if err != nil || get == "" { + cfg, err := SysCfgGetOne(eg, 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.Val) + if err != nil { + _ = logx.Error(err) + return "" + } + if !cacheKeyExist { // 如果是首次设置 设置过期时间 + _, err := cache.Expire(cacheKey, md.CfgCacheTime) + if err != nil { + _ = logx.Error(err) + return "" + } + } + return cfg.Val + } + return get +} + +// 多条记录获取 +func SysCfgFind(c *gin.Context, keys ...string) map[string]string { + res := map[string]string{} + cacheKey := fmt.Sprintf(md.AppCfgCacheKey, c.GetString("mid")) + err := cache.GetJson(cacheKey, &res) + if err != nil || len(res) == 0 { + cfgList, _ := SysCfgGetAll(DBs[c.GetString("mid")]) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + cache.SetJson(cacheKey, res, md.CfgCacheTime) + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} + +// 多条记录获取DB +func SysCfgFindWithDb(eg *xorm.Engine, masterId string, keys ...string) map[string]string { + res := map[string]string{} + cacheKey := fmt.Sprintf(md.AppCfgCacheKey, masterId) + err := cache.GetJson(cacheKey, &res) + if err != nil || len(res) == 0 { + cfgList, _ := SysCfgGetAll(eg) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + cache.SetJson(cacheKey, res, md.CfgCacheTime) + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} diff --git a/app/db/db_sys_push_app.go b/app/db/db_sys_push_app.go new file mode 100644 index 0000000..aefbb2b --- /dev/null +++ b/app/db/db_sys_push_app.go @@ -0,0 +1,39 @@ +package db + +import ( + "applet/app/db/model" + "errors" + + "xorm.io/xorm" +) + +// InertSysPushAppOne is 插入一条记录在 表sys_push_app +func InertSysPushAppOne(Db *xorm.Engine, m *model.SysPushApp) (int64, error) { + affect, err := Db.InsertOne(m) + if err != nil { + return 0, err + } + return affect, nil +} + +//UpdateSysPushApp is 更新某条记录 +func UpdateSysPushApp(Db *xorm.Engine, m *model.SysPushApp) (int64, error) { + affect, err := Db.ID(m.Id).Update(m) + if err != nil { + return 0, err + } + return affect, nil +} + +//SysPushAppByID is 根据id 获取对应的推送信息 +func SysPushAppByID(Db *xorm.Engine, id interface{}) (*model.SysPushApp, error) { + m := new(model.SysPushApp) + has, err := Db.ID(id).Get(m) + if err != nil { + return nil, err + } + if !has { + return nil, errors.New("Not Found") + } + return m, nil +} diff --git a/app/db/db_sys_push_template.go b/app/db/db_sys_push_template.go new file mode 100644 index 0000000..ac8c652 --- /dev/null +++ b/app/db/db_sys_push_template.go @@ -0,0 +1,21 @@ +package db + +import ( + "applet/app/db/model" + "errors" + + "xorm.io/xorm" +) + +//SysPushTemplateByType is 根据类型 +func SysPushTemplateByType(Db *xorm.Engine, t string) (*model.SysPushTemplate, error) { + m := new(model.SysPushTemplate) + has, err := Db.Where("type = ?", t).Get(m) + if err != nil { + return nil, err + } + if !has { + return nil, errors.New("Not Found") + } + return m, nil +} diff --git a/app/db/db_sys_push_user.go b/app/db/db_sys_push_user.go new file mode 100644 index 0000000..498ffcf --- /dev/null +++ b/app/db/db_sys_push_user.go @@ -0,0 +1,94 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "github.com/gin-gonic/gin" + "xorm.io/xorm" +) + +// SysPushUserByUIDByTypeIn is 根据uid和类型获取对应的通知 +func SysPushUserByUIDByTypeIn(c *gin.Context, Db *xorm.Engine, uid interface{}, limit int, start int, t ...interface{}) ([]*model.SysPushUser, error) { + var m []*model.SysPushUser + + if limit == 0 && start == 0 { + if err := Db.Where("uid = ?", uid).In("type", t...).Desc("time").Find(&m); err != nil { + return nil, err + } + mm, err := sysModFormat(c, m) + if err != nil { + return nil, err + } + return mm.([]*model.SysPushUser), nil + } + if err := Db.Where("uid = ?", uid).In("type", t...).Limit(limit, start).Desc("time").Find(&m); err != nil { + return nil, err + } + mm, err := sysModFormat(c, m) + if err != nil { + return nil, err + } + return mm.([]*model.SysPushUser), nil +} + +//SysPushUserInTypeGET is In 查询 一个 +func SysPushUserInTypeGET(c *gin.Context, Db *xorm.Engine, uid interface{}, ts ...interface{}) (*model.SysPushUser, error) { + m := new(model.SysPushUser) + + has, err := Db.Where("uid = ?", uid).In("type", ts...).Desc("time").Get(m) + if err != nil { + return nil, err + } + if !has { + return nil, nil + } + mm, err := sysModFormat(c, m) + if err != nil { + return nil, err + } + return mm.(*model.SysPushUser), nil +} + +//SysPushUserInType is In 查询 +func SysPushUserInType(c *gin.Context, Db *xorm.Engine, uid interface{}, ts ...interface{}) ([]*model.SysPushUser, error) { + var m []*model.SysPushUser + if err := Db.Where("uid = ?", uid).In("type", ts...).Desc("time").Find(&m); err != nil { + return nil, err + } + mm, err := sysModFormat(c, &m) + if err != nil { + return nil, err + } + return mm.([]*model.SysPushUser), nil +} + +//SysPushUserInTypeCount is In 查询 +func SysPushUserInTypeCount(Db *xorm.Engine, uid interface{}, ts ...interface{}) (int64, error) { + m := new(model.SysPushUser) + count, err := Db.Where("uid = ? AND is_read = 0", uid).In("type", ts...).Count(m) + if err != nil { + return 0, err + } + + return count, nil +} + +//SysPushUserInsertOne is 插入一条记录 +func SysPushUserInsertOne(Db *xorm.Engine, m *model.SysPushUser) (int64, error) { + affected, err := Db.InsertOne(m) + if err != nil { + return 0, err + } + return affected, nil +} + +//BatchUpdateSysPushUserRead is 批量更新某种类型的通知 +func BatchUpdateSysPushUserRead(Db *xorm.Engine, uid, t interface{}) error { + sql := "update sys_push_user set is_read = ? where uid = ? and type = ?" + _, err := Db.Exec(sql, 1, uid, t) + if err != nil { + return logx.Warn(err) + } + return nil +} diff --git a/app/db/db_sys_tmp.go b/app/db/db_sys_tmp.go new file mode 100644 index 0000000..9efaea6 --- /dev/null +++ b/app/db/db_sys_tmp.go @@ -0,0 +1,26 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +//SysTmpFindByIsUseByType is find tmp by issystem +func SysTmpFindByIsUseByType(Db *xorm.Engine, use, t, platform string) (*model.SysTemplate, error) { + var m model.SysTemplate + if has, err := Db.Where("is_use = ? AND type = ? AND platform = ?", use, t, platform). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +func SysTmpFindByID(Db *xorm.Engine, id int) (*model.SysTemplate, error) { + var m model.SysTemplate + if has, err := Db.Where("id = ?", id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} diff --git a/app/db/db_user.go b/app/db/db_user.go new file mode 100644 index 0000000..1860539 --- /dev/null +++ b/app/db/db_user.go @@ -0,0 +1,298 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils" + "applet/app/utils/logx" + "fmt" + "xorm.io/xorm" +) + +// UserisExistByUsernameAndPassword is usernameAndPassword exist +func UserisExistByUsernameAndPassword(Db *xorm.Engine, username string, password string) (bool, error) { + + has, err := Db.Where("username = ? AND password = ?", username, utils.Md5(password)).Exist(&model.User{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserisExistByMobile is exist +func UserisExistByMobile(Db *xorm.Engine, n string) (bool, error) { + has, err := Db.Where("phone = ?", n).Exist(&model.User{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserInByUIDByLevel is In查询 以及是否是有效用户 +func UserInByUIDByLevel(Db *xorm.Engine, ids []int, levelID interface{}) (*[]model.User, error) { + var m []model.User + if err := Db.In("uid", ids).Where("level = ?", levelID). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserFindByMobile search user by mobile +func UserFindByMobile(Db *xorm.Engine, mobile string) (*model.User, error) { + var m model.User + if has, err := Db.Where("(phone = ? OR uid = ?) AND delete_at = 0", mobile, mobile). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserFindExistByMobile search user by mobile +func UserFindExistByMobile(Db *xorm.Engine, mobile string) (*model.User, bool, error) { + var m model.User + has, err := Db.Where("(phone = ? OR uid = ?) AND delete_at = 0", mobile, mobile).Get(&m) + if err != nil { + logx.Infof("UserFindExistByMobile err") + return nil, false, logx.Warn(err) + } + return &m, has, nil +} + +// UserFindByMobile search user by mobile +func UserFindByMobileAll(Db *xorm.Engine, mobile string) (*model.User, error) { + var m model.User + if has, err := Db.Where("(phone = ? OR uid = ?)", mobile, mobile). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserGetByMobileIgnoreDelete search user by mobile ignore delete +func UserGetByMobileIgnoreDelete(Db *xorm.Engine, mobile string) (*model.User, bool, error) { + m := new(model.User) + has, err := Db.Where("phone = ?", mobile).Get(m) + if err != nil { + return nil, false, logx.Warn(err) + } + return m, has, nil +} + +// UsersFindByMobileLike search users by mobile +func UsersFindByMobileLike(Db *xorm.Engine, mobile string) (*[]model.User, error) { + var m []model.User + if err := Db.Where("phone like ?", "%"+mobile+"%"). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersFindByNickNameLike search users by nickname +func UsersFindByNickNameLike(Db *xorm.Engine, nickname string) (*[]model.User, error) { + var m []model.User + if err := Db.Where("nickname like ?", "%"+nickname+"%"). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UsersInByIds is 根据ids 查找users +func UsersInByIds(Db *xorm.Engine, ids []int, limit, start int) (*[]model.User, error) { + var m []model.User + if limit == 0 && start == 0 { + if err := Db.In("uid", ids). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.In("uid", ids).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UsersInByIdsWhereLv is 根据ids和 lv会员等级 查找users +func UsersInByIdsWhereLv(Db *xorm.Engine, ids []int, lv interface{}, limit, start int) (*[]model.User, error) { + var m []model.User + if limit == 0 && start == 0 { + if err := Db.Where("level = ?", lv).In("uid", ids). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("level = ?", lv).In("uid", ids).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UsersInByIdsByAscWhereLv is 根据ids和 lv会员等级 查找users 升排序 +func UsersInByIdsByAscWhereLv(Db *xorm.Engine, ids []int, lv interface{}, limit, start int, c string) (*[]model.User, error) { + var m []model.User + if limit == 0 && start == 0 { + if err := Db.Where("level = ?", lv).In("uid", ids).Asc(c). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("level = ?", lv).In("uid", ids).Asc(c).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UsersInByIdsByDescWhereLv is 根据ids和 lv会员等级 查找users 降排序 +func UsersInByIdsByDescWhereLv(Db *xorm.Engine, ids []int, lv interface{}, limit, start int, c string) (*[]model.User, error) { + var m []model.User + if limit == 0 && start == 0 { + if err := Db.Where("level = ?", lv).In("uid", ids).Desc(c). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("level = ?", lv).In("uid", ids).Desc(c).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserFindByArkidUserName search user by mobile +func UserFindByArkidUserName(Db *xorm.Engine, name string) (*model.User, error) { + var m model.User + if has, err := Db.Where("username = ?", name). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserFindByID is find user byid +func UserFindByID(Db *xorm.Engine, id interface{}) (*model.User, error) { + var m model.User + if has, err := Db.Where("uid = ?", id). + Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +func UserFindByIDs(Db *xorm.Engine, uids []int) (*[]model.User, error) { + var m []model.User + if err := Db.In("uid", uids).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersInByIdsByDesc is 根据某列 降序 +func UsersInByIdsByDesc(Db *xorm.Engine, ids []int, limit, start int, c string) (*[]model.User, error) { + var m []model.User + if limit == 0 && start == 0 { + if err := Db.In("uid", ids).Desc(c). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.In("uid", ids).Desc(c).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersInByIdsByAsc is 根据某列 升序 +func UsersInByIdsByAsc(Db *xorm.Engine, ids []int, limit, start int, c string) (*[]model.User, error) { + var m []model.User + if limit == 0 && start == 0 { + if err := Db.In("uid", ids).Asc(c). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.In("uid", ids).Asc(c).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UserInsert is insert user +func UserInsert(Db *xorm.Engine, user *model.User) (int64, error) { + affected, err := Db.Insert(user) + if err != nil { + return 0, err + } + return affected, nil +} + +// UserIsExistByMobile is mobile exist +func UserIsExistByMobile(Db *xorm.Engine, mobile string) (bool, error) { + //fmt.Println(mobile) + has, err := Db.Where("phone = ? OR uid = ?", mobile, mobile).Exist(&model.User{}) + fmt.Println(has, mobile) + if err != nil { + return false, err + } + return has, nil +} + +// UserIsExistByID is mobile exist by id +func UserIsExistByID(Db *xorm.Engine, id string) (bool, error) { + has, err := Db.Where("uid = ?", id).Exist(&model.User{}) + if err != nil { + return false, err + } + return has, nil +} + +// UserUpdate is update user +func UserUpdate(Db *xorm.Engine, uid interface{}, user *model.User, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = Db.Where("uid=?", uid).Cols(forceColums...).Update(user) + } else { + affected, err = Db.Where("uid=?", uid).Update(user) + } + if err != nil { + return 0, err + } + return affected, nil +} + +func UserUpdateWithSession(Db *xorm.Session, uid interface{}, user *model.User, forceColums ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColums != nil { + affected, err = Db.Where("uid=?", uid).Cols(forceColums...).Update(user) + } else { + affected, err = Db.Where("uid=?", uid).Update(user) + } + if err != nil { + return 0, err + } + return affected, nil +} + +// UserDelete is delete user +func UserDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + return Db.Where("uid = ?", uid).Delete(model.User{}) +} diff --git a/app/db/db_user_fav.go b/app/db/db_user_fav.go new file mode 100644 index 0000000..e877e98 --- /dev/null +++ b/app/db/db_user_fav.go @@ -0,0 +1,85 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +// GetUserFav is 获取商品收藏 +func GetUserFav(Db *xorm.Engine, uid, itemid interface{}) (*model.UserFavorite, error) { + var m model.UserFavorite + if has, err := Db.Where("uid = ? AND item_id = ?", uid, itemid).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + return &m, nil +} + +//InsertUserFavOne is 插入一条收藏记录 +func InsertUserFavOne(Db *xorm.Engine, m *model.UserFavorite) (int64, error) { + affected, err := Db.InsertOne(m) + if err != nil { + return 0, err + } + return affected, nil +} + +//UserFavFindByProvider 根据提供商来筛选收藏的 +func UserFavFindByProvider(Db *xorm.Engine, uid interface{}, provider string, limit, start int) ([]*model.UserFavorite, error) { + var m []*model.UserFavorite + var err error + if provider == "all" { + if limit == 0 && start == 0 { + err = Db.Where("uid = ?", uid).Desc("create_at").Find(&m) + } else { + err = Db.Where("uid = ?", uid).Desc("create_at").Limit(limit, start).Find(&m) + } + } else { + if limit == 0 && start == 0 { + err = Db.Where("uid = ? AND provider = ?", uid, provider).Desc("create_at").Find(&m) + } else { + err = Db.Where("uid = ? AND provider = ?", uid, provider).Desc("create_at").Limit(limit, start).Find(&m) + } + + } + if err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +//UserFavFindSearchByTitle is 我的收藏搜索 +func UserFavFindSearchByTitle(Db *xorm.Engine, uid interface{}, title string, limit, start int) ([]*model.UserFavorite, error) { + var m []*model.UserFavorite + var err error + if limit == 0 && start == 0 { + err = Db.Where("item_title like ?", "%"+title+"%").Desc("create_at").Find(&m) + } else { + err = Db.Where("item_title like ?", "%"+title+"%").Desc("create_at").Limit(limit, start).Find(&m) + } + if err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// UserFavDelete is 删除收藏夹 +func UserFavDelete(Db *xorm.Engine, uid interface{}, itemIds []interface{}) (int64, error) { + var m model.UserFavorite + affect, err := Db.Where("uid = ?", uid).In("item_id", itemIds).Delete(&m) + if err != nil { + return 0, logx.Warn(err) + } + return affect, nil +} + +// UserFavDeleteByUserDelete is 删除用户时删除对应的收藏夹 +func UserFavDeleteByUserDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + var m model.UserFavorite + affect, err := Db.Where("uid = ?", uid).Delete(&m) + if err != nil { + return 0, logx.Warn(err) + } + return affect, nil +} diff --git a/app/db/db_user_fin_flow.go b/app/db/db_user_fin_flow.go new file mode 100644 index 0000000..b918667 --- /dev/null +++ b/app/db/db_user_fin_flow.go @@ -0,0 +1,71 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +// GetFinUserFlowByID is 用户流水记录 +func GetFinUserFlowByID(Db *xorm.Engine, id interface{}) (*model.FinUserFlow, error) { + var m model.FinUserFlow + if has, err := Db.Where("id = ?", id).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + return &m, nil +} + +// GetFinUserFlowByID is 用户流水记录 +func GetFinUserFlowByUIDANDOID(Db *xorm.Engine, types, uid, ordId string) (*model.FinUserFlow, error) { + var m model.FinUserFlow + if has, err := Db.Where("uid = ? and ord_id = ? and type = ?", uid, ordId, types).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + return &m, nil +} + +//FinUserFlowInsertOne is 插入一条流水记录 +func FinUserFlowInsertOne(Db *xorm.Engine, m *model.FinUserFlow) error { + _, err := Db.InsertOne(m) + if err != nil { + return err + } + return nil +} + +// FinUserFlowByUID is 用户流水 +func FinUserFlowInputByUID(Db *xorm.Engine, uid interface{}, time string, limit, start int) ([]*model.FinUserFlow, error) { + var m []*model.FinUserFlow + if err := Db.Where("uid = ? AND create_at like ?", uid, time+"%").In("type", "0").Desc("create_at").Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// FinUserFlowByUIDByOrderAction is 用户流水 by OrderAction +func FinUserFlowInputByUIDByOrderActionByTime(Db *xorm.Engine, uid, oa interface{}, time string, limit, start int) ([]*model.FinUserFlow, error) { + var m []*model.FinUserFlow + if err := Db.Where("uid = ? AND ord_action = ? AND create_at like ?", uid, oa, time+"%").Desc("create_at").Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// FinUserFlowByUIDByOrderAction is 用户流水 by OrderAction +func FinUserFlowInputByUIDByTypeByTime(Db *xorm.Engine, uid int, time string, limit, start int) ([]*model.FinUserFlow, error) { + var m []*model.FinUserFlow + if err := Db.Where("uid = ? AND type = 1 AND create_at like ?", uid, time+"%").Desc("create_at").Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// 在事务中使用,插入一条流水记录 +func FinUserFlowInsertOneWithSession(session *xorm.Session, m *model.FinUserFlow) error { + _, err := session.InsertOne(m) + if err != nil { + return err + } + return nil +} diff --git a/app/db/db_user_foot_mark.go b/app/db/db_user_foot_mark.go new file mode 100644 index 0000000..d2a21e9 --- /dev/null +++ b/app/db/db_user_foot_mark.go @@ -0,0 +1,91 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +// GetUserFootMark is 获取商品足迹 +func GetUserFootMark(Db *xorm.Engine, uid, itemid interface{}) (*model.UserFootMark, error) { + var m model.UserFootMark + if has, err := Db.Where("uid = ? AND item_id = ?", uid, itemid).Get(&m); err != nil || !has { + return nil, logx.Warn(err) + } + return &m, nil +} + +//InsertUserFavOne is 插入一条足迹记录 +func InsertUserFootMarkOne(Db *xorm.Engine, m *model.UserFootMark) (int64, error) { + affected, err := Db.InsertOne(m) + if err != nil { + return 0, err + } + return affected, nil +} + +// UpdateUserFootMarkOne is 更新一条足迹记录 +func UpdateUserFootMarkOne(Db *xorm.Engine, m *model.UserFootMark) (int64, error) { + row, err := Db.ID(m.Id).AllCols().Update(m) + return row, err +} + +//UserFootMarkFindByProvider 根据提供商来筛选足迹的 +func UserFootMarkFindByProvider(Db *xorm.Engine, uid interface{}, provider string, limit, start int) ([]*model.UserFootMark, error) { + var m []*model.UserFootMark + var err error + if provider == "all" { + if limit == 0 && start == 0 { + err = Db.Where("uid = ?", uid).Desc("create_at").Find(&m) + } else { + err = Db.Where("uid = ?", uid).Desc("create_at").Limit(limit, start).Find(&m) + } + } else { + if limit == 0 && start == 0 { + err = Db.Where("uid = ? AND provider = ?", uid, provider).Desc("create_at").Find(&m) + } else { + err = Db.Where("uid = ? AND provider = ?", uid, provider).Desc("create_at").Limit(limit, start).Find(&m) + } + + } + if err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +//UserFootMarkFindSearchByTitle is 我的足迹搜索 +func UserFootMarkFindSearchByTitle(Db *xorm.Engine, uid interface{}, title string, limit, start int) ([]*model.UserFootMark, error) { + var m []*model.UserFootMark + var err error + if limit == 0 && start == 0 { + err = Db.Where("item_title like ?", "%"+title+"%").Desc("create_at").Find(&m) + } else { + err = Db.Where("item_title like ?", "%"+title+"%").Desc("create_at").Limit(limit, start).Find(&m) + } + if err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// UserFootMarkDelete is 删除足迹夹 +func UserFootMarkDelete(Db *xorm.Engine, uid interface{}, itemIds []interface{}) (int64, error) { + var m model.UserFootMark + affect, err := Db.Where("uid = ?", uid).In("item_id", itemIds).Delete(&m) + if err != nil { + return 0, logx.Warn(err) + } + return affect, nil +} + +// UserFootMarkDeleteByUserDelete is 删除用户时删除对应的足迹夹 +func UserFootMarkDeleteByUserDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + var m model.UserFootMark + affect, err := Db.Where("uid = ?", uid).Delete(&m) + if err != nil { + return 0, logx.Warn(err) + } + return affect, nil +} diff --git a/app/db/db_user_level.go b/app/db/db_user_level.go new file mode 100644 index 0000000..a600aa4 --- /dev/null +++ b/app/db/db_user_level.go @@ -0,0 +1,123 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "github.com/gin-gonic/gin" + "xorm.io/xorm" +) + +//UserLevelByID is 根据用户id 获取对应的等级信息 +func UserLevelByID(Db *xorm.Engine, id interface{}) (*model.UserLevel, error) { + m := new(model.UserLevel) + has, err := Db.Where("id = ?", id).Get(m) + if err != nil { + return nil, logx.Warn(err) + } + if !has { + return nil, logx.Error("Not found") + } + + return m, nil +} + +//UserLevelTop is 查询最高的等级 +func UserLevelTop(Db *xorm.Engine) (*model.UserLevel, error) { + m := new(model.UserLevel) + has, err := Db.OrderBy("level_weight DESC").Get(m) + if err != nil { + return nil, logx.Warn(err) + } + if !has { + return nil, logx.Error("Not found") + } + + return m, nil +} + +//UserLevelNext is 查询下一等级 +func UserLevelNext(Db *xorm.Engine, curLevelWeight int) (*model.UserLevel, error) { + m := new(model.UserLevel) + has, err := Db.Where("level_weight > ?", curLevelWeight).OrderBy("level_weight ASC").Get(m) + if err != nil { + return nil, logx.Warn(err) + } + if !has { + return nil, logx.Error("Not found") + } + + return m, nil +} + +// UserLevelByWeight is 根据权重获取对应的等级 +func UserLevelByWeight(Db *xorm.Engine, w interface{}) (*model.UserLevel, error) { + m := new(model.UserLevel) + has, err := Db.Where("level_weight = ?", w).Get(m) + if err != nil { + return nil, logx.Warn(err) + } + if !has { + return nil, logx.Warn("Not found") + } + return m, nil +} + +//UserLevelInIDescByWeight is In 查询获取 权重最低 对应等级 +func UserLevelInIDescByWeightLow(Db *xorm.Engine) ([]*model.UserLevel, error) { + var ms []*model.UserLevel + if err := Db.Asc("level_weight").Limit(1).Find(&ms); err != nil { + return nil, err + } + return ms, nil + +} + +//UserLevelInIDescByWeight is In 查询获取对应等级 根据权重排序 +func UserLevelInIDescByWeight(Db *xorm.Engine, ids []int) ([]*model.UserLevel, error) { + var ms []*model.UserLevel + if err := Db.In("id", ids).Desc("level_weight").Find(&ms); err != nil { + return nil, err + } + return ms, nil + +} + +// UserLevlAll is 获取所有开启等级并且升序返回 +func UserLevlAll(c *gin.Context, Db *xorm.Engine) ([]*model.UserLevel, error) { + var m []*model.UserLevel + err := Db.Where("is_use = ?", 1).Asc("level_weight").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + + mm, err := sysModFormat(c, m) + if err != nil { + return nil, err + } + return mm.([]*model.UserLevel), nil +} + +// UserLevlEgAll is 获取所有开启等级并且升序返回 +func UserLevlEgAll(Db *xorm.Engine) ([]*model.UserLevel, error) { + var m []*model.UserLevel + err := Db.Where("is_use = ?", 1).Asc("level_weight").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// UserLevlAllByWeight is 获取所有等级并且权重升序返回 +func UserLevlAllByWeight(c *gin.Context, Db *xorm.Engine) ([]*model.UserLevel, error) { + var m []*model.UserLevel + err := Db.Asc("level_weight").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + + mm, err := sysModFormat(c, m) + if err != nil { + return nil, err + } + return mm.([]*model.UserLevel), nil +} diff --git a/app/db/db_user_level_audit.go b/app/db/db_user_level_audit.go new file mode 100644 index 0000000..c5dabb3 --- /dev/null +++ b/app/db/db_user_level_audit.go @@ -0,0 +1,72 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "xorm.io/xorm" +) + +// 用户当前等级正在审核中的记录 +func UserLevelAuditingFindByUidAndLevel(Db *xorm.Engine, uid int, levelId int) (*model.UserLevelAudit, error) { + var m model.UserLevelAudit + has, err := Db.Where("uid=? AND state=? AND current_level_id=?", uid, 1, levelId).Get(&m) + if err != nil || !has { + return nil, logx.Warn(err) + } + return &m, nil +} + +// 用户正在审核中的记录 +func UserLevelAuditingFindByUid(Db *xorm.Engine, uid int) (*model.UserLevelAudit, error) { + var m model.UserLevelAudit + has, err := Db.Where("uid=? AND state=?", uid, 1).Get(&m) + if err != nil || !has { + return nil, err + } + return &m, nil +} + +// 插入一条记录 +func UserLevelAuditInsertWithSession(Db *xorm.Session, data *model.UserLevelAudit) (int64, error) { + affect, err := Db.Insert(data) + if err != nil || affect != 1 { + return 0, err + } + + return affect, nil +} + +func UserLevelAuditFindById(Db *xorm.Engine, id int) (*model.UserLevelAudit, error) { + var m model.UserLevelAudit + has, err := Db.Where("id=?", id).Get(&m) + if err != nil || !has { + return nil, err + } + return &m, nil +} + +func UserLevelAuditFindByIdAndUid(Db *xorm.Engine, id int, uid int) (*model.UserLevelAudit, error) { + var m model.UserLevelAudit + has, err := Db.Where("next_level_id=? AND uid=? AND state<>2", id, uid).Get(&m) + if err != nil || !has { + return nil, err + } + return &m, nil +} + +// 更新审核记录 +func UserLevelAuditUpdateByIdWithSession(session *xorm.Session, id int, audit *model.UserLevelAudit, forceColumns ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceColumns != nil { + affected, err = session.Where("id=?", id).Cols(forceColumns...).Update(audit) + } else { + affected, err = session.Where("id=?", id).Update(audit) + } + if err != nil { + return 0, err + } + return affected, nil +} diff --git a/app/db/db_user_level_ord.go b/app/db/db_user_level_ord.go new file mode 100644 index 0000000..ac8583c --- /dev/null +++ b/app/db/db_user_level_ord.go @@ -0,0 +1,145 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + "time" + + "xorm.io/xorm" +) + +// 获取对应等级未过期的付费订单 +func UserLevelOrderListByUIDByLevelTypeByExpire(Db *xorm.Engine, uid, levelID interface{}) ([]*model.UserLevelOrd, error) { + var m []*model.UserLevelOrd + err := Db.Where("uid = ? AND level_id = ? AND state = ? ", uid, levelID, 1).Desc("expire_at").Find(&m) + if err != nil { + return nil, err + } + return m, nil +} + +// OrderListByPvdAndNoSettledWithPage is 查询未结算订单 +func UserLevelOrderListByUIDByNoSettledWithPage(Db *xorm.Engine, page int) ([]*model.UserLevelOrd, error) { + perPage := 100 + startPlace := (page - 1) * perPage + var o []*model.UserLevelOrd + + err := Db.Where("settle_at=?", 0).In("state", 1).Limit(perPage, startPlace).Find(&o) + if err != nil { + return nil, err + } + return o, nil +} + +// 获取一个订单 +func UserLevelOrderById(Db *xorm.Engine, id string) (*model.UserLevelOrd, error) { + var m *model.UserLevelOrd + has, err := Db.Where("id = ? ", id).Get(&m) + if err != nil || has == false { + return nil, err + } + return m, nil +} + +//UserLevelOrderListByUIDByLevelByDateTypeByExpire is 获取某用户某等级的订单,按照过期时间降序排序 +func UserLevelOrderListByUIDByLevelByDateTypeByExpire(Db *xorm.Engine, uid, levelID, t interface{}) ([]*model.UserLevelOrd, error) { + var m []*model.UserLevelOrd + err := Db.Where("uid = ? AND level_id = ? AND date_type = ? AND state = ? ", uid, levelID, t, 1).Desc("expire_at").Find(&m) + if err != nil { + return nil, err + } + return m, nil +} + +//UserLevelOrderOneByUIDByLevelByDateTypeByExpire is 获取某用户某等级的订单,按照过期时间降序排序 +func UserLevelOrderOneByUIDByLevelByDateTypeByExpire(Db *xorm.Engine, uid, levelID interface{}) ([]*model.UserLevelOrd, error) { + var m []*model.UserLevelOrd + err := Db.Where("uid = ? AND level_id = ? and state = 1", uid, levelID).Desc("expire_at").Find(&m) + if err != nil { + return nil, err + } + return m, nil +} + +//UserLevelOrdInsertOne is 插入一个 等级付费升级的订单 +func UserLevelOrdInsertOne(Db *xorm.Engine, order *model.UserLevelOrd) (int64, error) { + affect, err := Db.InsertOne(order) + if err != nil { + return 0, err + } + return affect, nil +} + +//UserLevelOrdUpdateOne is 更新付费升级订单 +func UserLevelOrdUpdateOne(Db *xorm.Engine, order *model.UserLevelOrd) (int64, error) { + affect, err := Db.ID(order.Id).Update(order) + if err != nil { + return 0, err + } + return affect, nil +} + +func UserLevelOrdUpdateOneWithSession(Db *xorm.Session, order *model.UserLevelOrd) (int64, error) { + affect, err := Db.ID(order.Id).Update(order) + if err != nil { + return 0, err + } + return affect, nil +} + +func UserLevelOrdFindOneByOid(Db *xorm.Engine, id int64) (*model.UserLevelOrd, error) { + var m model.UserLevelOrd + has, err := Db.Where("id=? AND state=?", id, 1).Get(&m) + + if err != nil || !has { + return nil, err + } + + return &m, nil +} + +func UserLevelOrdFindByUidStateLevelAndNotExpire(Db *xorm.Engine, uid int, state int, level string) (*model.UserLevelOrd, error) { + var m model.UserLevelOrd + //过期时间还没到 或者是永久的订单 + if has, err := Db.Where("uid = ? AND level_id = ? AND state = ? AND (expire_at>= ? OR date_type=4)", uid, level, state, time.Now().Unix()).Get(&m); !has || err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// 查询订单状态 +func UserLevelOrdFindByID(Db *xorm.Engine, id int) (*model.UserLevelOrd, error) { + var m model.UserLevelOrd + if has, err := Db.Where("id = ?", id).Get(&m); !has || err != nil { + return nil, logx.Warn(err) + + } + return &m, nil +} + +// 获取对应id订单 +func UserLevelOrdFindByIDs(Db *xorm.Engine, ids []int) ([]*model.UserLevelOrd, error) { + var m []*model.UserLevelOrd + err := Db.In("id", ids).Find(&m) + if err != nil { + return nil, err + } + return m, nil +} + +//session事务修改订单 +func UserLvUpOrderUpdateWithSession(session *xorm.Session, ordId int64, order *model.UserLevelOrd, forceCols ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceCols != nil { + affected, err = session.Where("id=?", ordId).Cols(forceCols...).Update(order) + } else { + affected, err = session.Where("id=?", ordId).Update(order) + } + if err != nil { + return 0, logx.Warn(err) + } + return affected, nil +} diff --git a/app/db/db_user_level_task.go b/app/db/db_user_level_task.go new file mode 100644 index 0000000..c2c8be9 --- /dev/null +++ b/app/db/db_user_level_task.go @@ -0,0 +1,39 @@ +package db + +import ( + "applet/app/db/model" + "errors" + "xorm.io/xorm" +) + +//UserLevelTaskInIDS is 根据ids 获取等级任务 +func UserLevelTaskInIDS(Db *xorm.Engine, ids []int) ([]*model.UserLevelTask, error) { + var m []*model.UserLevelTask + if err := Db.In("level_id", ids).Asc("level_id").Find(&m); err != nil { + return nil, err + } + return m, nil +} + +//UserLevelTaskByID is 根据id 获取等级任务列表 +func UserLevelTaskByID(Db *xorm.Engine, id interface{}) ([]*model.UserLevelTask, error) { + var m []*model.UserLevelTask + if err := Db.Where("level_id = ?", id).Find(&m); err != nil { + return nil, err + } + return m, nil +} + +//UserLevelTaskOneByID is 根据id 获取等级任务 +func UserLevelTaskOneByID(Db *xorm.Engine, id interface{}) (*model.UserLevelTask, error) { + m := new(model.UserLevelTask) + has, err := Db.Where("id = ?", id).Get(m) + //fmt.Println(m) + if !has { + return nil, errors.New("Not found") + } + if err != nil { + return nil, err + } + return m, nil +} diff --git a/app/db/db_user_notice.go b/app/db/db_user_notice.go new file mode 100644 index 0000000..004a0c8 --- /dev/null +++ b/app/db/db_user_notice.go @@ -0,0 +1,188 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +//UserNoticeByReceiverByTime is 获取改接收者的 +func UserNoticeByReceiverByTime(Db *xorm.Engine, uid interface{}, sort string) (*[]model.UserNotice, error) { + switch sort { + case "desc": + // 降序 + var m []model.UserNotice + err := Db.Where("receiver = ?", uid).Desc("create_at").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + case "asc": + //升序 + var m []model.UserNotice + err := Db.Where("receiver = ?", uid).Asc("create_at").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + default: + //默认 + var m []model.UserNotice + err := Db.Where("receiver = ?", uid).Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } +} + +//UserNoticeByReceiverByTimeByType is 获取改接收者的By Type +func UserNoticeByReceiverByTimeByType(Db *xorm.Engine, uid, t interface{}, sort string, limit, start int) (*[]model.UserNotice, error) { + switch sort { + case "desc": + // 降序 + var m []model.UserNotice + if limit == 0 && start == 0 { + err := Db.Where("receiver = ? AND type = ?", uid, t).Desc("create_at").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + err := Db.Where("receiver = ? AND type = ?", uid, t).Desc("create_at").Limit(limit, start).Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + case "asc": + //升序 + var m []model.UserNotice + if limit == 0 && start == 0 { + err := Db.Where("receiver = ? AND type = ?", uid, t).Asc("create_at").Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + err := Db.Where("receiver = ? AND type = ?", uid, t).Asc("create_at").Limit(limit, start).Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + default: + //默认 + var m []model.UserNotice + if limit == 0 && start == 0 { + err := Db.Where("receiver = ? AND type = ?", uid).Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + err := Db.Where("receiver = ? AND type = ?", uid, t).Limit(limit, start).Find(&m) + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } +} + +//GetUserNoticeByReceiverByTimeByType is 获取改接收者的By Type +func GetUserNoticeByReceiverByTimeByType(Db *xorm.Engine, uid, t interface{}, sort string) (*model.UserNotice, error) { + var ( + has bool + err error + ) + switch sort { + case "desc": + // 降序 + var m model.UserNotice + if t == 0 || t == 1 { + // fmt.Println(t) + has, err = Db.Where("receiver = '0' AND type = ?", t).Desc("create_at").Get(&m) + } else { + has, err = Db.Where("receiver = ? AND type = ?", uid, t).Desc("create_at").Get(&m) + } + if !has { + return &m, logx.Warn("Not found") + } + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + case "asc": + //升序 + var m model.UserNotice + if t == 0 || t == 1 { + has, err = Db.Where("receiver = '0' AND type = ?", t).Asc("create_at").Get(&m) + } else { + has, err = Db.Where("receiver = ? AND type = ?", uid, t).Asc("create_at").Get(&m) + } + if !has { + return &m, logx.Warn("Not found") + } + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + default: + //默认 + var m model.UserNotice + if t == 0 || t == 1 { + // fmt.Println(t) + has, err = Db.Where("receiver = '0' AND type = ?", t).Get(&m) + } else { + has, err = Db.Where("receiver = ? AND type = ?", uid, t).Get(&m) + } + if !has { + return &m, logx.Warn("Not found") + } + if err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } +} + +//GetUserUnReadCountByType is 获取 +func GetUserUnReadCountByType(Db *xorm.Engine, id, t interface{}) (int64, error) { + var ( + count int64 + err error + ) + n := new(model.UserNotice) + if t == 0 || t == 1 { + count, err = Db.Where("receiver = '0' AND type = ? AND status = '0'", t).Count(n) + } else { + count, err = Db.Where("receiver = ? AND type = ? AND status = '0'", id, t).Count(n) + } + if err != nil { + return 0, nil + } + return count, nil +} + +//BatchUpdateUserNoticeRead is 批量更新某种类型的通知 +func BatchUpdateUserNoticeRead(Db *xorm.Engine, uid, t interface{}) error { + sql := "update user_notice set status = ? where receiver = ? and type = ?" + _, err := Db.Exec(sql, 1, uid, t) + if err != nil { + return logx.Warn(err) + } + return nil +} + +//UserNoticeInsertOne 插入一条通知数据 +func UserNoticeInsertOne(Db *xorm.Engine, m *model.UserNotice) error { + _, err := Db.InsertOne(m) + if err != nil { + return logx.Warn(err) + } + return nil +} + +//UserNoticeDeleteByUserDelete is 删除用户通知 +func UserNoticeDeleteByUserDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + return Db.Where("receiver = ? OR sender = ?", uid, uid).Delete(model.UserNotice{}) +} diff --git a/app/db/db_user_profile.go b/app/db/db_user_profile.go new file mode 100644 index 0000000..3102a5f --- /dev/null +++ b/app/db/db_user_profile.go @@ -0,0 +1,438 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/md" + "applet/app/utils/logx" + "errors" + "xorm.io/xorm" +) + +//UserProfileFindByArkID is get userprofile by arkid +func UserProfileFindByArkID(Db *xorm.Engine, id interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("arkid_uid = ?", id).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UserProfileFindByInviteCode is get userprofile by InviteCode +func UserProfileFindByInviteCode(Db *xorm.Engine, code string) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("invite_code = ?", code).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UserProfileFindByInviteCode is get userprofile by InviteCode +func UserProfileFindByCustomInviteCode(Db *xorm.Engine, code string) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("custom_invite_code = ?", code).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UserProfileFindByInviteCodes is get userprofile by InviteCode +func UserProfileFindByInviteCodes(Db *xorm.Engine, codes ...string) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := Db.In("invite_code", codes).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UserProfileFindByCustomInviteCodes is get userprofile by CustomInviteCode +func UserProfileFindByCustomInviteCodes(Db *xorm.Engine, codes ...string) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := Db.In("custom_invite_code", codes).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByID search user_profile by userid +func UserProfileFindByID(Db *xorm.Engine, id interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("uid = ?", id).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileOrderByNew 找最新的记录 +func UserProfileOrderByNew(Db *xorm.Engine) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("invite_code != ''").OrderBy("uid desc").Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByTaobaoOpenID search user_profile ByTaobaoOpenID +func UserProfileFindByTaobaoOpenID(Db *xorm.Engine, openid interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("third_party_taobao_oid = ?", openid).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByQQOpenID search user_profile ByTaobaoOpenID +func UserProfileFindByQQOpenID(Db *xorm.Engine, openid interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("third_party_qq_openid = ?", openid).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByAppleToken search user_profile AppleToken +func UserProfileFindByAppleToken(Db *xorm.Engine, token interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("third_party_apple_token = ?", token).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByWeChatOpenID search user_profile By 微信openid +func UserProfileFindByWeChatOpenID(Db *xorm.Engine, openid interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("third_party_wechat_openid = ?", openid).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByWeChatMiniOpenID search user_profile By 小程序openid +func UserProfileFindByWeChatMiniOpenID(Db *xorm.Engine, openid interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("third_party_wechat_mini_openid = ?", openid).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileFindByWeChatUnionID search user_profile By 微信唯一id +func UserProfileFindByWeChatUnionID(Db *xorm.Engine, openid interface{}) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := Db.Where("third_party_wechat_unionid = ?", openid).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileisExistByTaobaoOpenID is exist by Taobao +func UserProfileisExistByTaobaoOpenID(Db *xorm.Engine, openid string) (bool, error) { + has, err := Db.Where("third_party_taobao_oid = ?", openid).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistByQQOpenID is exist by QQ openid +func UserProfileisExistByQQOpenID(Db *xorm.Engine, openid string) (bool, error) { + has, err := Db.Where("third_party_qq_openid = ?", openid).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistByAppleToken is exist by apple token +func UserProfileisExistByAppleToken(Db *xorm.Engine, token string) (bool, error) { + has, err := Db.Where("third_party_apple_token = ?", token).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistByWeChatOpenID is exist by Wecaht openid +func UserProfileisExistByWeChatOpenID(Db *xorm.Engine, openid string) (bool, error) { + has, err := Db.Where("third_party_wechat_openid = ?", openid).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistByWeChatMiniOpenID is exist by Wecaht openid +func UserProfileisExistByWeChatMiniOpenID(Db *xorm.Engine, openid string) (bool, error) { + has, err := Db.Where("third_party_wechat_mini_openid = ?", openid).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistByWeChatUnionID is exist by Wecaht openid +func UserProfileisExistByWeChatUnionID(Db *xorm.Engine, openid string) (bool, error) { + has, err := Db.Where("third_party_wechat_unionid = ?", openid).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistByRelationIDAndSpecialID is exist by RelationIdAndSpecialId +func UserProfileisExistByRelationIDAndSpecialID(Db *xorm.Engine, SpecialID, RelationID int64) (bool, error) { + has, err := Db.Where("acc_taobao_self_id = ? AND acc_taobao_share_id = ?", SpecialID, RelationID).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileisExistBySpecialID is exist by SpecialId +func UserProfileisExistBySpecialID(Db *xorm.Engine, SpecialID string) (bool, error) { + has, err := Db.Where("acc_taobao_self_id = ? ", SpecialID).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileCountByRelationID 统计relationID数量 +func UserProfileCountByRelationID(Db *xorm.Engine) (total int64, err error) { + relate := new(model.UserProfile) + total, err = Db.Where("acc_taobao_share_id > 0").Count(relate) + return +} + +// UserProfileCountByPUID 统计直推下级数量 +func UserProfileCountByPUID(Db *xorm.Engine, puid int) (total int64, err error) { + relate := new(model.UserProfile) + total, err = Db.Where("parent_uid = ?", puid).Count(relate) + return +} + +// UserProfileisExistByRelationID is exist by RelationID +func UserProfileisExistByRelationID(Db *xorm.Engine, RelationID string) (bool, error) { + has, err := Db.Where("acc_taobao_share_id = ? ", RelationID).Exist(&model.UserProfile{}) + + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileFindByIDs is in sql by ids +func UserProfileFindByIDs(Db *xorm.Engine, uids ...int) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := Db.In("uid", uids).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileByPuid search user_profile by parent_uid +func UserProfileByPuid(Db *xorm.Engine, puid interface{}) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := Db.Where("parent_uid = ?", puid).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UsersProfileInByIds is profiles by ids +func UsersProfileInByIds(Db *xorm.Engine, ids []int, limit, start int) (*[]model.UserProfile, error) { + var m []model.UserProfile + if limit == 0 && start == 0 { + if err := Db.In("uid", ids).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.In("uid", ids).Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersProfileInByUIDByisVerify is In查询 以及是否是有效用户 +func UsersProfileInByUIDByisVerify(Db *xorm.Engine, ids []int, isVerify interface{}) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := Db.In("uid", ids).Where("is_verify = ?", isVerify). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersProfileInByIdsByDesc is 根据某列 降序 +func UsersProfileInByIdsByDesc(Db *xorm.Engine, ids []int, limit, start int, c string) (*[]model.UserProfile, error) { + var m []model.UserProfile + if limit == 0 && start == 0 { + if err := Db.In("uid", ids).Desc(c).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.In("uid", ids).Desc(c).Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersProfileInByIdsByAsc is 根据某列 升序 +func UsersProfileInByIdsByAsc(Db *xorm.Engine, ids []int, limit, start int, c string) (*[]model.UserProfile, error) { + var m []model.UserProfile + if limit == 0 && start == 0 { + if err := Db.In("uid", ids).Asc(c).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.In("uid", ids).Asc(c).Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UsersProfileByAll is 查询所有分享id大于0的数据 +func UsersProfileByTaobaoShateIdNotNull(Db *xorm.Engine, limit, start int) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := Db.Where("acc_taobao_share_id > 0").Limit(limit, start).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserProfileIsExistByUserID is mobile exist +func UserProfileIsExistByUserID(Db *xorm.Engine, id int) (bool, error) { + has, err := Db.Where("uid = ?", id).Exist(&model.UserProfile{}) + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileIsExistByInviteCode is exist ? +func UserProfileIsExistByInviteCode(Db *xorm.Engine, code string) (bool, error) { + has, err := Db.Where("invite_code = ?", code).Exist(&model.UserProfile{}) + if err != nil { + return false, err + } + return has, nil +} + +// UserProfileIsExistByCustomInviteCode is exist ? +func UserProfileIsExistByCustomInviteCode(Db *xorm.Engine, code string) (bool, error) { + has, err := Db.Where("custom_invite_code = ?", code).Exist(&model.UserProfile{}) + if err != nil { + return false, err + } + return has, nil +} + +//UserProfileInsert is insert user +func UserProfileInsert(Db *xorm.Engine, userProfile *model.UserProfile) (int64, error) { + affected, err := Db.Insert(userProfile) + if err != nil { + return 0, err + } + return affected, nil +} + +//UserProfileUpdate is update userprofile +func UserProfileUpdate(Db *xorm.Engine, uid interface{}, userProfile *model.UserProfile, forceCols ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceCols != nil { + affected, err = Db.Where("uid=?", uid).Cols(forceCols...).Update(userProfile) + } else { + affected, err = Db.Where("uid=?", uid).Update(userProfile) + } + if err != nil { + return 0, logx.Warn(err) + } + return affected, nil +} + +//UserProfileUpdateByArkID is update userprofile +func UserProfileUpdateByArkID(Db *xorm.Engine, arkid interface{}, userProfile *model.UserProfile, forceCols ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceCols != nil { + affected, err = Db.Where("arkid_uid=?", arkid).Cols(forceCols...).Update(userProfile) + } else { + affected, err = Db.Where("arkid_uid=?", arkid).Update(userProfile) + } + if err != nil { + return 0, logx.Warn(err) + } + return affected, nil +} + +// UserProfileDelete is delete user profile +func UserProfileDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + return Db.Where("uid = ?", uid).Delete(model.UserProfile{}) +} + +func UserProfileFindByIdWithSession(session *xorm.Session, uid int) (*model.UserProfile, error) { + var m model.UserProfile + if has, err := session.Where("uid = ?", uid).Get(&m); err != nil || has == false { + return nil, logx.Warn(err) + } + return &m, nil +} + +// 在事务中更新用户信息 +func UserProfileUpdateWithSession(session *xorm.Session, uid interface{}, userProfile *model.UserProfile, forceCols ...string) (int64, error) { + var ( + affected int64 + err error + ) + if forceCols != nil { + affected, err = session.Where("uid=?", uid).Cols(forceCols...).Update(userProfile) + } else { + affected, err = session.Where("uid=?", uid).Update(userProfile) + } + if err != nil { + return 0, logx.Warn(err) + } + return affected, nil +} + +// 根据uid获取md.user +func UserAllInfoByUid(Db *xorm.Engine, uid interface{}) (*md.User, error) { + u, err := UserFindByID(Db, uid) + if err != nil { + return nil, err + } + if u == nil { + return nil, errors.New("user is nil") + } + up, err := UserProfileFindByID(Db, uid) + if err != nil { + return nil, err + } + // 获取user 等级 + ul, err := UserLevelByID(Db, u.Level) + if err != nil { + return nil, err + } + user := &md.User{ + Info: u, + Profile: up, + Level: ul, + } + return user, nil +} diff --git a/app/db/db_user_relate.go b/app/db/db_user_relate.go new file mode 100644 index 0000000..e16fd8e --- /dev/null +++ b/app/db/db_user_relate.go @@ -0,0 +1,213 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +// UserRelateInsert is 插入一条数据到用户关系表 +func UserRelateInsert(Db *xorm.Engine, userRelate *model.UserRelate) (int64, error) { + affected, err := Db.Insert(userRelate) + if err != nil { + return 0, err + } + return affected, nil +} + +//UserRelateByPuid is 获取用户关系列表 by puid +func UserRelatesByPuid(Db *xorm.Engine, puid interface{}, limit, start int) (*[]model.UserRelate, error) { + var m []model.UserRelate + if limit == 0 && start == 0 { + if err := Db.Where("parent_uid = ?", puid). + Cols(`id,parent_uid,uid,level,invite_time`). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("parent_uid = ?", puid). + Cols(`id,parent_uid,uid,level,invite_time`).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + + return &m, nil +} + +//UserRelatesByPuidByLv is 获取用户关系列表 by puid 和lv +func UserRelatesByPuidByLv(Db *xorm.Engine, puid, lv interface{}, limit, start int) (*[]model.UserRelate, error) { + var m []model.UserRelate + if limit == 0 && start == 0 { + if err := Db.Where("parent_uid = ? AND level = ?", puid, lv). + Cols(`id,parent_uid,uid,level,invite_time`). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("parent_uid = ? AND level = ?", puid, lv). + Cols(`id,parent_uid,uid,level,invite_time`).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + + return &m, nil +} + +//UserRelatesByPuidByLvByTime is 获取直属 level =1用户关系列表 by puid 和lv by time +func UserRelatesByPuidByLvByTime(Db *xorm.Engine, puid, lv, stime, etime interface{}, limit, start int) (*[]model.UserRelate, error) { + var m []model.UserRelate + if limit == 0 && start == 0 { + if err := Db.Where("parent_uid = ? AND level = ? AND invite_time > ? AND invite_time < ?", puid, lv, stime, etime). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("parent_uid = ? AND level = ? AND invite_time > ? AND invite_time < ?", puid, lv, stime, etime). + Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + + return &m, nil +} + +//UserRelatesByPuidByTime is 获取户关系列表 by puid 和lv by time +func UserRelatesByPuidByTime(Db *xorm.Engine, puid, stime, etime interface{}, limit, start int) (*[]model.UserRelate, error) { + var m []model.UserRelate + if limit == 0 && start == 0 { + if err := Db.Where("parent_uid = ? AND invite_time > ? AND invite_time < ?", puid, stime, etime). + Cols(`id,parent_uid,uid,level,invite_time`). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("parent_uid = ? AND invite_time > ? AND invite_time < ?", puid, stime, etime). + Cols(`id,parent_uid,uid,level,invite_time`).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + + return &m, nil +} + +//UserRelatesByPuidExceptLv is 获取用户关系列表 by puid 和非 lv +func UserRelatesByPuidExceptLv(Db *xorm.Engine, puid, lv interface{}, limit, start int) (*[]model.UserRelate, error) { + var m []model.UserRelate + if limit == 0 && start == 0 { + if err := Db.Where("parent_uid = ? AND level != ?", puid, lv). + Cols(`id,parent_uid,uid,level,invite_time`). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil + } + if err := Db.Where("parent_uid = ? AND level != ?", puid, lv). + Cols(`id,parent_uid,uid,level,invite_time`).Limit(limit, start). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + + return &m, nil +} + +//UserRelateByUID is 获取用户关系表 by uid +func UserRelateByUID(Db *xorm.Engine, uid interface{}) (*model.UserRelate, bool, error) { + r := new(model.UserRelate) + has, err := Db.Where("uid=?", uid).Get(r) + if err != nil { + return nil, false, err + } + return r, has, nil +} + +//UserRelateByUIDByLv is 获取用户关系表 by uid +func UserRelateByUIDByLv(Db *xorm.Engine, uid, lv interface{}) (*model.UserRelate, bool, error) { + r := new(model.UserRelate) + has, err := Db.Where("uid=? AND level=?", uid, lv).Get(r) + if err != nil { + return nil, false, err + } + return r, has, nil +} + +//UserRelateByUIDAndPUID 根据 Puid 和uid 查找 ,用于确认关联 +func UserRelateByUIDAndPUID(Db *xorm.Engine, uid, puid interface{}) (*model.UserRelate, bool, error) { + r := new(model.UserRelate) + has, err := Db.Where("uid=? AND parent_uid=?", uid, puid).Get(r) + if err != nil { + return nil, false, err + } + return r, has, nil +} + +//UserRelatesByPuIDAndLv is 查询用户关系表 获取指定等级和puid的关系 +func UserRelatesByPuIDAndLv(Db *xorm.Engine, puid, lv interface{}) (*[]model.UserRelate, error) { + var m []model.UserRelate + if err := Db.Where("parent_uid = ? AND level = ?", puid, lv). + Cols(`id,parent_uid,uid,level,invite_time`). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserRelateInByUID is In查询 +func UserRelateInByUID(Db *xorm.Engine, ids []int) (*[]model.UserRelate, error) { + var m []model.UserRelate + if err := Db.In("uid", ids). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +// UserRelatesByUIDDescLv is Where 查询 根据level 降序 +func UserRelatesByUIDDescLv(Db *xorm.Engine, id interface{}) (*[]model.UserRelate, error) { + var m []model.UserRelate + if err := Db.Where("uid = ?", id).Desc("level"). + Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} + +//UserRelateCountByPUID is 根据puid 计数 +func UserRelateCountByPUID(Db *xorm.Engine, pid interface{}) (int64, error) { + + count, err := Db.Where("parent_uid = ?", pid).Count(model.UserRelate{}) + if err != nil { + return 0, nil + } + return count, nil +} + +//UserRelateDelete is 删除关联他上级的关系记录,以及删除他下级的关联记录 +func UserRelateDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + // 删除与之上级的记录 + _, err := Db.Where("uid = ?", uid).Delete(model.UserRelate{}) + if err != nil { + return 0, err + } + // 删除与之下级的记录 + _, err = Db.Where("parent_uid = ?", uid).Delete(model.UserRelate{}) + if err != nil { + return 0, err + } + + return 1, nil +} + +//UserRelateDelete is 删除关联他上级的关系记录 +func UserRelateExtendDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + // 删除与之上级的记录 + _, err := Db.Where("uid = ?", uid).Delete(model.UserRelate{}) + if err != nil { + return 0, err + } + return 1, nil +} diff --git a/app/db/db_user_tag.go b/app/db/db_user_tag.go new file mode 100644 index 0000000..c9270ba --- /dev/null +++ b/app/db/db_user_tag.go @@ -0,0 +1,25 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils/logx" + + "xorm.io/xorm" +) + +//UserTagDeleteByUserDelete is 删除用户时删除用户标签 +func UserTagDeleteByUserDelete(Db *xorm.Engine, uid interface{}) (int64, error) { + return Db.Where("uid = ?", uid).Delete(model.UserTag{}) +} + +func UserTagsByUid(Db *xorm.Engine, uid interface{}) ([]string, error) { + var tagList []*model.UserTag + if err := Db.Where("uid=?", uid).Cols("tag_name").Find(&tagList); err != nil { + return nil, logx.Warn(err) + } + var tags []string + for _, item := range tagList { + tags = append(tags, item.TagName) + } + return tags, nil +} diff --git a/app/db/db_user_virtual_amount.go b/app/db/db_user_virtual_amount.go new file mode 100644 index 0000000..4906fca --- /dev/null +++ b/app/db/db_user_virtual_amount.go @@ -0,0 +1,35 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/utils" + "xorm.io/xorm" +) + +func GetUserVirtualAmountOne(session *xorm.Session, uid int, coinId int) (*model.UserVirtualAmount, error) { + + var m model.UserVirtualAmount + isExist, err := session.Table("user_virtual_amount").Where("uid = ? AND coin_id = ?", uid, coinId).Get(&m) + if err != nil { + return nil, err + } + if !isExist { + return nil, nil + } + return &m, nil + +} +func GetUserVirtualAmountSum(eg *xorm.Engine, uid int, coinId int) (string, error) { + //TODO 后面针对单个虚拟币 + var m model.UserVirtualAmount + sum, err := eg.Table("user_virtual_amount").Where("uid = ? ", uid).Sum(&m, "amount") + if err != nil { + return "0", err + } + return utils.Float64ToStr(sum), nil + +} + +/*func UserVirtualAmountUpdateWithSession(session *xorm.Session, m *model.UserVirtualAmount) bool { + +}*/ diff --git a/app/db/db_virtual_coin.go b/app/db/db_virtual_coin.go new file mode 100644 index 0000000..b69b2c3 --- /dev/null +++ b/app/db/db_virtual_coin.go @@ -0,0 +1,63 @@ +package db + +import ( + "applet/app/db/model" + "applet/app/md" + "applet/app/utils/cache" + "errors" + "fmt" + "xorm.io/xorm" +) + +func GetVirtualCoinList(eg *xorm.Engine, masterId string) ([]*model.VirtualCoin, error) { + var m []*model.VirtualCoin + cacheKey := fmt.Sprintf(md.VirtualCoinCfgCacheKey, masterId) + + err := cache.GetJson(cacheKey, &m) + if err != nil || len(m) == 0 { + err := eg.Where("is_use=1").Find(&m) + if err != nil { + return nil, err + } + cache.SetJson(cacheKey, m, md.CfgCacheTime) + } + + return m, nil +} + +// InsertUserVirtualFlow 插入一条虚拟币流水 +func InsertUserVirtualFlow(eg *xorm.Engine, m model.UserVirtualCoinFlow) error { + insert, err := eg.Insert(m) + if err != nil { + return err + } + if insert == 0 { + return errors.New("插入虚拟币流水错误") + } + + return nil +} + +func InsertUserVirtualFlowWithSess(sess *xorm.Session, m model.UserVirtualCoinFlow) error { + insert, err := sess.Insert(m) + if err != nil { + return err + } + if insert == 0 { + return errors.New("插入虚拟币流水错误") + } + + return nil +} + +func GetBlockCoin(eg *xorm.Engine) (*model.VirtualCoin, error) { + var m model.VirtualCoin + get, err := eg.Where("is_block = 1").Get(&m) + if err != nil { + return nil, err + } + if get { + return &m, nil + } + return nil, errors.New("查询有误!") +} diff --git a/app/db/db_virtual_coin_relate.go b/app/db/db_virtual_coin_relate.go new file mode 100644 index 0000000..ee1ff9c --- /dev/null +++ b/app/db/db_virtual_coin_relate.go @@ -0,0 +1,16 @@ +package db + +import ( + "applet/app/db/model" + "xorm.io/xorm" +) + +// 根据订单id查出相关的数据 +func GetVirtualCoinRelateListWithOrdId(engine *xorm.Engine, ordId int64) ([]*model.VirtualCoinRelate, error) { + var list []*model.VirtualCoinRelate + err := engine.Table("virtual_coin_relate").Where("oid = ?", ordId).Find(&list) + if err != nil { + return nil, err + } + return list, nil +} diff --git a/app/db/dbs.go b/app/db/dbs.go new file mode 100644 index 0000000..9f02c9f --- /dev/null +++ b/app/db/dbs.go @@ -0,0 +1,104 @@ +package db + +import ( + "fmt" + "os" + "time" + + "xorm.io/xorm" + "xorm.io/xorm/log" + + "applet/app/cfg" + "applet/app/db/model" + "applet/app/utils/logx" +) + +var DBs map[string]*xorm.Engine + +// 每个站长都要有自己的syscfg 缓存, 键是站长id,值是缓存名 +// var SysCfgMapKey map[string]string + +func NewDB(c *cfg.DBCfg) (*xorm.Engine, error) { + db, err := xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", c.User, c.Psw, c.Host, c.Name)) + if err != nil { + return nil, err + } + db.SetConnMaxLifetime(c.MaxLifetime * time.Second) + db.SetMaxOpenConns(c.MaxOpenConns) + db.SetMaxIdleConns(c.MaxIdleConns) + err = db.Ping() + if err != nil { + return nil, err + } + if c.ShowLog { + db.ShowSQL(true) + db.Logger().SetLevel(log.LOG_DEBUG) + 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 nil, err + } + } + logger := log.NewSimpleLogger(f) + logger.ShowSQL(true) + db.SetLogger(logger) + } + return db, nil +} + +// InitDBs is 初始化多数据库 +func InitDBs(ch chan int) { + // 初始化多数据库 + var tables *[]model.DbMapping + InitMapDbs(cfg.DB, cfg.Prd) + ch <- 1 + // 每10s 查询一次模板数据库的db mapping 表,如果有新增数据库记录,则添加到 DBs中 + ticker := time.NewTicker(time.Duration(time.Second * 120)) + for range ticker.C { + if cfg.Prd { + tables = GetAllDatabasePrd() //默认获取全部 + } else { + tables = GetAllDatabaseDev() //默认获取全部 + } + if tables == nil { + logx.Warn("no database tables data") + continue + } + for _, item := range *tables { + _, ok := DBs[item.DbMasterId] + if !ok { + // 不在db.DBs 则添加进去 + dbCfg := cfg.DBCfg{ + Name: item.DbName, + ShowLog: cfg.DB.ShowLog, + MaxLifetime: cfg.DB.MaxLifetime, + MaxOpenConns: cfg.DB.MaxOpenConns, + MaxIdleConns: cfg.DB.MaxIdleConns, + Path: fmt.Sprintf(cfg.DB.Path, item.DbName), + } + if item.DbHost != "" && item.DbUsername != "" && item.DbPassword != "" { + dbCfg.Host = item.DbHost + dbCfg.User = item.DbUsername + dbCfg.Psw = item.DbPassword + } else { + dbCfg.Host = cfg.DB.Host + dbCfg.User = cfg.DB.User + dbCfg.Psw = cfg.DB.Psw + } + e, err := NewDB(&dbCfg) + if err != nil || e == nil { + logx.Warnf("db engine can't create, please check config, params[host:%s, user:%s, psw: %s, name: %s], err: %v", dbCfg.Host, dbCfg.User, dbCfg.Psw, dbCfg.Name, err) + } else { + DBs[item.DbMasterId] = e + } + } + // 如果 被禁用则删除 + if item.DeletedAt == 1 { + logx.Infof("%s have been removed", item.DbMasterId) + delete(DBs, item.DbMasterId) + } + } + } + +} diff --git a/app/db/dbs_deal_order_user.go b/app/db/dbs_deal_order_user.go new file mode 100644 index 0000000..f71a62c --- /dev/null +++ b/app/db/dbs_deal_order_user.go @@ -0,0 +1,38 @@ +package db + +import ( + "xorm.io/xorm" + + "applet/app/db/model" + "applet/app/utils/logx" +) + +// 批量获取信息 +func DbsDealOrderUseFindByIds(eg *xorm.Engine, types string, ids []string) (*[]model.DealOrderUser, error) { + var ord []model.DealOrderUser + if err := eg.In("id", ids). + Where("type = ?", types). + Find(&ord); err != nil { + return nil, logx.Error(err) + } + if len(ord) == 0 { + return nil, nil + } + return &ord, nil +} + +func DbsDealOrderUserUpdate(eg *xorm.Engine, id int, ord *model.DealOrderUser) error { + if _, err := eg.Where(" `id` = ?", id).AllCols().Update(ord); err != nil { + return logx.Warn(err) + } + return nil +} + +func DbsDealOrderUserInsert(eg *xorm.Engine, ord *model.DealOrderUser) bool { + _, err := eg.InsertOne(ord) + if err != nil { + logx.Warn(err) + return false + } + return true +} diff --git a/app/db/dbs_map.go b/app/db/dbs_map.go new file mode 100644 index 0000000..6a8e453 --- /dev/null +++ b/app/db/dbs_map.go @@ -0,0 +1,194 @@ +package db + +import ( + "errors" + "fmt" + + "xorm.io/xorm" + + "applet/app/cfg" + "applet/app/db/model" + "applet/app/utils/logx" +) + +func MapBaseExists() (bool, error) { + return Db.IsTableExist("db_mapping") +} + +func InitMapDbs(c *cfg.DBCfg, prd bool) { + var tables *[]model.DbMapping + exists, err := MapBaseExists() + if !exists || err != nil { + logx.Fatalf("db_mapping not exists : %v", err) + } + // tables := MapAllDatabases(debug) + if prd { + tables = GetAllDatabasePrd() //debug 获取生产 + } else { + tables = GetAllDatabaseDev() //debug 获取开发 + } + + if tables == nil { + logx.Fatal("no database tables data") + } + var e *xorm.Engine + DBs = map[string]*xorm.Engine{} + for _, v := range *tables { + if v.DbName != "" && v.DeletedAt == 0 && v.DbName != c.Name { + dbCfg := cfg.DBCfg{ + Name: v.DbName, + ShowLog: c.ShowLog, + MaxLifetime: c.MaxLifetime, + MaxOpenConns: c.MaxOpenConns, + MaxIdleConns: c.MaxIdleConns, + Path: fmt.Sprintf(c.Path, v.DbName), + } + if v.DbHost != "" && v.DbUsername != "" && v.DbPassword != "" { + dbCfg.Host = v.DbHost + dbCfg.User = v.DbUsername + dbCfg.Psw = v.DbPassword + } else { + dbCfg.Host = c.Host + dbCfg.User = c.User + dbCfg.Psw = c.Psw + } + e, err = NewDB(&dbCfg) + if err != nil || e == nil { + logx.Warnf("db engine can't create, please check config, params[host:%s, user:%s, psw: %s, name: %s], err: %v", dbCfg.Host, dbCfg.User, dbCfg.Psw, dbCfg.Name, err) + } else { + DBs[v.DbMasterId] = e + } + } + } +} + +func MapAllDatabases(debug bool) *[]model.DbMapping { + sql := "`db_name` != ?" + if debug { + sql = "`db_name` = ?" + } + var m []model.DbMapping + if err := Db.Where(sql, cfg.DB.Name).Find(&m); err != nil || len(m) == 0 { + logx.Warn(err) + return nil + } + return &m +} + +// GetAllDatabasePrd is 获取生成库 所有db 除了 deleted_at = 1 的 +func GetAllDatabasePrd() *[]model.DbMapping { + var m []model.DbMapping + if err := Db.Where("deleted_at != ? AND is_dev = '0' ", 1).Find(&m); err != nil || len(m) == 0 { + logx.Warn(err) + return nil + } + return &m +} + +// GetAllDatabaseDev is 获取开发库 所有db 除了 deleted_at = 1 的 +func GetAllDatabaseDev() *[]model.DbMapping { + var m []model.DbMapping + var err error + fmt.Println("cfg.Local is: ", cfg.Local) + if cfg.Local { // 本地调试 加快速度 + fmt.Println("notice:LOCAL TEST, only masterId:** 123456 ** available!") + err = Db.Where("deleted_at != ? AND is_dev = '1' AND db_master_id=?", 1, 123456).Find(&m) + } else { + err = Db.Where("deleted_at != ? AND is_dev = '1' ", 1).Find(&m) + } + + //err := Db.Where("deleted_at != ? AND is_dev = '1' and db_master_id='123456'", 1).Find(&m) + if err != nil || len(m) == 0 { + logx.Warn(err) + return nil + } + return &m +} + +//GetDatabaseByMasterID is 根据站长id 获取对应的的数据库信息 +func GetDatabaseByMasterID(Db *xorm.Engine, id string) (*model.DbMapping, error) { + var m model.DbMapping + has, err := Db.Where("db_master_id=?", id).Get(&m) + if !has { + return nil, errors.New("Not Found DB data by " + id) + } + if err != nil { + return nil, err + } + if m.DbHost == "" { + m.DbHost = cfg.DB.Host + m.DbUsername = cfg.DB.User + m.DbPassword = cfg.DB.Psw + } + return &m, nil +} + +//SessionGetDatabaseByMasterID is 根据站长id 获取对应的的数据库信息 +func SessionGetDatabaseByMasterID(Db *xorm.Session, id string) (*model.DbMapping, error) { + var m model.DbMapping + has, err := Db.Where("db_master_id=?", id).Get(&m) + if !has { + return nil, errors.New("Not Found DB data by " + id) + } + if err != nil { + return nil, err + } + if m.DbHost == "" { + m.DbHost = cfg.DB.Host + m.DbName = cfg.DB.Name + m.DbUsername = cfg.DB.User + m.DbPassword = cfg.DB.Psw + } + return &m, nil +} + +// 获取自动任务队列 +func MapCrontabCfg(eg *xorm.Engine) *[]model.SysCfg { + var c []model.SysCfg + // 数据库查询如果有下划线会认为是一个任意字符 + if err := eg.Where("`key` LIKE 'mall_cron\\_%' AND val != ''").Cols("`key`,`val`").Find(&c); err != nil || len(c) == 0 { + logx.Warn(err) + return nil + } + return &c +} + +// 获取官方域名 +func GetOfficialDomainInfoByType(Db *xorm.Engine, masterId, key string) (string, error) { + type SysCfg struct { + K string + V string + Memo string + } + var domainBase SysCfg + + has, err := Db.Where("k=?", "domain_base").Get(&domainBase) + if err != nil { + return "", err + } + if has == false { + return "", errors.New("can not find key by : domain_base") + } + + if key == "wap" { + return "h5." + masterId + "." + domainBase.V, nil + } + + if key == "api" { + var apiDomain SysCfg + has, err = Db.Where("k=?", "domain_api_base").Get(&apiDomain) + if err != nil { + return "", err + } + if has == false { + return "", errors.New("can not find key by : domain_api_base") + } + return apiDomain.V, nil + } + + if key == "admin" { + return "admin." + masterId + "." + domainBase.V, nil + } + // 默认返回H5的 + return "h5." + masterId + "." + domainBase.V, nil +} diff --git a/app/db/dbs_plan_commission.go b/app/db/dbs_plan_commission.go new file mode 100644 index 0000000..0edd340 --- /dev/null +++ b/app/db/dbs_plan_commission.go @@ -0,0 +1,30 @@ +package db + +import ( + "xorm.io/xorm" + + "applet/app/db/model" + "applet/app/utils/logx" +) + +func DbsPlanCommissionById(eg *xorm.Engine, id int) (*model.PlanCommission, error) { + var m model.PlanCommission + if isGet, err := eg.Where("id = ?", id).Get(&m); err != nil || !isGet { + return nil, logx.Warn(err) + } + return &m, nil +} + +func DbsPlanCommissionByIds(eg *xorm.Engine, ids ...int) []*model.PlanCommission { + var m []*model.PlanCommission + var err error + if len(ids) > 0 { + err = eg.In("id", ids).Find(&m) + } else { + err = eg.Find(&m) + } + if err != nil { + return nil + } + return m +} diff --git a/app/db/dbs_plan_reward.go b/app/db/dbs_plan_reward.go new file mode 100644 index 0000000..a75e698 --- /dev/null +++ b/app/db/dbs_plan_reward.go @@ -0,0 +1,51 @@ +package db + +import ( + "xorm.io/xorm" + + "applet/app/db/model" + "applet/app/utils/logx" +) + +func DbsPlanRewardByPvd(eg *xorm.Engine, pvd string) (*model.PlanReward, error) { + m := &model.PlanReward{} + if isGet, err := eg.Where("pvd = ?", pvd).Get(m); err != nil || !isGet { + return nil, logx.Warn(err) + } + + return m, nil +} + +func DbsPlanRewardByPvds(eg *xorm.Engine, pvds ...string) ([]*model.PlanReward, error) { + var m []*model.PlanReward + var err error + if len(pvds) > 0 { + err = eg.In("pvd", pvds).Find(&m) + } else { + err = eg.Find(&m) + } + if err != nil { + return nil, err + } + return m, nil +} + +// 查询使用自动结算的平台 +func DbsPlanRewardIsAutoSettle(eg *xorm.Engine) ([]*model.PlanReward, error) { + var m []*model.PlanReward + var err error + if err = eg.In("pvd", "mall_goods", "mall_group_buy", "mall_goods_user_lv").Where(" settle_mode=? AND plan_settle_id<>?", 1, 0).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return m, nil +} + +// 查出开启的渠道 +func DbsPlanRewardIsOpen(eg *xorm.Engine) ([]*model.PlanReward, error) { + var m []*model.PlanReward + var err error + if err = eg.Where(" state=?", 1).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return m, nil +} diff --git a/app/db/dbs_plan_settle.go b/app/db/dbs_plan_settle.go new file mode 100644 index 0000000..f3767aa --- /dev/null +++ b/app/db/dbs_plan_settle.go @@ -0,0 +1,17 @@ +package db + +import ( + "applet/app/db/model" + "xorm.io/xorm" +) + +func PlanSettleListByIds(eg *xorm.Engine, ids []int) ([]*model.PlanSettle, error) { + var m []*model.PlanSettle + var err error + err = eg.In("id", ids).Find(&m) + if err != nil { + return nil, err + } + + return m, nil +} diff --git a/app/db/dbs_sys_cfg.go b/app/db/dbs_sys_cfg.go new file mode 100644 index 0000000..25c0a14 --- /dev/null +++ b/app/db/dbs_sys_cfg.go @@ -0,0 +1,55 @@ +package db + +import ( + "xorm.io/xorm" + + "applet/app/db/model" + "applet/app/utils/logx" +) + +// 系统配置get +func DbsSysCfgGetAll(eg *xorm.Engine) (*[]model.SysCfg, error) { + var cfgList []model.SysCfg + if err := eg.Cols("`key`,`val`").Find(&cfgList); err != nil { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +// 获取一条记录 +func DbsSysCfgGet(eg *xorm.Engine, key string) (*model.SysCfg, error) { + var cfgList model.SysCfg + if has, err := eg.Where("`key`=?", key).Get(&cfgList); err != nil || has == false { + return nil, logx.Error(err) + } + return &cfgList, nil +} + +func DbsSysCfgInsert(eg *xorm.Engine, key, val string) bool { + cfg := model.SysCfg{Key: key, Val: val} + _, err := eg.Where("`key`=?", key).Cols("val,memo").Update(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} +func DbsSysCfgInserts(eg *xorm.Engine, key, val string) bool { + cfg := model.SysCfg{Key: key, Val: val} + _, err := eg.InsertOne(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} + +func DbsSysCfgUpdate(eg *xorm.Engine, key, val string) bool { + cfg := model.SysCfg{Key: key, Val: val} + _, err := eg.Where("`key`=?", key).Cols("val").Update(&cfg) + if err != nil { + logx.Error(err) + return false + } + return true +} diff --git a/app/db/dbs_user.go b/app/db/dbs_user.go new file mode 100644 index 0000000..d6f9b09 --- /dev/null +++ b/app/db/dbs_user.go @@ -0,0 +1,41 @@ +package db + +import ( + "xorm.io/xorm" + + "applet/app/db/model" + "applet/app/utils/logx" +) + +func DbsUser(eg *xorm.Engine, uid int) (*[]model.UserRelate, error) { + var userRelate []model.UserRelate + if err := eg.Where("uid = ?", uid).Asc("level").Find(&userRelate); err != nil { + return nil, logx.Error(err) + } + if len(userRelate) == 0 { + return nil, nil + } + return &userRelate, nil +} + +func DbsUserFindByIds(eg *xorm.Engine, uid []int) (*[]model.User, error) { + var users []model.User + if err := eg.In("uid", uid).Asc("level").Find(&users); err != nil { + return nil, logx.Error(err) + } + if len(users) == 0 { + return nil, nil + } + return &users, nil +} + +func DbsUserRelate(eg *xorm.Engine, uid int) (*[]model.UserRelate, error) { + var userRelate []model.UserRelate + if err := eg.Where("uid = ?", uid).Asc("level").Find(&userRelate); err != nil { + return nil, logx.Error(err) + } + if len(userRelate) == 0 { + return nil, nil + } + return &userRelate, nil +} diff --git a/app/db/dbs_user_profile.go b/app/db/dbs_user_profile.go new file mode 100644 index 0000000..7185376 --- /dev/null +++ b/app/db/dbs_user_profile.go @@ -0,0 +1,44 @@ +package db + +import ( + "xorm.io/xorm" + + "applet/app/db/model" + "applet/app/utils/logx" +) + +// UserProfileFindByIDs is in sql by ids +func DbsUserProfileFindByIDs(eg *xorm.Engine, uids ...int) (*[]model.UserProfile, error) { + var m []model.UserProfile + if err := eg.In("uid", uids).Find(&m); err != nil { + return nil, logx.Warn(err) + } + return &m, nil +} +func DbsUserProfileFindByIDsList(eg *xorm.Engine, uids []int) (*[]model.UserProfile, error) { + var m []model.UserProfile + col := "uid" + if err := eg.In(col, uids).Find(&m); err != nil { + return nil, logx.Warn(err) + } + if len(m) == 0 { + return nil, nil + } + return &m, nil +} +func DbsUserProfileFindByTbPids(eg *xorm.Engine, pids []int64, isShare bool) (*[]model.UserProfile, error) { + var m []model.UserProfile + col := "acc_taobao_self_id" + col_where := "acc_taobao_self_id>0" + if isShare { + col = "acc_taobao_share_id" + col_where = "acc_taobao_share_id>0" + } + if err := eg.Where(col_where).In(col, pids).Find(&m); err != nil { + return nil, logx.Warn(err) + } + if len(m) == 0 { + return nil, nil + } + return &m, nil +} diff --git a/app/db/model/TkBrand.go b/app/db/model/TkBrand.go new file mode 100644 index 0000000..283694b --- /dev/null +++ b/app/db/model/TkBrand.go @@ -0,0 +1,22 @@ +package model + +type TKBrand struct { + BrandId int `json:"brandId" xorm:"not null pk autoincr comment('品牌id') INT(10)"` //品牌ID + BrandName string `json:"brandName" xorm:"not null default '' comment('品牌名称') VARCHAR(64)"` //品牌名称 + BrandLogo string `json:"brandLogo" xorm:"not null default '' comment('品牌logo') VARCHAR(255)"` //品牌logo + BrandEnglish string `json:"brandEnglish" xorm:"not null default '' comment('品牌英文名称') VARCHAR(255)"` //品牌英文名称 + Name string `json:"name" xorm:"not null default '' comment('官方旗舰店旗舰店铺名称') VARCHAR(255)"` //官方旗舰店旗舰店铺名称 + SellerId string `json:"sellerId" xorm:"not null default '' comment('店铺ID') VARCHAR(255)"` //店铺ID + BrandScore int `json:"brandScore" xorm:"not null default '' comment('店铺评分') INT(10)"` //店铺评分 + Location string `json:"location" xorm:"not null default '' comment('发源地') VARCHAR(255)"` //发源地 + EstablishTime string `json:"establishTime" xorm:"not null default '' comment('创立时间') VARCHAR(255)"` //创立时间 + BelongTo string `json:"belongTo" xorm:"not null default '' comment('所属公司') VARCHAR(255)"` //所属公司 + Position string `json:"position" xorm:"not null default '' comment('品牌定位:1. 奢侈 2.轻奢 3.大众') VARCHAR(255)"` //品牌定位:1. 奢侈 2.轻奢 3.大众 + Consumer string `json:"consumer" xorm:"not null default '' comment('品牌定位:1. 奢侈 2.轻奢 3.大众') VARCHAR(255)"` //消费群体 + Label string `json:"Label" xorm:"not null default '' comment('标签') VARCHAR(255)"` //标签 + SimpleLabel string `json:"simpleLabel" xorm:"not null default '' comment('一句话评价') VARCHAR(255)"` //一句话评价 + Cids string `json:"cids" xorm:"not null default '' comment('主营类目(可能有多个主营类目,用逗号隔开)') VARCHAR(255)"` //主营类目(可能有多个主营类目,用逗号隔开) + BrandDesc string `json:"brandDesc" xorm:"not null default '' comment('品牌介绍') VARCHAR(255)"` //品牌介绍 (2020.10.30更新字段) + FansNum int `json:"fansNum" xorm:"not null default '' comment('粉丝数') INT(10)"` //粉丝数 (2020.10.30更新字段) + Sales2h int `json:"sales2h" xorm:"not null default '' comment('近期销量') INT(10)"` //近期销量 (2020.10.30更新字段) +} diff --git a/app/db/model/acquisition_log.go b/app/db/model/acquisition_log.go new file mode 100644 index 0000000..9fd9c4b --- /dev/null +++ b/app/db/model/acquisition_log.go @@ -0,0 +1,15 @@ +package model + +import ( + "time" +) + +type AcquisitionLog struct { + Id int64 `json:"id" xorm:"pk autoincr comment('主键') BIGINT(10)"` + ParentUid int `json:"parent_uid" xorm:"not null default 0 comment('上级会员ID') unique(idx_union_u_p_id) INT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('关联UserID') unique(idx_union_u_p_id) INT(20)"` + Level int `json:"level" xorm:"not null default 1 comment('推广等级(1直属,大于1非直属)') INT(10)"` + InviteTime time.Time `json:"invite_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('邀请时间') TIMESTAMP"` + State int `json:"state" xorm:"not null default 0 comment('0为未完成,1为已完成') TINYINT(1)"` + CompleteCon string `json:"complete_con" xorm:"not null default '' VARCHAR(16)"` +} diff --git a/app/db/model/acquisition_reward_log.go b/app/db/model/acquisition_reward_log.go new file mode 100644 index 0000000..7f449a5 --- /dev/null +++ b/app/db/model/acquisition_reward_log.go @@ -0,0 +1,21 @@ +package model + +type AcquisitionRewardLog struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Uid int `json:"uid" xorm:"not null default 0 INT(11)"` + ToUid int `json:"to_uid" xorm:"not null default 0 comment('被邀请人uid') INT(11)"` + Title string `json:"title" xorm:"not null comment('标题') VARCHAR(255)"` + Source int `json:"source" xorm:"not null default 0 comment('1为直推奖励 +2为间推奖励 +3为额外奖励 +4为榜单奖励 +来源标识') TINYINT(4)"` + SourceText string `json:"source_text" xorm:"not null default '' comment('来源text') VARCHAR(255)"` + JobsTag int `json:"jobs_tag" xorm:"comment('任务标识,只有额外奖励有') TINYINT(1)"` + Money string `json:"money" xorm:"not null default 0.00 comment('奖励金额') DECIMAL(10,2)"` + CreatedAt int `json:"created_at" xorm:"not null comment('创建时间') INT(10)"` + GivenAt int `json:"given_at" xorm:"comment('奖励发放时间') INT(10)"` + State int `json:"state" xorm:"not null default 1 comment('发放状态 0未发放 1已发放') TINYINT(1)"` + IsFrozen int `json:"is_frozen" xorm:"not null default 0 comment('冻结状态 0未冻结 1已冻结') TINYINT(1)"` + UpdatedAt int `json:"updated_at" xorm:"comment('更新时间') INT(10)"` +} diff --git a/app/db/model/adm_list.go b/app/db/model/adm_list.go new file mode 100644 index 0000000..4994786 --- /dev/null +++ b/app/db/model/adm_list.go @@ -0,0 +1,19 @@ +package model + +import ( + "time" +) + +type AdmList struct { + AdmId int `json:"adm_id" xorm:"not null pk autoincr INT(10)"` + AdmName string `json:"adm_name" xorm:"not null default '' comment('管理员名称') VARCHAR(64)"` + AdmPsw string `json:"adm_psw" xorm:"not null default '' comment('密码') CHAR(32)"` + Phone string `json:"phone" xorm:"not null default '' comment('手机号') VARCHAR(16)"` + RoleId int `json:"role_id" xorm:"not null default 0 comment('0为超管,其它请参照角色表') TINYINT(3)"` + State int `json:"state" xorm:"not null default 1 comment('0关闭,1启用') TINYINT(1)"` + Ip string `json:"ip" xorm:"not null default '' comment('最后登陆IP,如果与当前IP不一致,请强退') VARCHAR(15)"` + Token string `json:"token" xorm:"not null default '' comment('最后登陆token') unique CHAR(40)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(200)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + LatestLoginAt int `json:"latest_login_at" xorm:"not null default 0 comment('最新登陆时间,时间戳') INT(10)"` +} diff --git a/app/db/model/adm_log.go b/app/db/model/adm_log.go new file mode 100644 index 0000000..db747b6 --- /dev/null +++ b/app/db/model/adm_log.go @@ -0,0 +1,12 @@ +package model + +import ( + "time" +) + +type AdmLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + AdmId int `json:"adm_id" xorm:"not null default 0 comment('管理员ID') INT(11)"` + Ip string `json:"ip" xorm:"not null default '' comment('IP') VARCHAR(15)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('登陆时间') TIMESTAMP"` +} diff --git a/app/db/model/adm_op_log.go b/app/db/model/adm_op_log.go new file mode 100644 index 0000000..59bc2e9 --- /dev/null +++ b/app/db/model/adm_op_log.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type AdmOpLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + AdmId int `json:"adm_id" xorm:"not null pk default 0 comment('管理员ID') INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 comment('被操作人UID,如非操作用户信息,默认为0') INT(11)"` + Perm string `json:"perm" xorm:"not null default '' comment('操作权限,敏感模块权限') VARCHAR(128)"` + Memo string `json:"memo" xorm:"not null default '' comment('操作描述') VARCHAR(512)"` + Ip string `json:"ip" xorm:"not null default '' comment('操作IP') VARCHAR(15)"` + State int `json:"state" xorm:"not null default 1 comment('操作结果1成功,0失败') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` +} \ No newline at end of file diff --git a/app/db/model/adm_role.go b/app/db/model/adm_role.go new file mode 100644 index 0000000..81c8398 --- /dev/null +++ b/app/db/model/adm_role.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type AdmRole struct { + RoleId int `json:"role_id" xorm:"not null pk comment('角色ID') INT(10)"` + RoleName string `json:"role_name" xorm:"not null default '' comment('角色名称') VARCHAR(64)"` + Perms string `json:"perms" xorm:"comment('权限列表') TEXT"` + IsSuper int `json:"is_super" xorm:"not null default 0 comment('是否超管') TINYINT(1)"` + State int `json:"state" xorm:"not null default 1 comment('0禁用,1启用') TINYINT(1)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(500)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/adm_role_perms.go b/app/db/model/adm_role_perms.go new file mode 100644 index 0000000..ee6a98f --- /dev/null +++ b/app/db/model/adm_role_perms.go @@ -0,0 +1,10 @@ +package model + +type AdmRolePerms struct { + Id int `json:"id" xorm:"not null pk autoincr comment('ID') INT(10)"` + Pid int `json:"pid" xorm:"not null comment('父ID') INT(10)"` + Perm string `json:"perm" xorm:"not null default '' comment('权限匹配名') VARCHAR(128)"` + PermName string `json:"perm_name" xorm:"not null default '' comment('后台显示名称') VARCHAR(64)"` + Sort int `json:"sort" xorm:"not null default 0 comment('显示排序,越大越前') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('是否开启') TINYINT(1)"` +} diff --git a/app/db/model/app_release.go b/app/db/model/app_release.go new file mode 100644 index 0000000..18049f6 --- /dev/null +++ b/app/db/model/app_release.go @@ -0,0 +1,14 @@ +package model + +type AppRelease struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Version string `json:"version" xorm:"not null default '' VARCHAR(255)"` + Os int `json:"os" xorm:"comment('系统:1.Android;2.ios') TINYINT(1)"` + Memo string `json:"memo" xorm:"TEXT"` + Src string `json:"src" xorm:"VARCHAR(255)"` + CreatedAt int `json:"created_at" xorm:"INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('版本状态:0关 1开') TINYINT(1)"` + UpdateNotice int `json:"update_notice" xorm:"not null default 1 comment('更新提醒:0关 1开') TINYINT(1)"` + YybUrl string `json:"yyb_url" xorm:"comment('应用宝url') VARCHAR(255)"` + ForceUpdate int `json:"force_update" xorm:"comment('强制更新(安卓)') TINYINT(1)"` +} diff --git a/app/db/model/article.go b/app/db/model/article.go new file mode 100644 index 0000000..706bcee --- /dev/null +++ b/app/db/model/article.go @@ -0,0 +1,22 @@ +package model + +type Article struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pid int `json:"pid" xorm:"not null default 0 comment('一级分类,对应article_cate表parent_id=0的记录') INT(11)"` + CateId int `json:"cate_id" xorm:"not null default 0 comment('分类ID') index INT(11)"` + TypeId int `json:"type_id" xorm:"not null default 0 comment('类型,对应article_cate表pid=0的记录') index INT(11)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示:0不显示;1显示') TINYINT(1)"` + CreatedAt int `json:"created_at" xorm:"comment('创建时间') INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"comment('更新时间') INT(11)"` + Cover string `json:"cover" xorm:"comment('封面') VARCHAR(255)"` + Tags string `json:"tags" xorm:"comment('标签') VARCHAR(2048)"` + Content string `json:"content" xorm:"comment('内容') LONGTEXT"` + Info string `json:"info" xorm:"comment('描述') LONGTEXT"` + IsSelected int `json:"is_selected" xorm:"not null default 0 comment('是否精选') TINYINT(1)"` + IsRecommend int `json:"is_recommend" xorm:"not null default 0 comment('是否推荐') TINYINT(1)"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') INT(11)"` + WatchCount int `json:"watch_count" xorm:"not null default 0 comment('观看人数') INT(11)"` + LikeCount int `json:"like_count" xorm:"not null default 0 comment('喜爱人数') INT(11)"` + ForwardCount int `json:"forward_count" xorm:"not null default 0 comment('转发人数') INT(11)"` +} diff --git a/app/db/model/article_cate.go b/app/db/model/article_cate.go new file mode 100644 index 0000000..9309685 --- /dev/null +++ b/app/db/model/article_cate.go @@ -0,0 +1,10 @@ +package model + +type ArticleCate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pid int `json:"pid" xorm:"default 0 comment('上级,0表示文章类型') INT(11)"` + Name string `json:"name" xorm:"not null default '' VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + CreatedAt int `json:"created_at" xorm:"INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"INT(11)"` +} diff --git a/app/db/model/article_like_log.go b/app/db/model/article_like_log.go new file mode 100644 index 0000000..1a52a59 --- /dev/null +++ b/app/db/model/article_like_log.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type ArticleLikeLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + ArticleId int64 `json:"article_id" xorm:"not null default 0 comment('文章ID') unique(IDX_LOG) BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') unique(IDX_LOG) INT(10)"` + //CreatedAt time.Time `json:"created_at" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') TIMESTAMP"` + CreatedAt time.Time `json:"created_at" xorm:"created"` +} diff --git a/app/db/model/article_watch_log.go b/app/db/model/article_watch_log.go new file mode 100644 index 0000000..e496c7d --- /dev/null +++ b/app/db/model/article_watch_log.go @@ -0,0 +1,12 @@ +package model + +import ( + "time" +) + +type ArticleWatchLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + ArticleId int64 `json:"article_id" xorm:"not null default 0 comment('文章ID') unique(IDX_LOG) BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') unique(IDX_LOG) INT(10)"` + CreatedAt time.Time `json:"created_at" xorm:"created"` +} diff --git a/app/db/model/business_college_ord.go b/app/db/model/business_college_ord.go new file mode 100644 index 0000000..ef641a0 --- /dev/null +++ b/app/db/model/business_college_ord.go @@ -0,0 +1,15 @@ +package model + +import ( + "time" +) + +type BusinessCollegeOrd struct { + Id int64 `json:"id" xorm:"pk comment('订单id') BIGINT(22)"` + Uid int `json:"uid" xorm:"not null default 0 comment('uid') INT(10)"` + ModId int `json:"mod_id" xorm:"not null default 0 comment('模块ID') INT(10)"` + PayAmount string `json:"pay_amount" xorm:"not null default 0 comment('付费金额') DECIMAL(2)"` + PayChannel int `json:"pay_channel" xorm:"not null default 0 comment('1:支付宝,2:微信,3:余额') TINYINT(1)"` + State int `json:"state" xorm:"not null default 0 comment('支付状态:0未支付1已支付') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"created"` +} diff --git a/app/db/model/capital_pool.go b/app/db/model/capital_pool.go new file mode 100644 index 0000000..fff71b1 --- /dev/null +++ b/app/db/model/capital_pool.go @@ -0,0 +1,18 @@ +package model + +import ( + "time" +) + +type CapitalPool struct { + Id int `json:"id" xorm:"not null pk autoincr comment('主键id') INT(11)"` + IsUse int `json:"is_use" xorm:"not null comment('是否开启(否:0;是:1)') TINYINT(1)"` + IsAuto int `json:"is_auto" xorm:"not null default 0 comment('是否自动分红(否:0;是:1)') TINYINT(1)"` + BonusType string `json:"bonus_type" xorm:"not null default '0' comment('分红类型(1佣金,2积分,3区块币)多个以逗号隔开') VARCHAR(255)"` + BonusDateType int `json:"bonus_date_type" xorm:"not null default 0 comment('日期类型(1每天,2固定时间)') TINYINT(1)"` + BonusTime string `json:"bonus_time" xorm:"default '0' comment('分红日期(1,15,30)多个日期已逗号分隔开;ps 只有日期类型是2才是有数据') VARCHAR(255)"` + BonusLevelType int `json:"bonus_level_type" xorm:"not null default 0 comment('用户等级分红类型(1,指定等级,2大于或等于指定等级)') TINYINT(1)"` + UserLevelGroup string `json:"user_level_group" xorm:"not null comment('指定用户等级组json') TEXT"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/channel_activity.go b/app/db/model/channel_activity.go new file mode 100644 index 0000000..9780871 --- /dev/null +++ b/app/db/model/channel_activity.go @@ -0,0 +1,15 @@ +package model + +type ChannelActivity struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Img string `json:"img" xorm:"default '' comment('图片') VARCHAR(255)"` + Sort int `json:"sort" xorm:"default 0 comment('排序') INT(5)"` + IsUse int `json:"is_use" xorm:"default 0 comment('是否使用') INT(1)"` + Source string `json:"source" xorm:"default '' comment('来源 淘宝=>tb 京东=>jd 拼多多=>pdd 唯品会=>wph 苏宁易购=>snyg 考拉=>kaola') VARCHAR(50)"` + IsRecommend int `json:"is_recommend" xorm:"default 0 comment('是否官方推荐') INT(1)"` + StartTime int `json:"start_time" xorm:"default 0 comment('开始时间') INT(11)"` + EndTime int `json:"end_time" xorm:"default 0 comment('结束时间') INT(11)"` + ActivityId string `json:"activity_id" xorm:"default '' comment('活动id') VARCHAR(100)"` + Url string `json:"url" xorm:"comment('活动链接') TEXT"` + Name string `json:"name" xorm:"default '' comment('名称') VARCHAR(255)"` +} diff --git a/app/db/model/city.go b/app/db/model/city.go new file mode 100644 index 0000000..5437b62 --- /dev/null +++ b/app/db/model/city.go @@ -0,0 +1,7 @@ +package model + +type City struct { + Name string `json:"name" xorm:"VARCHAR(64)"` + Id string `json:"id" xorm:"not null pk VARCHAR(12)"` + ProvinceId string `json:"province_id" xorm:"index VARCHAR(12)"` +} diff --git a/app/db/model/cloud_bundle.go b/app/db/model/cloud_bundle.go new file mode 100644 index 0000000..1497d9d --- /dev/null +++ b/app/db/model/cloud_bundle.go @@ -0,0 +1,17 @@ +package model + +type CloudBundle struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Os int `json:"os" xorm:"not null default 1 comment('系统类型:1.Android; 2.IOS') TINYINT(1)"` + Version string `json:"version" xorm:"not null default '' comment('版本号') VARCHAR(255)"` + Modules string `json:"modules" xorm:"not null default '' comment('包含的模块') VARCHAR(255)"` + ApplyAt int `json:"apply_at" xorm:"comment('申请时间') INT(11)"` + FinishAt int `json:"finish_at" xorm:"comment('完成时间') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('状态:正在排队0,正在同步代码1,正在更新配置2,正在混淆3,正在打包4,正在上传5,打包成功999,异常-1') SMALLINT(5)"` + Memo string `json:"memo" xorm:"comment('备注') TEXT"` + ErrorMsg string `json:"error_msg" xorm:"comment('错误信息') TEXT"` + Src string `json:"src" xorm:"comment('包源地址') VARCHAR(255)"` + BuildId string `json:"build_id" xorm:"comment('build版本ID') VARCHAR(255)"` + BuildNumber string `json:"build_number" xorm:"default '' VARCHAR(255)"` + TemplateDuringAudit string `json:"template_during_audit" xorm:"not null default '' VARCHAR(255)"` +} diff --git a/app/db/model/county.go b/app/db/model/county.go new file mode 100644 index 0000000..9b915e9 --- /dev/null +++ b/app/db/model/county.go @@ -0,0 +1,7 @@ +package model + +type County struct { + Name string `json:"name" xorm:"VARCHAR(64)"` + Id string `json:"id" xorm:"not null pk VARCHAR(12)"` + CityId string `json:"city_id" xorm:"index VARCHAR(12)"` +} diff --git a/app/db/model/db_mapping.go b/app/db/model/db_mapping.go new file mode 100644 index 0000000..f2f5d06 --- /dev/null +++ b/app/db/model/db_mapping.go @@ -0,0 +1,18 @@ +package model + +import ( + "time" +) + +type DbMapping struct { + DbMasterId string `json:"db_master_id" xorm:"not pk null comment('站长id') VARCHAR(32)"` + DbHost string `json:"db_host" xorm:"not null default '' comment('数据库连接(带port)') VARCHAR(255)"` + DbUsername string `json:"db_username" xorm:"not null default '' comment('数据库用户名') VARCHAR(255)"` + DbPassword string `json:"db_password" xorm:"not null default '' comment('数据库用户名密码') VARCHAR(255)"` + DbName string `json:"db_name" xorm:"not null comment('数据库名') VARCHAR(255)"` + ExternalMysql string `json:"external_mysql" xorm:"not null default '0' comment('是否外部mysql(0是内部,1是外部)') VARCHAR(255)"` + IsDev int `json:"is_dev" xorm:"not null default 0 comment('开发库是1,0是生产库') TINYINT(1)"` + CreatedAt time.Time `json:"created_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdatedAt time.Time `json:"updated_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 comment('是否已删除') TINYINT(1)"` +} diff --git a/app/db/model/deal_order_user.go b/app/db/model/deal_order_user.go new file mode 100644 index 0000000..40bebc9 --- /dev/null +++ b/app/db/model/deal_order_user.go @@ -0,0 +1,11 @@ +package model + +type DealOrderUser struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Type string `json:"type" xorm:"default '' comment('类型 activity_order活动订单 oil_order加油订单') index VARCHAR(100)"` + Uid int `json:"uid" xorm:"default 0 comment('用户id') index INT(11)"` + Time int `json:"time" xorm:"default 0 comment('写入时间') index INT(11)"` + EndTime int `json:"end_time" xorm:"default 0 comment('结束时间') index INT(11)"` + RelationId int64 `json:"relation_id" xorm:"default 0 comment('渠道id') index BIGINT(12)"` + Data string `json:"data" xorm:"comment('拓展用') TEXT"` +} diff --git a/app/db/model/duomai_mall_brand.go b/app/db/model/duomai_mall_brand.go new file mode 100644 index 0000000..3610f20 --- /dev/null +++ b/app/db/model/duomai_mall_brand.go @@ -0,0 +1,22 @@ +package model + +type DuomaiMallBrand struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + ThirdId int `json:"third_id" xorm:"not null default 0 comment('三方平台ID') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('品牌名称') VARCHAR(255)"` + SubName string `json:"sub_name" xorm:"not null default '' comment('品牌副标题') VARCHAR(255)"` + Logo string `json:"logo" xorm:"not null default '' comment('品牌logo') VARCHAR(255)"` + Url string `json:"url" xorm:"not null default '' comment('商城地址') VARCHAR(255)"` + CommRate string `json:"comm_rate" xorm:"not null default '' comment('返利比例') VARCHAR(255)"` + CateId int `json:"cate_id" xorm:"not null default 0 comment('分类ID') INT(11)"` + Desc string `json:"desc" xorm:"not null comment('商城简介') TEXT"` + Detail string `json:"detail" xorm:"not null comment('商城详情') TEXT"` + Data string `json:"data" xorm:"not null comment('商城详情') TEXT"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` + Tags string `json:"tags" xorm:"not null comment('标签,“,”号分割') TEXT"` + CommList string `json:"comm_list" xorm:"comment('分佣列表') TEXT"` +} diff --git a/app/db/model/duomai_mall_brand_cate.go b/app/db/model/duomai_mall_brand_cate.go new file mode 100644 index 0000000..ecab347 --- /dev/null +++ b/app/db/model/duomai_mall_brand_cate.go @@ -0,0 +1,11 @@ +package model + +type DuomaiMallBrandCate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('分类名称') VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + Sort int `json:"sort" xorm:"default 0 comment('排序') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` +} diff --git a/app/db/model/fin_sys_log.go b/app/db/model/fin_sys_log.go new file mode 100644 index 0000000..b64950d --- /dev/null +++ b/app/db/model/fin_sys_log.go @@ -0,0 +1,10 @@ +package model + +type FinSysLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + FromUid int `json:"from_uid" xorm:"not null default 0 comment('来自于用户') INT(11)"` + FromSource int `json:"from_source" xorm:"not null default 0 comment('来源') TINYINT(4)"` + Amount float32 `json:"amount" xorm:"not null default 0.0000 comment('金额') FLOAT(12,4)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(50)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('创建时间') INT(11)"` +} diff --git a/app/db/model/fin_user_commission_log.go b/app/db/model/fin_user_commission_log.go new file mode 100644 index 0000000..fe7137c --- /dev/null +++ b/app/db/model/fin_user_commission_log.go @@ -0,0 +1,9 @@ +package model + +type FinUserCommissionLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"not null INT(10)"` + Oid string `json:"oid" xorm:"not null default '' comment('订单ID') VARCHAR(50)"` + OrderType int `json:"order_type" xorm:"not null default 0 comment('0自购,1分享') TINYINT(1)"` + OrderProvider string `json:"order_provider" xorm:"not null default '' comment('订单供应商') VARCHAR(32)"` +} diff --git a/app/db/model/fin_user_flow.go b/app/db/model/fin_user_flow.go new file mode 100644 index 0000000..da8a443 --- /dev/null +++ b/app/db/model/fin_user_flow.go @@ -0,0 +1,29 @@ +package model + +import ( + "time" +) + +type FinUserFlow struct { + Id int64 `json:"id" xorm:"pk autoincr comment('流水编号') BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户id') INT(11)"` + Type int `json:"type" xorm:"not null default 0 comment('0收入,1支出') TINYINT(1)"` + Amount string `json:"amount" xorm:"not null default 0.0000 comment('变动金额') DECIMAL(11,4)"` + BeforeAmount string `json:"before_amount" xorm:"not null default 0.0000 comment('变动前金额') DECIMAL(11,4)"` + AfterAmount string `json:"after_amount" xorm:"not null default 0.0000 comment('变动后金额') DECIMAL(11,4)"` + SysFee string `json:"sys_fee" xorm:"not null default 0.0000 comment('手续费') DECIMAL(11,4)"` + PaymentType int `json:"payment_type" xorm:"not null default 1 comment('1支付宝,2微信.3手动转账') TINYINT(1)"` + OrdType string `json:"ord_type" xorm:"not null default '' comment('订单类型taobao,jd,pdd,vip,suning,kaola,own自营,withdraw提现') VARCHAR(20)"` + OrdId string `json:"ord_id" xorm:"not null default '' comment('对应订单编号') VARCHAR(50)"` + OrdTitle string `json:"ord_title" xorm:"not null default '' comment('订单标题') VARCHAR(50)"` + OrdAction int `json:"ord_action" xorm:"not null default 0 comment('10自购,11推广,12团队,20提现,21消费') TINYINT(2)"` + OrdTime int `json:"ord_time" xorm:"not null default 0 comment('下单时间or提现时间') INT(11)"` + OrdDetail string `json:"ord_detail" xorm:"not null default '' comment('记录商品ID或提现账号') VARCHAR(50)"` + ExpectedTime string `json:"expected_time" xorm:"not null default '0' comment('预期到账时间,字符串用于直接显示,结算后清除内容') VARCHAR(30)"` + State int `json:"state" xorm:"not null default 1 comment('1未到账,2已到账') TINYINT(1)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(2000)"` + OtherId int64 `json:"other_id" xorm:"not null default 0 comment('其他关联订单,具体根据订单类型判断') BIGINT(20)"` + AliOrdId string `json:"ali_ord_id" xorm:"default '' comment('支付宝订单号') VARCHAR(128)"` + CreateAt time.Time `json:"create_at" xorm:"created not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"updated not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/fin_user_log.go b/app/db/model/fin_user_log.go new file mode 100644 index 0000000..5f5a228 --- /dev/null +++ b/app/db/model/fin_user_log.go @@ -0,0 +1,19 @@ +package model + +type FinUserLog struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"not null comment('用户ID') INT(10)"` + Type int `json:"type" xorm:"not null default 0 comment('0余额,1积分') TINYINT(1)"` + FromType int `json:"from_type" xorm:"not null default 0 comment('来源类型:1,导购订单结算;2,拉新奖励;3,管理调整') TINYINT(3)"` + ValidBefore float32 `json:"valid_before" xorm:"not null default 0.0000 comment('之前可用余额') FLOAT(10,4)"` + ValidAfter float32 `json:"valid_after" xorm:"not null default 0.0000 comment('之后可用余额') FLOAT(10,4)"` + ValidAlter float32 `json:"valid_alter" xorm:"not null default 0.0000 comment('变更金额') FLOAT(10,4)"` + InvalidBefore float32 `json:"invalid_before" xorm:"not null default 0.0000 comment('之前冻结余额') FLOAT(10,4)"` + InvalidAfter float32 `json:"invalid_after" xorm:"not null default 0.0000 comment('之后冻结余额') FLOAT(10,4)"` + InvalidAlter float32 `json:"invalid_alter" xorm:"not null default 0.0000 comment('变更金额') FLOAT(10,4)"` + SysCommission float32 `json:"sys_commission" xorm:"not null default 0.000000 comment('平台抽取手续费') FLOAT(12,6)"` + Oid string `json:"oid" xorm:"not null default '' comment('关联订单ID') VARCHAR(50)"` + Action string `json:"action" xorm:"not null default '' comment('操作行为,如:withdraw提现') VARCHAR(20)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(80)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('创建时间') INT(10)"` +} diff --git a/app/db/model/fin_withdraw_apply.go b/app/db/model/fin_withdraw_apply.go new file mode 100644 index 0000000..be5c9e5 --- /dev/null +++ b/app/db/model/fin_withdraw_apply.go @@ -0,0 +1,20 @@ +package model + +import ( + "time" +) + +type FinWithdrawApply struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') index INT(10)"` + AdmId int `json:"adm_id" xorm:"not null default 0 comment('审核人ID,0为系统自动') INT(10)"` + Amount string `json:"amount" xorm:"not null default 0.00 comment('提现金额') DECIMAL(10,2)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注,失败请备注原因') VARCHAR(500)"` + Type int `json:"type" xorm:"not null default 1 comment('提现类型;1:手动;2:自动') TINYINT(1)"` + WithdrawAccount string `json:"withdraw_account" xorm:"not null default '' comment('提现账号') VARCHAR(64)"` + WithdrawName string `json:"withdraw_name" xorm:"not null default '' comment('提现人姓名') VARCHAR(12)"` + Reason int `json:"reason" xorm:"not null default 0 comment('审核失败(驳回理由);1:当前账号不满足提现规则;2:账号异常;3:资金异常') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('申请时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('处理时间') TIMESTAMP"` + State int `json:"state" xorm:"not null default 0 comment('0申请中,1通过,2完成,3失败') TINYINT(1)"` +} diff --git a/app/db/model/guide_goods.go b/app/db/model/guide_goods.go new file mode 100644 index 0000000..6249463 --- /dev/null +++ b/app/db/model/guide_goods.go @@ -0,0 +1,67 @@ +package model + +import "time" + +//产品发布表 +type GuideGoods struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + GoodsUrl string `json:"goodsUrl" xorm:"not null comment('传进来的商品链接') VARCHAR(50)"` + ItemUrl string `json:"itemUrl" xorm:"not null default 0 comment('搜索出来的商品链接') VARCHAR(50)"` + GoodsId string `json:"goodsId" xorm:"not null default 0 comment('商品id') VARCHAR(50)"` + Provider string `json:"provider" xorm:"not null default 0 comment('渠道key') VARCHAR(50)"` + ProviderName string `json:"providerName" xorm:"not null default 0.0000 comment('渠道') VARCHAR(50)"` + ProviderImg string `json:"providerImg" xorm:"not null default 0 comment('渠道图片') VARCHAR(50)"` + ProviderImgUrl string `json:"providerImgUrl" xorm:"not null default 0 comment('渠道图片Url') VARCHAR(50)"` + GoodsTitle string `json:"goodsTitle" xorm:"not null default 0.0000 comment('商品标题') VARCHAR(50)"` + GoodsPrice string `json:"goodsPrice" xorm:"not null default 1 comment('商品原价') VARCHAR(50)"` + WlGoodsPrice string `json:"wlGoodsPrice" xorm:"not null default 1 comment('商品卷后价') VARCHAR(50)"` + CouponPrice string `json:"couponPrice" xorm:"not null default 1 comment('优惠劵金额') VARCHAR(50)"` + CouponUrl string `json:"couponUrl" xorm:"not null default 1 comment('优惠劵Url') VARCHAR(50)"` + Category string `json:"category" xorm:"not null default 1 comment('分类') VARCHAR(50)"` + CategoryName string `json:"categoryName" xorm:"not null default 1 comment('分类名') VARCHAR(50)"` + InOrderCount string `json:"inOrderCount" xorm:"not null default 1 comment('销量') VARCHAR(50)"` + GoodsDesc string `json:"goodsDesc" xorm:"not null default 0.0000 comment('商品描述') VARCHAR(50)"` + GoodsContent string `json:"goodsContent" xorm:"not null default 0.0000 comment('商品文案(以json存)') VARCHAR(50)"` + GoodsImg string `json:"goodsImg" xorm:"not null default 0.0000 comment('商品主图') VARCHAR(50)"` + GoodsImgUrl string `json:"goodsImgUrl" xorm:"not null default 0.0000 comment('商品主图URL') VARCHAR(50)"` + GoodsImgList string `json:"goodsImgList" xorm:"not null default 0.0000 comment('商品图片组(以json存)') VARCHAR(50)"` + VideoUrl string `json:"videoUrl" xorm:"not null default 0.0000 comment('商品视频URL') VARCHAR(50)"` + IsPutOn string `json:"isPutOn" xorm:"not null default 0 comment('是否上架;0:否;1:是') VARCHAR(50)"` + PutOnAt time.Time `json:"putOnAt"xorm:"not null default 0 comment('创建时间') datetime"` + PutDownAt time.Time `json:"putDownAt"xorm:"not null default 0 comment('创建时间') datetime"` + CreateAt time.Time `xorm:"not null default 0 comment('创建时间') datetime"` + UpdateAt time.Time `xorm:"not null default 0 comment('更新时间') datetime"` +} + +//产品发布app端数据 +type GuideGoodsApp struct { + Id string `json:"id"` + GoodsUrl string `json:"goods_url"` + ItemURL string `json:"item_url"` + GoodsId string `json:"goods_id"` + Provider string `json:"provider"` + ProviderName string `json:"provider_name"` + ProviderImg string `json:"provider_img"` + ProviderImgUrl string `json:"provider_img_url"` + GoodsTitle string `json:"goods_title"` + GoodsPrice string `json:"goods_price"` + WlGoodsPrice string `json:"wl_goods_price"` + CouponPrice string `json:"coupon_price"` + CouponURL string `json:"coupon_url"` + Category string `json:"category"` + CategoryName string `json:"category_name"` + InOrderCount string `json:"in_order_count"` + GoodsDesc string `json:"goods_desc"` + GoodsContent string `json:"goods_content"` + GoodsImg string `json:"goods_img"` + GoodsImgUrl string `json:"goods_img_url"` + GoodsImgList string `json:"goods_img_list"` + VideoUrl string `json:"video_url"` + StateInfo string `json:"state_info"` + IsPutOn string `json:"is_put_on"` + PutOnAt string `json:"put_on_at"` + PutDownAt string `json:"put_down_at"` + Commission string `json:"commission"` //反的价钱 + ShareValue string `json:"share_value"` //分享赚 + SlefBuy string `json:"slef_buy"` //自购赚 +} diff --git a/app/db/model/incentive_ad.go b/app/db/model/incentive_ad.go new file mode 100644 index 0000000..2b0dd08 --- /dev/null +++ b/app/db/model/incentive_ad.go @@ -0,0 +1,23 @@ +package model + +import "time" + +//激励广告表 +type IncentiveAd struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + AdName string `json:"ad_name" xorm:"not null comment('广告名称(唯一)') VARCHAR(50)"` + SdkType string `json:"sdk_type" xorm:"not null default 0 comment('平台:1,优量汇;2,快手联盟;3,穿山甲') VARCHAR(50)"` + AndroidMediaId string `json:"android_media_id" xorm:"not null default 0 comment('安卓媒体id') VARCHAR(50)"` + IosMediaId string `json:"ios_media_id" xorm:"not null default 0.0000 comment('ios媒体id') VARCHAR(50)"` + AndroidAdId string `json:"android_ad_id" xorm:"not null default 0.0000 comment('安卓广告id') VARCHAR(50)"` + IosAdId string `json:"ios_ad_id" xorm:"not null default 0.0000 comment('ios广告id') VARCHAR(50)"` + Conditions int `json:"conditions" xorm:"not null default 1 comment('是否开启条件限制(0否,1是)') TINYINT(1)"` + Autoplay int `json:"autoplay" xorm:"not null default 1 comment('是否自动播放(0否,1是)') TINYINT(1)"` + AutoClickAd int `json:"auto_click_ad" xorm:"not null default 1 comment('是否自动点击广告(0否,1是)') TINYINT(1)"` + LevelLimitId int `json:"level_limit_id" xorm:"comment('等级id') INT(11)"` + LevelLimitName string `json:"level_limit_name" xorm:"comment('等级名称') VARCHAR(50)"` + LevelWeight int `json:"level_weight" xorm:" comment('等级权重') INT(11)"` + CreateAt time.Time `xorm:"not null default 0 comment('创建时间') datetime"` + UpdateAt time.Time `xorm:"not null default 0 comment('更新时间') datetime"` + VisitCount int `json:"visit_count" xorm:" default 0 comment('可观看次数') INT(11)"` +} diff --git a/app/db/model/incentive_ad_total.go b/app/db/model/incentive_ad_total.go new file mode 100644 index 0000000..43f463c --- /dev/null +++ b/app/db/model/incentive_ad_total.go @@ -0,0 +1,9 @@ +package model + +type IncentiveAdTotal struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + AdId int `json:"ad_id" xorm:"default 0 comment('广告id') INT(11)"` + Count int `json:"count" xorm:"default 0 comment('观看数量') INT(11)"` + Time int `json:"time" xorm:"default 0 comment('时间') INT(11)"` + Uid int `json:"uid" xorm:"default 0 comment('用户id') INT(11)"` +} diff --git a/app/db/model/logistic_company.go b/app/db/model/logistic_company.go new file mode 100644 index 0000000..1d27dd2 --- /dev/null +++ b/app/db/model/logistic_company.go @@ -0,0 +1,6 @@ +package model + +type LogisticCompany struct { + Name string `json:"name" xorm:"comment('快递公司名称') VARCHAR(255)"` + Code string `json:"code" xorm:"comment('快递公司代号') VARCHAR(255)"` +} diff --git a/app/db/model/mall_ord.go b/app/db/model/mall_ord.go new file mode 100644 index 0000000..5479472 --- /dev/null +++ b/app/db/model/mall_ord.go @@ -0,0 +1,52 @@ +package model + +import ( + "time" +) + +type MallOrd struct { + OrdId int64 `json:"ord_id" xorm:"not null pk BIGINT(20)"` + MainOrdId int64 `json:"main_ord_id" xorm:"not null comment('主订单号') index BIGINT(20)"` + Uid int `json:"uid" xorm:"comment('用户id') index INT(11)"` + BuyerName string `json:"buyer_name" xorm:"comment('购买人') VARCHAR(255)"` + BuyerPhone string `json:"buyer_phone" xorm:"comment('购买人手机号') VARCHAR(255)"` + CostPrice string `json:"cost_price" xorm:"comment('价格') DECIMAL(12,2)"` + CostVirtualCoin string `json:"cost_virtual_coin" xorm:"comment('消耗的虚拟币') DECIMAL(12,2)"` + VirtualCoinId int `json:"virtual_coin_id" xorm:"comment('使用的虚拟币id') INT(11)"` + State int `json:"state" xorm:"comment('订单状态:0未支付 1已支付 2已发货 3已完成 4售后中 5部分售后中 6关闭') TINYINT(1)"` + PayTime time.Time `json:"pay_time" xorm:"comment('支付时间') DATETIME"` + PayChannel int `json:"pay_channel" xorm:"not null comment('支付方式:1balance 2alipay 3wx_pay') TINYINT(1)"` + ShippingTime time.Time `json:"shipping_time" xorm:"comment('发货时间') DATETIME"` + LogisticCompany string `json:"logistic_company" xorm:"not null default '' comment('物流公司') VARCHAR(255)"` + LogisticNum string `json:"logistic_num" xorm:"not null default '' comment('物流单号') VARCHAR(255)"` + ReceiverPhone string `json:"receiver_phone" xorm:"not null default '' comment('收货人手机号') VARCHAR(20)"` + ReceiverName string `json:"receiver_name" xorm:"not null default '' comment('收货人名字') VARCHAR(255)"` + ReceiverAddressDetail string `json:"receiver_address_detail" xorm:"not null default '' comment('收货人地址') VARCHAR(255)"` + ShippingType int `json:"shipping_type" xorm:"not null default 1 comment('运送方式:1快递送货') TINYINT(1)"` + CouponDiscount string `json:"coupon_discount" xorm:"not null default 0.00 comment('优惠券折扣额') DECIMAL(12,2)"` + DiscountPrice string `json:"discount_price" xorm:"not null default 0.00 comment('立减') DECIMAL(12,2)"` + UserCouponId int64 `json:"user_coupon_id" xorm:"comment('使用的优惠券id') BIGINT(20)"` + ReturnInsuranceFee string `json:"return_insurance_fee" xorm:"not null default 0.00 comment('退货无忧费用') DECIMAL(12,2)"` + IsReceipt int `json:"is_receipt" xorm:"not null default 0 comment('是否开具发票 0否 1是') TINYINT(255)"` + ShippingFee string `json:"shipping_fee" xorm:"not null default 0.00 comment('运费') DECIMAL(12,2)"` + Comment string `json:"comment" xorm:"not null comment('备注') VARCHAR(2048)"` + ProvinceName string `json:"province_name" xorm:"not null default '' comment('收货省份') VARCHAR(255)"` + CityName string `json:"city_name" xorm:"not null default '' comment('收货城市') VARCHAR(255)"` + CountyName string `json:"county_name" xorm:"not null default '' comment('收货区域') VARCHAR(255)"` + PayNum string `json:"pay_num" xorm:"not null default '' comment('交易流水') VARCHAR(255)"` + ConfirmTime time.Time `json:"confirm_time" xorm:"comment('确认时间') DATETIME"` + EstimateIntegral string `json:"estimate_integral" xorm:"not null default 0.0000 comment('预计积分') DECIMAL(12,4)"` + EstimateCommission string `json:"estimate_commission" xorm:"not null default 0.0000 comment('预计佣金') DECIMAL(12,4)"` + CreateTime time.Time `json:"create_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + DeletedTime time.Time `json:"deleted_time" xorm:"comment('删除时间') DATETIME"` + FinishTime time.Time `json:"finish_time" xorm:"comment('完成时间') DATETIME"` + OrderType int `json:"order_type" xorm:"not null default 1 comment('订单类型:1普通订单 2拼团订单') TINYINT(3)"` + Data string `json:"data" xorm:"comment('订单相关的数据') TEXT"` + GroupBuyCommission string `json:"group_buy_commission" xorm:"default 0.0000 comment('团购未中奖佣金') DECIMAL(12,4)"` + GroupBuySettleTime time.Time `json:"group_buy_settle_time" xorm:"comment('拼团结算时间') DATETIME"` + SettleTime time.Time `json:"settle_time" xorm:"comment('结算时间') DATETIME"` + GroupBuyCommissionTime time.Time `json:"group_buy_commission_time" xorm:"comment('拼团分佣时间') DATETIME"` + CommissionTime time.Time `json:"commission_time" xorm:"comment('分佣时间') DATETIME"` + ShareUid int `json:"share_uid" xorm:"comment('分享人') INT(11)"` +} diff --git a/app/db/model/moments_cate.go b/app/db/model/moments_cate.go new file mode 100644 index 0000000..c35ec24 --- /dev/null +++ b/app/db/model/moments_cate.go @@ -0,0 +1,14 @@ +package model + +type MomentsCate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pid int `json:"pid" xorm:"not null default 0 comment('上级,0表示一级分类') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('分类名称') VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + TypeId int `json:"type_id" xorm:"not null default 0 comment('类型') TINYINT(10)"` + Data string `json:"data" xorm:"not null default '' comment('保存不同类型定义的设置数据') VARCHAR(255)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` + Child *[]MomentsCate `json:"child"` +} diff --git a/app/db/model/moments_material.go b/app/db/model/moments_material.go new file mode 100644 index 0000000..ff5fb26 --- /dev/null +++ b/app/db/model/moments_material.go @@ -0,0 +1,13 @@ +package model + +type MomentsMaterial struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pid int `json:"pid" xorm:"not null default 0 comment('分类ID') INT(11)"` + CateId int `json:"cate_id" xorm:"not null default 0 comment('子分类') INT(11)"` + TypeId int `json:"type_id" xorm:"not null default 0 comment('类型ID') INT(11)"` + Data string `json:"data" xorm:"not null comment('素材详情json') TEXT"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示:0不显示;1显示') TINYINT(1)"` + CreatedAt int `json:"created_at" xorm:"comment('创建时间') INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"comment('更新时间') INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` +} diff --git a/app/db/model/newcomers_free_price_type.go b/app/db/model/newcomers_free_price_type.go new file mode 100644 index 0000000..c2d54ff --- /dev/null +++ b/app/db/model/newcomers_free_price_type.go @@ -0,0 +1,15 @@ +package model + +type NewcomersFreePriceType struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + PriceName string `json:"price_name" xorm:"not null comment('价格类型') VARCHAR(255)"` + NeedQuan int `json:"need_quan" xorm:"not null default 0 comment('需要的福利券') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否开启') TINYINT(1)"` + IsDel int `json:"is_del" xorm:"not null default 0 INT(11)"` + NeedUseQuan int `json:"need_use_quan" xorm:"not null default 1 INT(1)"` + NeedLimitBuy int `json:"need_limit_buy" xorm:"not null default 0 INT(1)"` + Auth string `json:"auth" xorm:"not null comment('权限') TEXT"` + LimitBuyCondition string `json:"limit_buy_condition" xorm:"not null comment('限购条件') TEXT"` +} diff --git a/app/db/model/newcomers_free_product.go b/app/db/model/newcomers_free_product.go new file mode 100644 index 0000000..a7d8803 --- /dev/null +++ b/app/db/model/newcomers_free_product.go @@ -0,0 +1,31 @@ +package model + +import ( + "time" +) + +type NewcomersFreeProduct struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + GoodId string `json:"good_id" xorm:"not null default '' comment('平台商品ID') VARCHAR(255)"` + Source string `json:"source" xorm:"not null default 'taobao' comment('来源平台') VARCHAR(255)"` + SourceUrl string `json:"source_url" xorm:"not null default '' comment('用户输入地址') VARCHAR(255)"` + PriceType int `json:"price_type" xorm:"not null default 0 comment('所属价格类型') TINYINT(1)"` + OriginalPrice string `json:"original_price" xorm:"not null default 0.00 comment('原价') DECIMAL(10,2)"` + CouponPrice string `json:"coupon_price" xorm:"not null default 0.00 comment('券后价格') DECIMAL(10,2)"` + ReturnMoney string `json:"return_money" xorm:"not null default 0.00 comment('返还的钱') DECIMAL(10,2)"` + Money string `json:"money" xorm:"not null default 0 comment('实付金额') DECIMAL(10)"` + Stock int `json:"stock" xorm:"not null default 0 comment('库存数量') INT(11)"` + Sale int `json:"sale" xorm:"not null default 0 comment('卖掉的数量') INT(11)"` + EndTime time.Time `json:"end_time" xorm:"not null comment('结束时间') DATETIME"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否上架') TINYINT(1)"` + IsDel int `json:"is_del" xorm:"not null default 0 comment('是否删除') TINYINT(1)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(255)"` + StartTime time.Time `json:"start_time" xorm:"not null comment('开始时间') DATETIME"` + Pictures string `json:"pictures" xorm:"not null default '' comment('图片地址') VARCHAR(255)"` + CouponUrl string `json:"coupon_url" xorm:"not null default '' comment('优惠券链接') VARCHAR(255)"` + Amount int `json:"amount" xorm:"default 0 comment('总数') INT(11)"` + ReturnType int `json:"return_type" xorm:"default 0 comment('0平台补贴 1 淘礼金补贴') INT(1)"` + OwnbuyReturnType int `json:"ownbuy_return_type" xorm:"default 0 comment('自购补贴:1开启、0关闭') INT(1)"` +} diff --git a/app/db/model/newcomers_invite_record.go b/app/db/model/newcomers_invite_record.go new file mode 100644 index 0000000..b5e129f --- /dev/null +++ b/app/db/model/newcomers_invite_record.go @@ -0,0 +1,8 @@ +package model + +type NewcomersInviteRecord struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + FromUid int `json:"from_uid" xorm:"not null default 0 comment('邀请人uid') INT(11)"` + ToUid int `json:"to_uid" xorm:"not null default 0 comment('被邀请人uid') INT(11)"` + QualificationRecord int `json:"qualification_record" xorm:"not null default 0 comment('获得资格记录表ID') INT(11)"` +} diff --git a/app/db/model/newcomers_qualification.go b/app/db/model/newcomers_qualification.go new file mode 100644 index 0000000..6159686 --- /dev/null +++ b/app/db/model/newcomers_qualification.go @@ -0,0 +1,10 @@ +package model + +type NewcomersQualification struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') INT(10)"` + RemainTimes int `json:"remain_times" xorm:"not null default 0 comment('剩余次数') INT(11)"` + CumulativeTimes int `json:"cumulative_times" xorm:"not null default 0 comment('累计次数') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` +} diff --git a/app/db/model/newcomers_qualification_record.go b/app/db/model/newcomers_qualification_record.go new file mode 100644 index 0000000..451d4e1 --- /dev/null +++ b/app/db/model/newcomers_qualification_record.go @@ -0,0 +1,21 @@ +package model + +import ( + "time" +) + +type NewcomersQualificationRecord struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Uid int `json:"uid" xorm:"not null default 0 INT(11)"` + Source int `json:"source" xorm:"not null default 0 comment('1为注册获得 +2为分享获得 +3为消费扣除 +4为后台修改 +来源标识') TINYINT(4)"` + SourceText string `json:"source_text" xorm:"not null default '' comment('来源') VARCHAR(255)"` + ChangeNum int `json:"change_num" xorm:"not null default 0 comment('变更值') INT(11)"` + AfterChangeNum int `json:"after_change_num" xorm:"not null default 0 comment('变更后值') INT(11)"` + OrderId int64 `json:"order_id" xorm:"not null default 0 comment('新人免单订单ID(与order_list主键对应)') BIGINT(20)"` + CreatedAt time.Time `json:"created_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdatedAt time.Time `json:"updated_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/ord_item_info.go b/app/db/model/ord_item_info.go new file mode 100644 index 0000000..45144bb --- /dev/null +++ b/app/db/model/ord_item_info.go @@ -0,0 +1,10 @@ +package model + +type OrdItemInfo struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + ItemId string `json:"item_id" xorm:"not null default '' comment('商品id') index VARCHAR(100)"` + Pvd string `json:"pvd" xorm:"not null default '' comment('供应商') VARCHAR(32)"` + Thumbnail string `json:"thumbnail" xorm:"not null default '' comment('缩略图URL') VARCHAR(2000)"` + ItemTitle string `json:"item_title" xorm:"not null default '' comment('标题') VARCHAR(256)"` + ItemLink string `json:"item_link" xorm:"not null default '' comment('商品链接') VARCHAR(2000)"` +} diff --git a/app/db/model/ord_list.go b/app/db/model/ord_list.go new file mode 100644 index 0000000..9631437 --- /dev/null +++ b/app/db/model/ord_list.go @@ -0,0 +1,42 @@ +package model + +type OrdList struct { + OrdId int64 `xorm:"pk autoincr BIGINT(20)" json:"ord_id"` + Uid int `xorm:"not null index INT(10)" json:"uid"` + PvdOid string `xorm:"not null index(IDX_PVD) VARCHAR(50)" json:"pvd_oid"` + ParentOrdId int64 `xorm:" BIGINT(20)" json:"parent_ord_id"` + Pvd string `xorm:"not null default '' index(IDX_PVD) index(IDX_PVD_ITEM) VARCHAR(8)" json:"pvd"` + ItemId string `xorm:"not null default '' index(IDX_PVD_ITEM) VARCHAR(50)" json:"item_id"` + ItemNum int `xorm:"not null default 1 TINYINT(3)" json:"item_num"` + ItemPrice float64 `xorm:"not null default 0.00 FLOAT(10,2)" json:"item_price"` + ItemCommissionRate float64 `xorm:"not null default 0.00 FLOAT(6,4)" json:"item_commission_rate"` + PaidPrice float64 `xorm:"not null default 0.00 FLOAT(10,2)" json:"paid_price"` + OrderType int `xorm:"not null default 0 TINYINT(1)" json:"order_type"` + PriceType int `xorm:"not null default 0 INT(1)" json:"price_type"` + OrderCompare int `xorm:"not null default 0 TINYINT(1)" json:"order_compare"` + SubsidyFee float64 `xorm:"not null default 0.00 FLOAT(8,2)" json:"subsidy_fee"` + SubsidyRate float64 `xorm:"not null default 0.0000 FLOAT(10,4)" json:"subsidy_rate"` + UserCommission float64 `xorm:"not null default 0.000 FLOAT(8,3)" json:"user_commission"` + UserCommissionRate float64 `xorm:"not null default 0.0000 FLOAT(6,4)" json:"user_commission_rate"` + PvdCommission float64 `xorm:"not null default 0.0000 FLOAT(8,4)" json:"pvd_commission"` + PvdCommissionRate float64 `xorm:"not null default 0.0000 FLOAT(6,4)" json:"pvd_commission_rate"` + SysCommission float64 `xorm:"not null default 0.0000 FLOAT(8,4)" json:"sys_commission"` + SysCommissionRate float64 `xorm:"not null default 0.0000 FLOAT(6,4)" json:"sys_commission_rate"` + PlanCommissionId int `xorm:"not null default 0 INT(10)" json:"plan_commission_id"` + PlanCommissionState int `xorm:"not null default 0 TINYINT(1)" json:"plan_commission_state"` + Reason string `xorm:"not null default '' VARCHAR(32)" json:"reason"` + State int `xorm:"not null default 0 TINYINT(1)" json:"state"` + LockState int `xorm:"not null default 0 TINYINT(1)" json:"lock_state"` + CreateAt int `xorm:"not null default 0 INT(10)" json:"create_at"` + UpdateAt int `xorm:"not null default 0 INT(11)" json:"update_at"` + ConfirmAt int `xorm:"not null default 0 INT(10)" json:"confirm_at"` + PvdSettleAt int `xorm:"not null default 0 INT(10)" json:"pvd_settle_at"` + SettleAt int `xorm:"not null default 0 INT(10)" json:"settle_at"` + SubsidyAt int `xorm:"not null default 0 INT(10)" json:"subsidy_at"` + BenefitList string `xorm:"not null default '' index VARCHAR(200)" json:"benefit_list"` + BenefitAll float64 `xorm:"not null default 0.00 FLOAT(8,2)" json:"benefit_all"` + Data string `xorm:"not null default '' VARCHAR(2000)" json:"data"` + UpdateFrom int `xorm:"not null default 0 TINYINT(1)" json:"update_from"` + CreateFrom int `xorm:"not null default 0 TINYINT(1)" json:"create_from"` + PvdPid string `xorm:"not null default '' index VARCHAR(100)" json:"pvd_pid"` +} diff --git a/app/db/model/ord_list_his.go b/app/db/model/ord_list_his.go new file mode 100644 index 0000000..397ee7c --- /dev/null +++ b/app/db/model/ord_list_his.go @@ -0,0 +1,29 @@ +package model + +type OrdListHis struct { + OrdId int64 `json:"ord_id" xorm:"not null pk autoincr comment('本系统订单ID') BIGINT(20)"` + Uid int `json:"uid" xorm:"not null comment('用户id') index INT(11)"` + ProviderOid string `json:"provider_oid" xorm:"not null comment('供应商订单订单号') VARCHAR(50)"` + SkuId string `json:"sku_id" xorm:"not null default '' comment('商品SKU') VARCHAR(50)"` + ItemId string `json:"item_id" xorm:"not null comment('商品ID') VARCHAR(50)"` + ItemPrice string `json:"item_price" xorm:"not null default 0.00 comment('商品单价') DECIMAL(10,2)"` + Provider string `json:"provider" xorm:"not null default '' comment('供应商taobao,jd,pdd,vip,suning,kaola') VARCHAR(16)"` + PaidPrice string `json:"paid_price" xorm:"not null default 0.00 comment('付款金额') DECIMAL(10,2)"` + OrderType int `json:"order_type" xorm:"not null default 0 comment('0自购,1分享订单,粉丝订单') TINYINT(1)"` + BuyerId int `json:"buyer_id" xorm:"not null default 0 comment('0分享订单,其它自购或粉丝') INT(11)"` + UpdateFrom int `json:"update_from" xorm:"not null default 0 comment('订单更新来源,0.API,1.导入,2.后台操作') TINYINT(1)"` + UserCommission string `json:"user_commission" xorm:"not null default 0.000 comment('用户佣金') DECIMAL(10,3)"` + UserCommissionRate string `json:"user_commission_rate" xorm:"not null default 0.0000 comment('用户佣金比例,如10.05%就是0.1005') DECIMAL(6,4)"` + ProviderAllCommission string `json:"provider_all_commission" xorm:"not null default 0.000 comment('联盟返佣总额') DECIMAL(10,3)"` + ProviderAllCommissionRate string `json:"provider_all_commission_rate" xorm:"not null default 0.0000 comment('联盟返佣比例') DECIMAL(6,4)"` + PlanCommissionId int `json:"plan_commission_id" xorm:"not null default 0 comment('分佣方案') INT(10)"` + PlanSettleId int `json:"plan_settle_id" xorm:"not null default 0 comment('结算方案') INT(10)"` + SettleState int `json:"settle_state" xorm:"not null default 0 comment('用户结算状态,0未结算,1已结算') TINYINT(1)"` + Reason string `json:"reason" xorm:"not null default '' comment('失效原因') VARCHAR(32)"` + State int `json:"state" xorm:"not null default 0 comment('0已付款,1已收货,2成功,3失效') TINYINT(1)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('创建时间') INT(10)"` + UpdateAt int `json:"update_at" xorm:"not null default 0 comment('更新时间') INT(10)"` + ConfirmAt int `json:"confirm_at" xorm:"not null default 0 comment('确认收货时间') INT(10)"` + ProviderSettleAt int `json:"provider_settle_at" xorm:"not null default 0 comment('供应商结算时间') INT(10)"` + SettleAt int `json:"settle_at" xorm:"not null default 0 comment('用户分佣结算时间') INT(10)"` +} diff --git a/app/db/model/ord_list_relate.go b/app/db/model/ord_list_relate.go new file mode 100644 index 0000000..6142177 --- /dev/null +++ b/app/db/model/ord_list_relate.go @@ -0,0 +1,11 @@ +package model + +type OrdListRelate struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Oid int64 `json:"oid" xorm:"not null default 0 comment('订单号') index unique(IDX_ORD) BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') unique(IDX_ORD) index INT(10)"` + Amount float64 `json:"amount" xorm:"not null default 0.00 comment('金额') FLOAT(10,2)"` + Pvd string `json:"pvd" xorm:"not null default '' comment('供应商taobao,jd,pdd,vip,suning,kaola') index VARCHAR(8)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('订单创建时间') index INT(10)"` + Level int `json:"level" xorm:"not null default 0 comment('0自购 1直推 大于1:间推') INT(10)"` +} diff --git a/app/db/model/plan_commission.go b/app/db/model/plan_commission.go new file mode 100644 index 0000000..ded1ebb --- /dev/null +++ b/app/db/model/plan_commission.go @@ -0,0 +1,12 @@ +package model + +import "time" + +type PlanCommission struct { + Id int `json:"id" xorm:"not null pk autoincr comment('分佣方案ID,现在只允许1,2') INT(10)"` + PlanName string `json:"plan_name" xorm:"not null default '' comment('方案名称') VARCHAR(64)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(256)"` + Mode string `json:"mode" xorm:"not null default '' comment('模式,lv_all级差按总佣金,lv_self级差按自购') VARCHAR(16)"` + Data string `json:"data" xorm:"not null default '' comment('里面包含等级分配方案数据') VARCHAR(3000)"` + UpdateAt time.Time `json:"update_at" xorm:"default CURRENT_TIMESTAMP TIMESTAMP"` +} diff --git a/app/db/model/plan_reward.go b/app/db/model/plan_reward.go new file mode 100644 index 0000000..94d87cb --- /dev/null +++ b/app/db/model/plan_reward.go @@ -0,0 +1,15 @@ +package model + +type PlanReward struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Pvd string `json:"pvd" xorm:"not null comment('供应商') VARCHAR(16)"` + PvdRate float32 `json:"pvd_rate" xorm:"not null default 0.0000 comment('供应商抽成比例') FLOAT(6,4)"` + SysRate float32 `json:"sys_rate" xorm:"not null default 0.0000 comment('平台抽成比例') FLOAT(6,4)"` + SettleMode int `json:"settle_mode" xorm:"not null default 1 comment('0.手动方案,1.自动方案') TINYINT(1)"` + PlanCommissionId int `json:"plan_commission_id" xorm:"not null default 0 comment('佣金方案0未设置,>0对应方案') TINYINT(3)"` + PlanSettleId int `json:"plan_settle_id" xorm:"not null default 0 comment('结算方案0未设置,>0对应方案') TINYINT(3)"` + State int `json:"state" xorm:"not null default 1 comment('0关闭,1开启') TINYINT(1)"` + Source int `json:"source" xorm:"not null default 1 comment('佣金来源:1联盟佣金 2补贴金额') TINYINT(1)"` + RegionRate float32 `json:"region_rate" xorm:"not null default 0.0000 comment('区域代理抽成比例') FLOAT(6,4)"` + GlobalRate float32 `json:"global_rate" xorm:"not null default 0.0000 comment('全球分红抽成比例') FLOAT(6,4)"` +} diff --git a/app/db/model/plan_settle.go b/app/db/model/plan_settle.go new file mode 100644 index 0000000..d8f5f61 --- /dev/null +++ b/app/db/model/plan_settle.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type PlanSettle struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + PlanName string `json:"plan_name" xorm:"not null default '' comment('方案名称') VARCHAR(128)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(512)"` + SettleMode int `json:"settle_mode" xorm:"not null default 0 comment('0组合条件,1固定条件') TINYINT(1)"` + SettleCondition string `json:"settle_condition" xorm:"comment('当组合时候存json,固定存数字(组合条件时settleTime为0时是立即结算)') TEXT"` + UpdateTime time.Time `json:"update_time" xorm:"default CURRENT_TIMESTAMP TIMESTAMP"` +} diff --git a/app/db/model/plan_withdraw.go b/app/db/model/plan_withdraw.go new file mode 100644 index 0000000..23083c8 --- /dev/null +++ b/app/db/model/plan_withdraw.go @@ -0,0 +1,8 @@ +package model + +type PlanWithdraw struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + PlanName string `json:"plan_name" xorm:"not null default '' comment('方案名称') VARCHAR(64)"` + Memo string `json:"memo" xorm:"not null default '' comment('方案描述') VARCHAR(512)"` + State int `json:"state" xorm:"not null default 1 comment('0关闭,1开启') TINYINT(1)"` +} diff --git a/app/db/model/privilege_card_brand.go b/app/db/model/privilege_card_brand.go new file mode 100644 index 0000000..28ba842 --- /dev/null +++ b/app/db/model/privilege_card_brand.go @@ -0,0 +1,21 @@ +package model + +type PrivilegeCardBrand struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + ZhimengId int `json:"zhimeng_id" xorm:"not null default 0 comment('智盟ID') INT(11)"` + ThirdId int `json:"third_id" xorm:"not null default 0 comment('三方平台ID') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('品牌名称') VARCHAR(255)"` + SubName string `json:"sub_name" xorm:"not null default '' comment('品牌副标题') VARCHAR(255)"` + Logo string `json:"logo" xorm:"not null default '' comment('品牌logo') VARCHAR(255)"` + CateId int `json:"cate_id" xorm:"not null default 0 comment('分类ID') INT(11)"` + Type string `json:"type" xorm:"not null default '' comment('类型') VARCHAR(255)"` + TypeId string `json:"type_id" xorm:"not null default '' comment('类型id') VARCHAR(255)"` + AccountType string `json:"account_type" xorm:"comment('账号类型') VARCHAR(255)"` + Remark string `json:"remark" xorm:"not null comment('温馨提示') TEXT"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + Sort int `json:"sort" xorm:"default 0 comment('排序') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` + Skip string `json:"skip" xorm:"comment('跳转信息') TEXT"` +} diff --git a/app/db/model/privilege_card_brand_cate.go b/app/db/model/privilege_card_brand_cate.go new file mode 100644 index 0000000..f3c58f6 --- /dev/null +++ b/app/db/model/privilege_card_brand_cate.go @@ -0,0 +1,12 @@ +package model + +type PrivilegeCardBrandCate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('分类名称') VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + Sort int `json:"sort" xorm:"default 0 comment('排序') INT(11)"` + Amount int `json:"amount" xorm:"default 0 comment('品牌数量') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` +} diff --git a/app/db/model/privilege_card_goods.go b/app/db/model/privilege_card_goods.go new file mode 100644 index 0000000..95ecb4b --- /dev/null +++ b/app/db/model/privilege_card_goods.go @@ -0,0 +1,24 @@ +package model + +type PrivilegeCardGoods struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + ZhimengId int `json:"zhimeng_id" xorm:"not null default 0 comment('智盟ID') INT(11)"` + ThirdId int `json:"third_id" xorm:"not null default 0 comment('三方平台ID') INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('商品名称') VARCHAR(255)"` + GoodsImg string `json:"goods_img" xorm:"not null default '' comment('商品图片') VARCHAR(255)"` + BrandId int `json:"brand_id" xorm:"not null default 0 comment('品牌ID') INT(11)"` + Times string `json:"times" xorm:"not null default '' comment('时长') VARCHAR(255)"` + CateId int `json:"cate_id" xorm:"not null default 0 comment('商品分类ID') INT(11)"` + MarkupMode int `json:"markup_mode" xorm:"not null default 0 comment('加价模式 0为利润空间1为进货价') TINYINT(1)"` + MarkupRate int `json:"markup_rate" xorm:"not null default 0 comment('加价比例') INT(3)"` + OfficialPrice string `json:"official_price" xorm:"not null default 0.00 comment('原价') DECIMAL(10,2)"` + PlatformPrice string `json:"platform_price" xorm:"not null default 0.00 comment('进货价') DECIMAL(10,2)"` + FinalPrice string `json:"final_price" xorm:"not null default 0.00 comment('销售价') DECIMAL(10,2)"` + Type string `json:"type" xorm:"not null default '' comment('类型') VARCHAR(255)"` + TypeId string `json:"type_id" xorm:"not null default '' comment('类型id') VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + Sort int `json:"sort" xorm:"default 0 comment('排序') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` +} diff --git a/app/db/model/privilege_card_goods_cate.go b/app/db/model/privilege_card_goods_cate.go new file mode 100644 index 0000000..487489c --- /dev/null +++ b/app/db/model/privilege_card_goods_cate.go @@ -0,0 +1,13 @@ +package model + +type PrivilegeCardGoodsCate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('商品分类名称') VARCHAR(255)"` + IsShow int `json:"is_show" xorm:"not null default 1 comment('是否显示') TINYINT(1)"` + Sort int `json:"sort" xorm:"default 0 comment('排序') INT(11)"` + BrandId int `json:"brand_id" xorm:"not null default 0 comment('从属品牌ID') INT(11)"` + Amount int `json:"amount" xorm:"default 0 comment('商品数量') INT(11)"` + CreatedAt int `json:"created_at" xorm:"not null default 0 INT(11)"` + UpdatedAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 INT(11)"` +} diff --git a/app/db/model/privilege_card_num.go b/app/db/model/privilege_card_num.go new file mode 100644 index 0000000..c3d4147 --- /dev/null +++ b/app/db/model/privilege_card_num.go @@ -0,0 +1,24 @@ +package model + +import ( + "time" +) + +type PrivilegeCardNum struct { + Id int64 `json:"id" xorm:"pk autoincr comment('主键') BIGINT(11)"` + CardNum string `json:"card_num" xorm:"not null default '' comment('卡号') index VARCHAR(255)"` + CardKey string `json:"card_key" xorm:"not null default '' comment('激活码') index VARCHAR(255)"` + Uid int `json:"uid" xorm:"comment('绑定用户') index INT(11)"` + Phone string `json:"phone" xorm:"not null default '' comment('用户手机号') VARCHAR(255)"` + ParentUid int `json:"parent_uid" xorm:"not null default 0 comment('推荐人') index INT(11)"` + CardType int `json:"card_type" xorm:"not null default 1 comment('1实体卡 2虚拟卡') TINYINT(1)"` + Theme string `json:"theme" xorm:"not null default '' comment('主题描述') VARCHAR(255)"` + FromType int `json:"from_type" xorm:"not null default 0 comment('来源:0用户购买 1后台操作 2 导入') TINYINT(1)"` + Status int `json:"status" xorm:"not null default 0 comment('状态:0未激活 1激活') TINYINT(1)"` + GivenData string `json:"given_data" xorm:"comment('赠送权益数据') TEXT"` + ValidDateType int `json:"valid_date_type" xorm:"not null default 0 comment('1月度 2季度 3年度 4永久 5自定义(天数)') TINYINT(1)"` + CustomDay int `json:"custom_day" xorm:"not null default 0 comment('自定义天数') INT(11)"` + ExpireTime time.Time `json:"expire_time" xorm:"not null default '0000-00-00 00:00:00' comment('过期时间 2099-12-31 23:59:59表示永久') DATETIME"` + CreateTime time.Time `json:"create_time" xorm:"created not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"updated not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` +} diff --git a/app/db/model/privilege_card_ord.go b/app/db/model/privilege_card_ord.go new file mode 100644 index 0000000..537c3a2 --- /dev/null +++ b/app/db/model/privilege_card_ord.go @@ -0,0 +1,21 @@ +package model + +type PrivilegeCardOrd struct { + Id int64 `json:"id" xorm:"pk autoincr comment('订单ID') BIGINT(20)"` + OrdId int64 `json:"ord_id" xorm:"not null comment('订单号') index BIGINT(20)"` + Uid int `json:"uid" xorm:"not null comment('用户id') index INT(10)"` + GoodsId int `json:"goods_id" xorm:"not null default 0 comment('商品ID') INT(11)"` + GoodsNum int `json:"goods_num" xorm:"not null default 1 comment('商品数量') INT(11)"` + GoodsPrice string `json:"goods_price" xorm:"not null default 0.00 comment('商品单价') DECIMAL(10,2)"` + PaidPrice string `json:"paid_price" xorm:"not null default 0.00 comment('付款金额') DECIMAL(10,2)"` + Profit string `json:"profit" xorm:"not null default 0.00 comment('利润') DECIMAL(10,2)"` + PayMethod int `json:"pay_method" xorm:"not null default 1 comment('1余额2支付宝3微信') TINYINT(1)"` + State int `json:"state" xorm:"not null default 0 comment('0未支付,1已支付,2已退款,3失效') TINYINT(1)"` + Account string `json:"account" xorm:"not null default '' comment('充值账号--直冲') VARCHAR(255)"` + KeySecret string `json:"key_secret" xorm:"not null comment('兑换用json--卡券') TEXT"` + CreatedAt int `json:"created_at" xorm:"created not null default 0 comment('创建时间') INT(10)"` + UpdatedAt int `json:"updated_at" xorm:"updated not null default 0 comment('更新时间') INT(10)"` + DeletedAt int `json:"deleted_at" xorm:"not null default 0 comment('删除时间') INT(10)"` + ExpiredAt int `json:"expired_at" xorm:"not null default 0 comment('到期时间') INT(10)"` + SettleAt int `json:"settle_at" xorm:"not null default 0 comment('结算时间') INT(10)"` +} diff --git a/app/db/model/privilege_open_card_ord.go b/app/db/model/privilege_open_card_ord.go new file mode 100644 index 0000000..ecdaae8 --- /dev/null +++ b/app/db/model/privilege_open_card_ord.go @@ -0,0 +1,28 @@ +package model + +import ( + "time" +) + +type PrivilegeOpenCardOrd struct { + OrdId int64 `json:"ord_id" xorm:"not null pk comment('系统订单号') BIGINT(20)"` + Uid int `json:"uid" xorm:"not null comment('购买人id') INT(11)"` + DateType int `json:"date_type" xorm:"not null default 1 comment('日期类型: 1月 2季 3年 4永久') TINYINT(1)"` + CardType int `json:"card_type" xorm:"not null default 1 comment('权益卡类型:1实体卡 2虚拟卡') TINYINT(1)"` + Address string `json:"address" xorm:"comment('收货地址') TEXT"` + Receiver string `json:"receiver" xorm:"comment('收货人') VARCHAR(255)"` + Phone string `json:"phone" xorm:"comment('收货人手机号') VARCHAR(20)"` + CostPrice string `json:"cost_price" xorm:"not null default 0.0000 comment('支付金额') DECIMAL(10,4)"` + LogisticNum string `json:"logistic_num" xorm:"comment('物流单号') VARCHAR(255)"` + State int `json:"state" xorm:"not null default 0 comment('0未支付 1已支付 2印刷中 3已发货 4已完成 5售后中 6关闭') TINYINT(1)"` + LogisticCompany string `json:"logistic_company" xorm:"comment('物流公司') VARCHAR(255)"` + CardNum string `json:"card_num" xorm:"comment('卡号') VARCHAR(255)"` + CardKey string `json:"card_key" xorm:"comment('卡密') VARCHAR(255)"` + AfterSaleId int64 `json:"after_sale_id" xorm:"comment('售后单号') BIGINT(20)"` + PayTime time.Time `json:"pay_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('支付时间') DATETIME"` + CreateTime time.Time `json:"create_time" xorm:"created not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"updated not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + GivenData string `json:"given_data" xorm:"comment('赠送设置') TEXT"` + PayChannel int `json:"pay_channel" xorm:"not null comment('支付方式:1balance 2alipay 3wx_pay') TINYINT(1)"` + SettleAt int `json:"settle_at" xorm:"default 0 comment('返现时间') INT(11)"` +} diff --git a/app/db/model/product.go b/app/db/model/product.go new file mode 100644 index 0000000..08965d9 --- /dev/null +++ b/app/db/model/product.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type Product struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('产品库名称') VARCHAR(64)"` + Pvd string `json:"pvd" xorm:"not null default '' comment('平台') VARCHAR(12)"` + Source string `json:"source" xorm:"not null default '' comment('来源;official:官方') VARCHAR(32)"` + Data string `json:"data" xorm:"not null comment('设置数据源,json') TEXT"` + CreateAt time.Time `json:"create_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` +} diff --git a/app/db/model/product_category.go b/app/db/model/product_category.go new file mode 100644 index 0000000..edce559 --- /dev/null +++ b/app/db/model/product_category.go @@ -0,0 +1,25 @@ +package model + +import ( + "time" +) + +type ProductCategory struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pid int `json:"pid" xorm:"not null default 0 comment('分类父ID') INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 comment('操作人') INT(11)"` + Pvd string `json:"pvd" xorm:"not null default '' comment('供应商名,,taobao,jd,pdd,vip,suning,kaola') VARCHAR(16)"` + Name string `json:"name" xorm:"not null default '' comment('分类名称') VARCHAR(32)"` + Img string `json:"img" xorm:"not null default '' comment('分类图片') VARCHAR(64)"` + Sort int `json:"sort" xorm:"default 0 comment('序号') INT(5)"` + IsUse int `json:"is_use" xorm:"not null default 1 comment('是否开启;1:开启;0:关闭') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` +} + +type ProductCategoryApp struct { + Id string `json:"id"` + Pid string `json:"pid"` + Pvd string `json:"pvd"` + Name string `json:"name"` +} diff --git a/app/db/model/product_goods.go b/app/db/model/product_goods.go new file mode 100644 index 0000000..acd1da9 --- /dev/null +++ b/app/db/model/product_goods.go @@ -0,0 +1,7 @@ +package model + +type ProductGoods struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + SourceId int `json:"source_id" xorm:"not null comment('product表id') INT(11)"` + GoodsId int `json:"goods_id" xorm:"not null comment('guide_goods表id') INT(11)"` +} diff --git a/app/db/model/province.go b/app/db/model/province.go new file mode 100644 index 0000000..651b5bf --- /dev/null +++ b/app/db/model/province.go @@ -0,0 +1,6 @@ +package model + +type Province struct { + Id string `json:"id" xorm:"not null pk VARCHAR(12)"` + Name string `json:"name" xorm:"VARCHAR(64)"` +} diff --git a/app/db/model/redirect_info.go b/app/db/model/redirect_info.go new file mode 100644 index 0000000..5e646fc --- /dev/null +++ b/app/db/model/redirect_info.go @@ -0,0 +1,16 @@ +package model + +type RedirectInfo struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Host string `json:"host" xorm:"not null default '' VARCHAR(255)"` + PreHost string `json:"pre_host" xorm:"not null default '' VARCHAR(255)"` + InviteCode string `json:"invite_code" xorm:"not null default '' VARCHAR(255)"` + Appid string `json:"appid" xorm:"not null default '' VARCHAR(255)"` + Unionid string `json:"unionid" xorm:"not null default '' VARCHAR(255)"` + Openid string `json:"openid" xorm:"not null default '' VARCHAR(255)"` + Headimgurl string `json:"headimgurl" xorm:"not null default '' VARCHAR(255)"` + Nickname string `json:"nickname" xorm:"not null default '' VARCHAR(2550)"` + CreateAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + UpdateAt int `json:"updated_at" xorm:"not null default 0 INT(11)"` + Sex int `json:"sex" xorm:"not null default 0 INT(1)"` +} diff --git a/app/db/model/regional_agent_base.go b/app/db/model/regional_agent_base.go new file mode 100644 index 0000000..07bc926 --- /dev/null +++ b/app/db/model/regional_agent_base.go @@ -0,0 +1,35 @@ +package model + +import ( + "time" +) + +type RegionalAgentBase struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + OpenProvinceAgent int `json:"open_province_agent" xorm:"not null comment('是否开启省级代理(否:0;是:1)') TINYINT(1)"` + ProvinceSchemeId int `json:"province_scheme_id" xorm:"comment('省级方案id') INT(11)"` + OpenCityAgent int `json:"open_city_agent" xorm:"not null default 0 comment('是否开启市级代理(否:0;是:1)') TINYINT(1)"` + CitySchemeId int `json:"city_scheme_id" xorm:"comment('市级方案id') INT(11)"` + OpenDistrictAgent int `json:"open_district_agent" xorm:"not null default 0 comment('是否开启县区级代理(否:0;是:1)') TINYINT(1)"` + DistrictSchemeId int `json:"district_scheme_id" xorm:"default 0 comment('县区级方案id') INT(11)"` + OpenBranchesAgent int `json:"open_branches_agent" xorm:"not null default 0 comment('是否开启网点代理(否:0;是:1)') TINYINT(1)"` + BranchesSchemeId int `json:"branches_scheme_id" xorm:"default 1 comment('网点方案id') INT(11)"` + UpgradeSequence int `json:"upgrade_sequence" xorm:"not null default 0 comment('升级顺序(1逐级升级2任意级别升级)') TINYINT(1)"` + AutoOrderBy int `json:"auto_order_by" xorm:"not null default 0 comment('订单归属,网点加入方式(1定位后自动加入,2用户手动选择加入)') TINYINT(1)"` + ScopeOfOrder int `json:"scope_of_order" xorm:"default 0 comment('当选择定位后自动加入,该字段必填;订单归属范围(km单位)用处:定位后自动绑定xxkm之内离用户最近的网点') INT(11)"` + RemindType int `json:"remind_type" xorm:"default 0 comment('当选择用户手动选择加入,该字段必填;应用内用户选择网点弹窗方式(0关闭,1正常开启,2强制开启)') TINYINT(1)"` + ScopeOfBranches int `json:"scope_of_branches" xorm:"not null comment('网点展示范围(单位:km)') INT(11)"` + PayChannel string `json:"pay_channel" xorm:"not null comment('支付方式设置([1,2,3])1余额支付2微信支付3支付宝支付') VARCHAR(255)"` + CreateTime time.Time `json:"create_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + UpdateTime time.Time `json:"update_time" xorm:"not null default 'CURRENT_TIMESTAMP' comment('更新时间') DATETIME"` + MallAutoOrderBy int `json:"mall_auto_order_by" xorm:"default 0 comment('订单归属,网点加入方式(1定位后自动加入,2用户手动选择加入 3以订单收货地址自动归属)') TINYINT(1)"` + O2oAutoOrderBy int `json:"o2o_auto_order_by" xorm:"default 0 comment('订单归属,网点加入方式(1定位后自动加入,2用户手动选择加入 4以消费商家定位自动归属)') TINYINT(1)"` + MallScopeOfOrder int `json:"mall_scope_of_order" xorm:"default 0 comment('当选择定位后自动加入,该字段必填;订单归属范围(km单位)用处:定位后自动绑定xxkm之内离用户最近的网点') INT(11)"` + MallRemindType int `json:"mall_remind_type" xorm:"default 0 comment('当选择用户手动选择加入,该字段必填;应用内用户选择网点弹窗方式(0关闭,1正常开启,2强制开启)') TINYINT(1)"` + O2oScopeOfOrder int `json:"o2o_scope_of_order" xorm:"default 0 comment('当选择定位后自动加入,该字段必填;订单归属范围(km单位)用处:定位后自动绑定xxkm之内离用户最近的网点') INT(11)"` + O2oRemindType int `json:"o2o_remind_type" xorm:"default 0 comment('当选择用户手动选择加入,该字段必填;应用内用户选择网点弹窗方式(0关闭,1正常开启,2强制开启)') TINYINT(1)"` + IsJoin int `json:"is_join" xorm:"default 0 comment('是否加入,1:是,2:否') TINYINT"` + CoinRewardOpen int `json:"coin_reward_open" xorm:"default 0 comment('1:启用,2:关闭') TINYINT(1)"` + CoinSelect string `json:"coin_select" xorm:"default 0 comment('选中的虚拟币') VARCHAR(255)"` + CoinSet string `json:"coin_set" xorm:"comment('虚拟币设置') TEXT"` +} diff --git a/app/db/model/search_base.go b/app/db/model/search_base.go new file mode 100644 index 0000000..8a62546 --- /dev/null +++ b/app/db/model/search_base.go @@ -0,0 +1,10 @@ +package model + +type SearchBase struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pvd string `json:"pvd" xorm:"not null default '' VARCHAR(64)"` + AppFilterId string `json:"app_filter_id" xorm:"not null comment('app过滤词模板search_filter id') TEXT"` + AppletFilterId string `json:"applet_filter_id" xorm:"not null comment('小程序过滤词模板search_filter id') TEXT"` + ConditionId int `json:"condition_id" xorm:"not null default 0 comment('搜索模板 search_condition id') INT(11)"` + Type string `json:"type" xorm:"not null default '' comment('默认商品搜索类型') VARCHAR(64)"` +} diff --git a/app/db/model/search_condition.go b/app/db/model/search_condition.go new file mode 100644 index 0000000..688f274 --- /dev/null +++ b/app/db/model/search_condition.go @@ -0,0 +1,17 @@ +package model + +import ( + "time" +) + +type SearchCondition struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' VARCHAR(64)"` + Memo string `json:"memo" xorm:"VARCHAR(255)"` + SearchType int `json:"search_type" xorm:"not null default 1 comment('默认搜索模式;1:默认有券;2:默认全部') TINYINT(1)"` + SortType int `json:"sort_type" xorm:"not null default 1 comment('搜索默认排序;1:销量高到低;2:价格低到高;3:佣金比例低到高;4:佣金高到低') TINYINT(1)"` + LowestPrice string `json:"lowest_price" xorm:"not null default 0.00 comment('搜索最低价格') DECIMAL(11,2)"` + LowestCommissionRate string `json:"lowest_commission_rate" xorm:"not null default 0.0000 comment('搜索最低佣金比例') DECIMAL(11,4)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' TIMESTAMP"` +} diff --git a/app/db/model/search_filter.go b/app/db/model/search_filter.go new file mode 100644 index 0000000..c5df861 --- /dev/null +++ b/app/db/model/search_filter.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type SearchFilter struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' VARCHAR(64)"` + FilterKey string `json:"filter_key" xorm:"not null TEXT"` + Memo string `json:"memo" xorm:"not null default '' VARCHAR(256)"` + CreateAt time.Time `json:"create_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` +} diff --git a/app/db/model/shake_ticket.go b/app/db/model/shake_ticket.go new file mode 100644 index 0000000..64628db --- /dev/null +++ b/app/db/model/shake_ticket.go @@ -0,0 +1,24 @@ +package model + +type ShakeTicket struct { + Id int `json:"id" xorm:"not null pk autoincr comment('主键') INT(11)"` + Uid int `json:"uid" xorm:"not null comment('用户id') index INT(20)"` + ItemId string `json:"item_id" xorm:"comment('商品id') VARCHAR(50)"` + IsCollect int `json:"is_collect" xorm:"not null comment('是否收藏') INT(1)"` + IsCoupons int `json:"is_coupons" xorm:"not null comment('是否领券买') INT(1)"` + IsShare int `json:"is_share" xorm:"not null comment('是否分享赚') INT(1)"` + UserName string `json:"user_name" xorm:"not null default '' comment('用户名') VARCHAR(50)"` + AvatarUrl string `json:"avatar_url" xorm:"not null default '' comment('头像url') VARCHAR(2000)"` +} + +type ShakeTicketApp struct { + Id int `json:"id"` + Uid int `json:"uid"` + ItemId string `json:"item_id"` + IsCollect int `json:"is_collect"` + IsCoupons int `json:"is_coupons"` + IsShare int `json:"is_share"` + UserName string `json:"user_name"` + AvatarUrl string `json:"avatar_url"` + Content string `json:"content"` +} diff --git a/app/db/model/sys_category.go b/app/db/model/sys_category.go new file mode 100644 index 0000000..f9b02f8 --- /dev/null +++ b/app/db/model/sys_category.go @@ -0,0 +1,12 @@ +package model + +type SysCategory struct { + Cid int `json:"cid" xorm:"not null pk autoincr comment('分类ID') INT(10)"` + Pid int `json:"pid" xorm:"not null default 0 comment('父分类ID') index INT(10)"` + ProviderCid int `json:"provider_cid" xorm:"not null default 0 comment('供应商分类ID') INT(10)"` + Provider string `json:"provider" xorm:"not null default '' comment('供应商名,sys为首页分类,taobao,jd,pdd,vip,suning,kaola') VARCHAR(16)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(64)"` + Icon string `json:"icon" xorm:"not null default '' comment('图标') VARCHAR(2000)"` + Sort int `json:"sort,omitempty" xorm:"not null default 0 comment('排序,越大越前') INT(10)"` + State int `json:"state,omitempty" xorm:"default 1 comment('0隐藏,1显示') TINYINT(1)"` +} diff --git a/app/db/model/sys_cfg.go b/app/db/model/sys_cfg.go new file mode 100644 index 0000000..22d906b --- /dev/null +++ b/app/db/model/sys_cfg.go @@ -0,0 +1,7 @@ +package model + +type SysCfg struct { + Key string `json:"key" xorm:"not null pk comment('键') VARCHAR(127)"` + Val string `json:"val" xorm:"comment('值') TEXT"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` +} diff --git a/app/db/model/sys_faq.go b/app/db/model/sys_faq.go new file mode 100644 index 0000000..c471037 --- /dev/null +++ b/app/db/model/sys_faq.go @@ -0,0 +1,14 @@ +package model + +type SysFaq struct { + Id int `json:"id" xorm:"not null pk autoincr INT(10)"` + Pid int `json:"pid" xorm:"not null default 0 comment('父级ID') INT(11)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(128)"` + Icon string `json:"icon" xorm:"not null default '' comment('图标') VARCHAR(2000)"` + Content string `json:"content" xorm:"not null comment('内容') TEXT"` + Sort int `json:"sort,omitempty" xorm:"not null default 0 comment('排序,越大越前') INT(11)"` + Platform string `json:"platform,omitempty" xorm:"not null default 'wx_applet' comment('平台,wx_applet,toutiao_applet,baidu_applet,tiktok_applet,wap,android,ios') VARCHAR(64)"` + IsAll int `json:"is_all,omitempty" xorm:"not null default 1 comment('是否平台通用,0指定平台, 1所有平台') TINYINT(1)"` + State int `json:"state,omitempty" xorm:"not null default 1 comment('0隐藏,1显示') TINYINT(1)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('创建时间') INT(10)"` +} diff --git a/app/db/model/sys_feedback.go b/app/db/model/sys_feedback.go new file mode 100644 index 0000000..4ff8ff0 --- /dev/null +++ b/app/db/model/sys_feedback.go @@ -0,0 +1,21 @@ +package model + +type SysFeedback struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Pid int `json:"pid" xorm:"not null default 0 comment('父ID') index INT(10)"` + Uid int `json:"uid" xorm:"not null default 0 comment('0官方账号,>0为用户') index INT(10)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(255)"` + Content string `json:"content" xorm:"not null default '' comment('内容') VARCHAR(2000)"` + ImgList string `json:"img_list" xorm:"not null default '' comment('上传图片地址') VARCHAR(2000)"` + Type int `json:"type" xorm:"not null default 0 comment('0功能异常,1优化建议,2其它') TINYINT(1)"` + Platform string `json:"platform" xorm:"not null default 'wx_applet' comment('平台,wx_applet,toutiao_applet,baidu_applet,tiktok_applet,wap,android,ios') VARCHAR(64)"` + AppVersion string `json:"app_version" xorm:"not null default '' comment('应用版本') VARCHAR(32)"` + OsVersion string `json:"os_version" xorm:"not null default '' comment('系统版本') VARCHAR(128)"` + DeviceId string `json:"device_id" xorm:"not null default '' comment('设备唯一ID') VARCHAR(128)"` + DeviceModel string `json:"device_model" xorm:"not null default '' comment('设备型号,如:iPhone 11') VARCHAR(64)"` + Solution string `json:"solution" xorm:"not null default '' comment('分辨率') VARCHAR(32)"` + Ip string `json:"ip" xorm:"not null default '' comment('IP地址') VARCHAR(32)"` + State int `json:"state" xorm:"not null default 0 comment('0未解决,1已回复,2已解决') TINYINT(1)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('创建时间') INT(10)"` + UpdateAt int `json:"update_at" xorm:"not null default 0 comment('更新时间') TINYINT(3)"` +} diff --git a/app/db/model/sys_file.go b/app/db/model/sys_file.go new file mode 100644 index 0000000..1324692 --- /dev/null +++ b/app/db/model/sys_file.go @@ -0,0 +1,21 @@ +package model + +type SysFile struct { + Fid int64 `json:"fid" xorm:"not null pk autoincr BIGINT(20)"` + ParentFid int64 `json:"parent_fid" xorm:"not null default 0 comment('父目录id') index BIGINT(20)"` + FileType int `json:"file_type" xorm:"not null default 1 comment('0目录,1文件') TINYINT(1)"` + Uid int `json:"uid" xorm:"not null default 0 comment('上传用户ID,0为管理员') INT(10)"` + ShowName string `json:"show_name" xorm:"not null default '' comment('显示名字') index VARCHAR(128)"` + SaveName string `json:"save_name" xorm:"not null default '' comment('服务器保存名字') VARCHAR(128)"` + Ext string `json:"ext" xorm:"not null default '' comment('文件后缀') VARCHAR(8)"` + Hash string `json:"hash" xorm:"not null default '' comment('哈希值') VARCHAR(32)"` + Mime string `json:"mime" xorm:"not null default '' comment('Mime类型') VARCHAR(64)"` + Provider string `json:"provider" xorm:"not null default '' comment('供应商,qiniu,aliyun,local') VARCHAR(16)"` + Width int `json:"width" xorm:"not null default 0 comment('宽,px') INT(10)"` + Height int `json:"height" xorm:"not null default 0 comment('高,px') INT(10)"` + Bucket string `json:"bucket" xorm:"not null default '' comment('上传空间') VARCHAR(32)"` + FileSize int64 `json:"file_size" xorm:"not null default 0 comment('文件大小,byte') BIGINT(20)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('上传时间') INT(10)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(100)"` + State int `json:"state" xorm:"not null default 0 comment('状态0未锁定,1锁定,锁定状态不能进行删除') TINYINT(1)"` +} diff --git a/app/db/model/sys_invite_codes.go b/app/db/model/sys_invite_codes.go new file mode 100644 index 0000000..4cfff29 --- /dev/null +++ b/app/db/model/sys_invite_codes.go @@ -0,0 +1,6 @@ +package model + +type SysInviteCodes struct { + Code string `json:"code" xorm:"not null pk default '0' comment('邀请码') VARCHAR(10)"` + IsUsed int `json:"is_used" xorm:"not null default 0 comment('是否被使用') TINYINT(1)"` +} diff --git a/app/db/model/sys_module.go b/app/db/model/sys_module.go new file mode 100644 index 0000000..82b4e88 --- /dev/null +++ b/app/db/model/sys_module.go @@ -0,0 +1,34 @@ +package model + +import ( + "time" +) + +type SysModule struct { + ModId int `json:"mod_id" xorm:"not null pk autoincr INT(10)"` + ModPid int `json:"mod_pid" xorm:"not null default 0 comment('父级模块ID') INT(10)"` + TemplateId int `json:"template_id" xorm:"not null default 0 comment('模板ID') INT(11)"` + ModName string `json:"mod_name" xorm:"not null default '' comment('模块名称') VARCHAR(250)"` + Position string `json:"position" xorm:"not null default '' comment('位置') VARCHAR(250)"` + SkipIdentifier string `json:"skip_identifier" xorm:"not null default '' comment('跳转标识') VARCHAR(250)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(128)"` + Subtitle string `json:"subtitle" xorm:"not null default '' comment('副标题') VARCHAR(255)"` + Url string `json:"url" xorm:"not null default '' comment('跳转链接') VARCHAR(512)"` + Margin string `json:"margin" xorm:"not null default '0,0,0,0' comment('边距,上右下左') VARCHAR(64)"` + AspectRatio string `json:"aspect_ratio" xorm:"not null default 0.00 comment('宽高比,宽/高保留两位小数') DECIMAL(4,2)"` + Icon string `json:"icon" xorm:"not null default '' comment('图标') VARCHAR(512)"` + Img string `json:"img" xorm:"not null default '' comment('图片') VARCHAR(512)"` + FontColor string `json:"font_color" xorm:"not null default '' comment('文字颜色') VARCHAR(128)"` + BgImg string `json:"bg_img" xorm:"not null default '' comment('背景图片') VARCHAR(512)"` + BgColor string `json:"bg_color" xorm:"not null default '' comment('背景颜色') VARCHAR(512)"` + BgColorT string `json:"bg_color_t" xorm:"not null default '' comment('背景颜色过度') VARCHAR(255)"` + Badge string `json:"badge" xorm:"not null default '' comment('badge图片') VARCHAR(512)"` + Path string `json:"path" xorm:"not null default '' comment('跳转路径') VARCHAR(255)"` + Data string `json:"data" xorm:"comment('内容') TEXT"` + Sort int `json:"sort" xorm:"not null default 0 comment('排序') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('0不显示,1显示') TINYINT(1)"` + IsGlobal int `json:"is_global" xorm:"not null default 0 comment('是否全局显示') TINYINT(1)"` + Platform int `json:"platform" xorm:"not null default 1 comment('平台;1:全平台;2:App应用(ios和android);3:H5(wap);4:微信小程序;5:抖音小程序;6:百度小程序') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/sys_popup.go b/app/db/model/sys_popup.go new file mode 100644 index 0000000..3e8cc69 --- /dev/null +++ b/app/db/model/sys_popup.go @@ -0,0 +1,22 @@ +package model + +import ( + "time" +) + +type SysPopup struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 INT(11)"` + Name string `json:"name" xorm:"not null default '' VARCHAR(32)"` + ConditionType string `json:"condition_type" xorm:"not null default 'all' comment('展示人群类型;all:所有人;new_user:新用户;level:指定等级;tag:指定标签;no_order_user:未出单用户') VARCHAR(32)"` + Condition string `json:"condition" xorm:"not null comment('弹窗条件,json') TEXT"` + Position string `json:"position" xorm:"not null default 'index' comment('展示位置;index:首页') VARCHAR(64)"` + Image string `json:"image" xorm:"not null default '' comment('弹窗图片') VARCHAR(128)"` + Interval int `json:"interval" xorm:"not null default 0 comment('弹窗时间间隔;单位:分钟') INT(11)"` + Skip string `json:"skip" xorm:"not null default '' comment('跳转标识') VARCHAR(255)"` + Type int `json:"type" xorm:"not null default 1 comment('弹窗时间类型;1:固定时间;2:每天定时') TINYINT(1)"` + PopupTime string `json:"popup_time" xorm:"comment('弹窗时间,json') TEXT"` + State int `json:"state" xorm:"not null default 0 comment('状态;0:不启用;1:启用') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` +} diff --git a/app/db/model/sys_push_app.go b/app/db/model/sys_push_app.go new file mode 100644 index 0000000..5d0ee70 --- /dev/null +++ b/app/db/model/sys_push_app.go @@ -0,0 +1,22 @@ +package model + +import ( + "time" +) + +type SysPushApp struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(32)"` + Content string `json:"content" xorm:"not null comment('内容') TEXT"` + Image string `json:"image" xorm:"not null default '' comment('图片(只有官方才有图片)') VARCHAR(255)"` + Provider string `json:"provider" xorm:"not null default 'mob' comment('平台供应商,如:mob,official:官方推送') VARCHAR(16)"` + Type string `json:"type" xorm:"not null default '' comment('模板类型 | 推送类型;public;:普通推送;activity:活动通知;order_self:新订单提醒(导购自购新订单),order_team:新订单提醒(团队新订单),order_share:新订单提醒(导购分享新订单),member_register:团队成员注册成功,level_upgrade:团队成员等级升级成功,withdraw_fail:提现失败提醒,withdraw_success:提现成功提醒,comission_settle_success:佣金结算提醒(平台结算)') VARCHAR(50)"` + SendAt int `json:"send_at" xorm:"not null default 0 comment('指定发送时间0为马上执行') INT(11)"` + State int `json:"state" xorm:"not null default 1 comment('1发送中,2成功,3失败,4部分成功') TINYINT(1)"` + DeviceProvider int `json:"device_provider" xorm:"default 1 comment('推送设备平台。1:全平台;2:安卓;3:ios') TINYINT(1)"` + Target int `json:"target" xorm:"not null default 1 comment('推送目标;1:全部会员;2:指定会员;3:指定等级;4:指定标签') TINYINT(1)"` + TargetCondition string `json:"target_condition" xorm:"comment('推送目标条件。json格式;') TEXT"` + Skip string `json:"skip" xorm:"not null default '' comment('跳转功能') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/sys_push_sms.go b/app/db/model/sys_push_sms.go new file mode 100644 index 0000000..ef63a2b --- /dev/null +++ b/app/db/model/sys_push_sms.go @@ -0,0 +1,19 @@ +package model + +import ( + "time" +) + +type SysPushSms struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Content string `json:"content" xorm:"not null comment('内容') TEXT"` + Provider string `json:"provider" xorm:"not null default '' comment('短信供应平台,暂时没有') VARCHAR(20)"` + SendAt int `json:"send_at" xorm:"not null default 0 comment('指定发送时间0为马上执行') INT(10)"` + State int `json:"state" xorm:"not null default 0 comment('0发送中,1成功,2失败,3部分成功') TINYINT(1)"` + Target int `json:"target" xorm:"not null default 1 comment('推送目标;1:全部会员;2:指定会员;3:指定等级;4:指定标签') TINYINT(1)"` + TargetCondition string `json:"target_condition" xorm:"comment('推送目标条件。json格式;') TEXT"` + Skip string `json:"skip" xorm:"not null default '' comment('跳转功能') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` + Type string `json:"type" xorm:"not null default '' comment('模板类型 | 推送类型;public;:普通推送;activity:活动通知;order_self:新订单提醒(导购自购新订单),order_team:新订单提醒(团队新订单),order_share:新订单提醒(导购分享新订单),member_register:团队成员注册成功,level_upgrade:团队成员等级升级成功,withdraw_fail:提现失败提醒,withdraw_success:提现成功提醒,comission_settle_success:佣金结算提醒(平台结算)') VARCHAR(50)"` +} diff --git a/app/db/model/sys_push_template.go b/app/db/model/sys_push_template.go new file mode 100644 index 0000000..ec82030 --- /dev/null +++ b/app/db/model/sys_push_template.go @@ -0,0 +1,17 @@ +package model + +import ( + "time" +) + +type SysPushTemplate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + IsAppPush int `json:"is_app_push" xorm:"not null default 1 comment('是否app推送') TINYINT(1)"` + IsSmsPush int `json:"is_sms_push" xorm:"not null default 0 comment('是否短信推送') TINYINT(1)"` + Type string `json:"type" xorm:"not null default '' comment('模板类型') VARCHAR(50)"` + Title string `json:"title" xorm:"not null default '' comment('标题') VARCHAR(128)"` + Content string `json:"content" xorm:"not null comment('内容') TEXT"` + Skip string `json:"skip" xorm:"not null default '' comment('跳转功能') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"default CURRENT_TIMESTAMP TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default CURRENT_TIMESTAMP TIMESTAMP"` +} diff --git a/app/db/model/sys_push_user.go b/app/db/model/sys_push_user.go new file mode 100644 index 0000000..d4a2157 --- /dev/null +++ b/app/db/model/sys_push_user.go @@ -0,0 +1,18 @@ +package model + +import ( + "time" +) + +type SysPushUser struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + PushId int `json:"push_id" xorm:"not null default 0 comment('sys_push_app表ID') index INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 INT(11)"` + State int `json:"state" xorm:"not null default 0 comment('发送状态;0:失败;1:成功') TINYINT(1)"` + Time time.Time `json:"time" xorm:"default CURRENT_TIMESTAMP comment('发送时间') TIMESTAMP"` + SendData string `json:"send_data" xorm:"comment('发送内容,json格式') TEXT"` + Provider string `json:"provider" xorm:"not null default 'mob' comment('平台供应商,如:mob,official:官方推送') VARCHAR(16)"` + Type string `json:"type" xorm:"not null default '' comment('模板类型 | 推送类型;public;:普通推送;activity:活动通知;order_self:新订单提醒(导购自购新订单),order_team:新订单提醒(团队新订单),order_share:新订单提醒(导购分享新订单),member_register:团队成员注册成功,level_upgrade:团队成员等级升级成功,withdraw_fail:提现失败提醒,withdraw_success:提现成功提醒,comission_settle_success:佣金结算提醒(平台结算)') VARCHAR(50)"` + SendAt int `json:"send_at" xorm:"not null default 0 comment('官方活动显示时间(大于当前时间戳才显示);0为即可显示') INT(11)"` + IsRead int `json:"is_read" xorm:"not null default 0 comment('是否已读;0:未读;1:已读') TINYINT(1)"` +} diff --git a/app/db/model/sys_template.go b/app/db/model/sys_template.go new file mode 100644 index 0000000..4ee3b05 --- /dev/null +++ b/app/db/model/sys_template.go @@ -0,0 +1,19 @@ +package model + +import ( + "time" +) + +type SysTemplate struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"not null default 0 INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('模板名称') VARCHAR(32)"` + Title string `json:"title" xorm:"not null default '' comment('页面title字段') VARCHAR(32)"` + Type string `json:"type" xorm:"not null default 'index' comment('模板类型;index:首页;bottom:底部导航栏;member:会员中心;custom:自定义模板;share_goods_image:商品图文分享;share_goods_link:商品链接分享;share_goods_platform_xx:商品分享平台(xx对应平台类型)') VARCHAR(64)"` + Image string `json:"image" xorm:"not null default '' VARCHAR(128)"` + IsUse int `json:"is_use" xorm:"default 0 comment('是否使用;1:使用;0未使用') TINYINT(1)"` + Remark string `json:"remark" xorm:"not null default '' comment('备注') VARCHAR(128)"` + IsSystem int `json:"is_system" xorm:"not null default 0 comment('是否系统模板;0:否;1:是') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"default CURRENT_TIMESTAMP TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default CURRENT_TIMESTAMP TIMESTAMP"` +} diff --git a/app/db/model/sys_union_set.go b/app/db/model/sys_union_set.go new file mode 100644 index 0000000..b3e93a0 --- /dev/null +++ b/app/db/model/sys_union_set.go @@ -0,0 +1,24 @@ +package model + +import ( + "time" +) + +type SysUnionSet struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Provider string `json:"provider" xorm:"not null default '' comment('联盟类型;taobao:淘宝;jd:京东;pdd:多多(拼多多);vip:唯品会;suning:苏宁;kaola:考拉;duomai:多麦') VARCHAR(32)"` + Name string `json:"name" xorm:"not null default '' comment('备注名') VARCHAR(32)"` + ChannelList string `json:"channel_list" xorm:"not null default '["share","weixin","android","ios"]' comment('渠道开关;share:分享;weixin:微信;android:安卓;ios:ios') VARCHAR(64)"` + AuthType int `json:"auth_type" xorm:"not null default 1 comment('授权模式;1:跟随官方;2:自有联盟账号') TINYINT(1)"` + PromoteShare string `json:"promote_share" xorm:"not null default '' comment('分享推广位') VARCHAR(128)"` + SelfShare string `json:"self_share" xorm:"not null default '' comment('自购推广位') VARCHAR(128)"` + KeyData string `json:"key_data" xorm:"comment('联盟key的值') TEXT"` + IsUse int `json:"is_use" xorm:"default 0 comment('是否启用;0否;1是') TINYINT(3)"` + AuthStatus int `json:"auth_status" xorm:"default 0 comment('授权状态;0:未授权;1:已授权') TINYINT(1)"` + AuthTime time.Time `json:"auth_time" xorm:"comment('授权时间') TIMESTAMP"` + SyncTime time.Time `json:"sync_time" xorm:"comment('订单同步时间') TIMESTAMP"` + ExpireTime time.Time `json:"expire_time" xorm:"TIMESTAMP"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP"` + IsDelete int `json:"is_delete" xorm:"not null default 0 TINYINT(1)"` +} diff --git a/app/db/model/taobao_id_list.go b/app/db/model/taobao_id_list.go new file mode 100644 index 0000000..ee50994 --- /dev/null +++ b/app/db/model/taobao_id_list.go @@ -0,0 +1,8 @@ +package model + +type TaobaoIdList struct { + AccTaobaoId int64 `json:"acc_taobao_id" xorm:"not null default 0 comment('渠道id') unique BIGINT(12)"` + IsUse int `json:"is_use" xorm:"default 0 comment('是否在使用') INT(1)"` + EndTime int `json:"end_time" xorm:"default 0 comment('结束时间') INT(11)"` + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` +} diff --git a/app/db/model/user.go b/app/db/model/user.go new file mode 100644 index 0000000..6569667 --- /dev/null +++ b/app/db/model/user.go @@ -0,0 +1,25 @@ +package model + +import ( + "time" +) + +type User struct { + Uid int `json:"uid" xorm:"not null pk autoincr comment('主键ID') INT(10)"` + Username string `json:"username" xorm:"not null default '' comment('用户名') index VARCHAR(50)"` + Password string `json:"password" xorm:"not null default '' comment('密码') CHAR(32)"` + Email string `json:"email" xorm:"not null default '' comment('邮箱') VARCHAR(128)"` + Phone string `json:"phone" xorm:"not null default '' comment('联系电话') VARCHAR(20)"` + Nickname string `json:"nickname" xorm:"not null default '' comment('昵称') VARCHAR(20)"` + Level int `json:"level" xorm:"not null default 0 comment('用户等级id') INT(11)"` + InviteTotal int `json:"invite_total" xorm:"not null default 0 comment('直推邀请总人数') INT(11)"` + LevelArriveAt time.Time `json:"level_arrive_at" xorm:"not null default CURRENT_TIMESTAMP comment('到达该等级的时间') TIMESTAMP"` + LevelExpireAt time.Time `json:"level_expire_at" xorm:"not null default CURRENT_TIMESTAMP comment('该等级过期时间') TIMESTAMP"` + CreateAt time.Time `json:"create_at" xorm:"created not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"updated default CURRENT_TIMESTAMP comment('最后修改资料时间') TIMESTAMP"` + LastLoginAt time.Time `json:"last_login_at" xorm:"default CURRENT_TIMESTAMP comment('最近登录时间') TIMESTAMP"` + DeleteAt int `json:"delete_at" xorm:"not null default 0 comment('是否删除;0未删除;1已删除') TINYINT(1)"` + State int `json:"state" xorm:"not null default 1 comment('0未激活,1正常,2冻结,3删除') TINYINT(1)"` + LastLoginIp string `json:"last_login_ip" xorm:"not null default '' comment('最后登录IP') VARCHAR(64)"` + RegisterIp string `json:"register_ip" xorm:"not null default '' comment('注册IP') VARCHAR(64)"` +} diff --git a/app/db/model/user_address.go b/app/db/model/user_address.go new file mode 100644 index 0000000..352c9d9 --- /dev/null +++ b/app/db/model/user_address.go @@ -0,0 +1,18 @@ +package model + +type UserAddress struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"comment('用户id') index INT(11)"` + ProvinceId string `json:"province_id" xorm:"VARCHAR(12)"` + CityId string `json:"city_id" xorm:"VARCHAR(12)"` + CountyId string `json:"county_id" xorm:"VARCHAR(12)"` + ProvinceName string `json:"province_name" xorm:"VARCHAR(12)"` + CityName string `json:"city_name" xorm:"VARCHAR(64)"` + CountyName string `json:"county_name" xorm:"VARCHAR(64)"` + Detail string `json:"detail" xorm:"VARCHAR(64)"` + Tag string `json:"tag" xorm:"comment('地址标签') VARCHAR(255)"` + PostCode string `json:"post_code" xorm:"comment('邮编') VARCHAR(255)"` + Phone string `json:"phone" xorm:"comment('手机号') VARCHAR(20)"` + Receiver string `json:"receiver" xorm:"comment('收货人') VARCHAR(255)"` + IsDefault int `json:"is_default" xorm:"not null default 0 comment('是否为默认') TINYINT(1)"` +} diff --git a/app/db/model/user_app_domain.go b/app/db/model/user_app_domain.go new file mode 100644 index 0000000..4522cef --- /dev/null +++ b/app/db/model/user_app_domain.go @@ -0,0 +1,8 @@ +package model + +type UserAppDomain struct { + Domain string `json:"domain" xorm:"not null pk comment('绑定域名') VARCHAR(100)"` + Uuid int `json:"uuid" xorm:"not null comment('对应APP ID编号') index unique(IDX_UUID_TYPE) INT(10)"` + Type string `json:"type" xorm:"not null comment('api接口域名,wap.h5域名,admin管理后台') unique(IDX_UUID_TYPE) ENUM('admin','api','wap')"` + IsSsl int `json:"is_ssl" xorm:"not null default 0 comment('是否开启ssl:0否;1是') TINYINT(255)"` +} diff --git a/app/db/model/user_favorite.go b/app/db/model/user_favorite.go new file mode 100644 index 0000000..e32ab2e --- /dev/null +++ b/app/db/model/user_favorite.go @@ -0,0 +1,15 @@ +package model + +import ( + "time" +) + +type UserFavorite struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') index INT(11)"` + Provider string `json:"provider" xorm:"not null default '' comment('供应商') VARCHAR(8)"` + ItemId string `json:"item_id" xorm:"not null default '' comment('商品ID') VARCHAR(30)"` + ItemTitle string `json:"item_title" xorm:"not null default '' comment('商品标题') VARCHAR(255)"` + ItemData string `json:"item_data" xorm:"not null comment('商品数据') TEXT"` + CreateAt time.Time `json:"create_at" xorm:"created default CURRENT_TIMESTAMP TIMESTAMP"` +} diff --git a/app/db/model/user_foot_mark.go b/app/db/model/user_foot_mark.go new file mode 100644 index 0000000..4ad8450 --- /dev/null +++ b/app/db/model/user_foot_mark.go @@ -0,0 +1,16 @@ +package model + +import ( + "time" +) + +type UserFootMark struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') index INT(11)"` + Provider string `json:"provider" xorm:"not null default '' comment('供应商') VARCHAR(8)"` + ItemId string `json:"item_id" xorm:"default '' comment('商品ID') VARCHAR(100)"` + ItemTitle string `json:"item_title" xorm:"not null default '' comment('商品标题') VARCHAR(255)"` + ItemData string `json:"item_data" xorm:"not null comment('商品数据') TEXT"` + CreateAt time.Time `json:"create_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' TIMESTAMP"` +} diff --git a/app/db/model/user_level.go b/app/db/model/user_level.go new file mode 100644 index 0000000..21f078c --- /dev/null +++ b/app/db/model/user_level.go @@ -0,0 +1,20 @@ +package model + +import ( + "time" +) + +type UserLevel struct { + Id int `json:"id" xorm:"not null pk autoincr comment('等级id') INT(11)"` + BenefitIds string `json:"benefit_ids" xorm:"comment('该等级拥有的权益id【json】') TEXT"` + LevelName string `json:"level_name" xorm:"not null default '' comment('等级名称') VARCHAR(255)"` + LevelWeight int `json:"level_weight" xorm:"not null default 0 comment('等级权重') INT(11)"` + LevelUpdateCondition int `json:"level_update_condition" xorm:"not null default 2 comment('2是条件升级,1是无条件升级') TINYINT(1)"` + AutoAudit int `json:"auto_audit" xorm:"not null default 0 comment('(自动审核)0关闭,1开启') TINYINT(1)"` + LevelDate int `json:"level_date" xorm:"default 0 comment('会员有效期(0永久有效,单位月)') INT(11)"` + IsUse int `json:"is_use" xorm:"not null default 1 comment('是否开启(0否,1是)') TINYINT(1)"` + ChoosableNum int `json:"choosable_num" xorm:"default 0 comment('可选任务数量(当is_must_task为0时生效)') INT(6)"` + Memo string `json:"memo" xorm:"default '' comment('备注') VARCHAR(255)"` + CssSet string `json:"css_set" xorm:"TEXT"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP"` +} diff --git a/app/db/model/user_level_audit.go b/app/db/model/user_level_audit.go new file mode 100644 index 0000000..5c194db --- /dev/null +++ b/app/db/model/user_level_audit.go @@ -0,0 +1,21 @@ +package model + +import ( + "time" +) + +type UserLevelAudit struct { + Id int `json:"id" xorm:"not null pk autoincr comment('自增ID') INT(10)"` + Uid int `json:"uid" xorm:"not null default 0 comment('uid') index INT(10)"` + CurrentLevelId int `json:"current_level_id" xorm:"not null default 0 comment('用户当前等级id') INT(10)"` + NextLevelId int `json:"next_level_id" xorm:"not null default 0 comment('要升级到的等级id(续费情况id=当前等级id)') INT(10)"` + ConditionType int `json:"condition_type" xorm:"not null default 0 comment('升级方式:1无条件升级 2条件升级') TINYINT(1)"` + AutoAudit int `json:"auto_audit" xorm:"not null default 0 comment('自动审核:0关闭(手动审核),1开启(自动审核)') TINYINT(1)"` + DateType int `json:"date_type" xorm:"not null default 0 comment('1:包月,2:包季,3:包年,4:永久,0:非付费升级') TINYINT(1)"` + State int `json:"state" xorm:"not null default 1 comment('审核状态:1待审核;2审核通过;3审核拒绝') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' comment('更新时间') TIMESTAMP"` + RelateOid int64 `json:"relate_oid" xorm:"comment('关联的订单id(如果是付费)') BIGINT(22)"` + Reason string `json:"reason" xorm:"comment('拒绝理由') TEXT"` + LevelDate int `json:"level_date" xorm:"not null default 0 comment('会员有效期:0永久;其他为x月') INT(11)"` +} diff --git a/app/db/model/user_level_change_log.go b/app/db/model/user_level_change_log.go new file mode 100644 index 0000000..c205ff9 --- /dev/null +++ b/app/db/model/user_level_change_log.go @@ -0,0 +1,18 @@ +package model + +import ( + "time" +) + +type UserLevelChangeLog struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"default 0 comment('用户id') INT(11)"` + BeforeLv int `json:"before_lv" xorm:"default 0 comment('更改前的等级') INT(11)"` + AfterLv int `json:"after_lv" xorm:"default 0 comment('更改后的等级') INT(11)"` + Time int `json:"time" xorm:"INT(11)"` + BeforeLevelExpireAt time.Time `json:"before_level_expire_at" xorm:"default 'CURRENT_TIMESTAMP' comment('更新前过期时间') TIMESTAMP"` + AfterLevelExpireAt time.Time `json:"after_level_expire_at" xorm:"default 'CURRENT_TIMESTAMP' comment('更新后过期时间') TIMESTAMP"` + Type string `json:"type" xorm:"default '' comment('类型') VARCHAR(255)"` + BeforeLevelArriveAt time.Time `json:"before_level_arrive_at" xorm:"default 'CURRENT_TIMESTAMP' comment('更新前到达该等级的时间') TIMESTAMP"` + AfterLevelArriveAt time.Time `json:"after_level_arrive_at" xorm:"default 'CURRENT_TIMESTAMP' comment('更新后到达该等级的时间') TIMESTAMP"` +} diff --git a/app/db/model/user_level_ord.go b/app/db/model/user_level_ord.go new file mode 100644 index 0000000..b657693 --- /dev/null +++ b/app/db/model/user_level_ord.go @@ -0,0 +1,18 @@ +package model + +import ( + "time" +) + +type UserLevelOrd struct { + Id int64 `json:"id" xorm:"pk autoincr comment('订单id') BIGINT(22)"` + Uid int `json:"uid" xorm:"not null default 0 comment('uid') INT(10)"` + LevelId int `json:"level_id" xorm:"not null default 0 comment('等级id') INT(10)"` + PayAmount string `json:"pay_amount" xorm:"not null default 0 comment('付费金额') DECIMAL(2)"` + PayChannel int `json:"pay_channel" xorm:"not null default 0 comment('1:支付宝,2:微信,3:余额') TINYINT(1)"` + DateType int `json:"date_type" xorm:"not null default 0 comment('1:包月,2:包季,3:包年,4:永久') TINYINT(1)"` + State int `json:"state" xorm:"not null default 0 comment('支付状态:0未支付1已支付') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + ExpireAt time.Time `json:"expire_at" xorm:"not null default CURRENT_TIMESTAMP comment('过期时间') TIMESTAMP"` + SettleAt int `json:"settle_at" xorm:" default 0 comment('平台结算时间') INT(11)"` +} diff --git a/app/db/model/user_level_task.go b/app/db/model/user_level_task.go new file mode 100644 index 0000000..c3d49d4 --- /dev/null +++ b/app/db/model/user_level_task.go @@ -0,0 +1,18 @@ +package model + +import ( + "time" +) + +type UserLevelTask struct { + Id int `json:"id" xorm:"not null pk autoincr comment('主键id') INT(11)"` + LevelId int `json:"level_id" xorm:"not null default 0 comment('等级id') INT(11)"` + IsMustTask int `json:"is_must_task" xorm:"not null default 0 comment('是否必做(0,1)') TINYINT(1)"` + TaskType int `json:"task_type" xorm:"not null default 0 comment('1:累计自购订单总数,2:累计到账佣金,3:累计直推粉丝数量,4:累计团队有效直推人数,5:累计团队符合相应等级的人数,6:付费升级') TINYINT(2)"` + TaskTypeLevelId int `json:"task_type_level_id" xorm:"not null default 0 comment('task_type=5时生效,表示等级id') INT(10)"` + WithinDays int `json:"within_days" xorm:"not null default 0 comment('多少天内完成') INT(11)"` + FinishCount string `json:"finish_count" xorm:"not null default '' comment('多少天内完成的指标数') VARCHAR(11)"` + PayLevels string `json:"pay_levels" xorm:"not null default '' comment('一个json:[1,2,3,4] 表示开启月季年永久') VARCHAR(255)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/user_log.go b/app/db/model/user_log.go new file mode 100644 index 0000000..6ca2e8a --- /dev/null +++ b/app/db/model/user_log.go @@ -0,0 +1,14 @@ +package model + +import ( + "time" +) + +type UserLog struct { + Id int64 `json:"id" xorm:"pk BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') INT(11)"` + Ip string `json:"ip" xorm:"not null default '' comment('ip') VARCHAR(15)"` + Platform string `json:"platform" xorm:"not null default '' comment('平台') VARCHAR(32)"` + Device string `json:"device" xorm:"not null default '' comment('设备') VARCHAR(64)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` +} diff --git a/app/db/model/user_notice.go b/app/db/model/user_notice.go new file mode 100644 index 0000000..e881541 --- /dev/null +++ b/app/db/model/user_notice.go @@ -0,0 +1,23 @@ +package model + +import ( + "time" +) + +type UserNotice struct { + Id int `json:"id" xorm:"not null pk autoincr comment('id') INT(22)"` + Sender int `json:"sender" xorm:"not null default 0 comment('发送者用户id,0为系统发出') INT(11)"` + Receiver int `json:"receiver" xorm:"not null default 0 comment('接受用户id,0为全部接受') INT(11)"` + Type int `json:"type" xorm:"not null default 0 comment('0官方活动,1官方通知,2交易通知,3推广通知,4反馈通知') TINYINT(1)"` + MainPreview string `json:"main_preview" xorm:"not null default '' comment('主页预览') VARCHAR(255)"` + Title string `json:"title" xorm:"not null default '' comment('通知标题') VARCHAR(255)"` + Subtitle string `json:"subtitle" xorm:"not null default '' comment('通知子标题') VARCHAR(255)"` + Img string `json:"img" xorm:"not null default '' comment('消息图') VARCHAR(255)"` + Body string `json:"body" xorm:"comment('消息体') TEXT"` + Oid int64 `json:"oid" xorm:"not null default 0 comment('订单id') BIGINT(20)"` + Fid int64 `json:"fid" xorm:"not null default 0 comment('流水单id') BIGINT(20)"` + Url string `json:"url" xorm:"not null default '' comment('跳转url') VARCHAR(255)"` + SkipIdentifier string `json:"skip_identifier" xorm:"not null default '' comment('跳转标识') VARCHAR(20)"` + Status int `json:"status" xorm:"not null default 0 comment('0未读,1已读') TINYINT(1)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` +} diff --git a/app/db/model/user_profile.go b/app/db/model/user_profile.go new file mode 100644 index 0000000..357eed7 --- /dev/null +++ b/app/db/model/user_profile.go @@ -0,0 +1,90 @@ +package model + +import ( + "time" +) + +type UserProfile struct { + Uid int `json:"uid" xorm:"not null pk comment('关联userID') INT(20)"` + ArkidUid int `json:"arkid_uid" xorm:"not null default 0 comment('Arkid 用户ID') INT(20)"` + ParentUid int `json:"parent_uid" xorm:"not null default 0 comment('上级ID') INT(20)"` + ArkidToken string `json:"arkid_token" xorm:"not null default '' comment('token') VARCHAR(2000)"` + AvatarUrl string `json:"avatar_url" xorm:"not null default '' comment('头像URL') VARCHAR(2000)"` + CustomInviteCode string `json:"custom_invite_code" xorm:"not null default '' comment('邀请码(自定义)') VARCHAR(16)"` + InviteCode string `json:"invite_code" xorm:"not null default '' comment('邀请码(系统)') VARCHAR(16)"` + Gender int `json:"gender" xorm:"not null default 2 comment('性别0女,1男,2未知') TINYINT(1)"` + Birthday int `json:"birthday" xorm:"not null default 0 comment('出生日期') INT(10)"` + AccWxId string `json:"acc_wx_id" xorm:"not null default '' comment('账户_微信id') VARCHAR(50)"` + AccWxOpenid string `json:"acc_wx_openid" xorm:"not null default '' comment('账户_微信openid') VARCHAR(80)"` + AccTaobaoNickname string `json:"acc_taobao_nickname" xorm:"not null default '' comment('淘宝昵称') VARCHAR(50)"` + AccTaobaoAuthTime int64 `json:"acc_taobao_auth_time" xorm:"not null default 0 comment('淘宝授权备案时间') BIGINT(11)"` + AccTaobaoShareId int64 `json:"acc_taobao_share_id" xorm:"not null default 0 comment('淘宝分享relationId,') index BIGINT(12)"` + AccTaobaoSelfId int64 `json:"acc_taobao_self_id" xorm:"not null default 0 comment('淘宝自购specialId') index BIGINT(12)"` + AccJdSelfId string `json:"acc_jd_self_id" xorm:"not null default '' comment('京东自购ID') index VARCHAR(50)"` + AccJdShareId string `json:"acc_jd_share_id" xorm:"not null default '' comment('京东分享ID') index VARCHAR(50)"` + AccJdFreeId string `json:"acc_jd_free_id" xorm:"not null default '' comment('京东新人免单ID') VARCHAR(50)"` + AccSuningSelfId string `json:"acc_suning_self_id" xorm:"not null default '' comment('苏宁自购ID') index VARCHAR(50)"` + AccSuningShareId string `json:"acc_suning_share_id" xorm:"not null default '' comment('苏宁分享ID') index VARCHAR(50)"` + AccSuningFreeId string `json:"acc_suning_free_id" xorm:"not null default '' comment('苏宁新人免单ID') VARCHAR(50)"` + AccPddSelfId string `json:"acc_pdd_self_id" xorm:"not null default '' comment('拼多多自购ID') index VARCHAR(50)"` + AccPddShareId string `json:"acc_pdd_share_id" xorm:"not null default '' comment('拼多多分享ID') index VARCHAR(50)"` + AccPddFreeId string `json:"acc_pdd_free_id" xorm:"not null default '' comment('拼多多新人免单ID') VARCHAR(50)"` + AccPddBind int `json:"acc_pdd_bind" xorm:"not null default 0 comment('拼多多是否授权绑定') TINYINT(1)"` + AccVipSelfId string `json:"acc_vip_self_id" xorm:"not null default '' comment('唯品会自购ID') index VARCHAR(50)"` + AccVipShareId string `json:"acc_vip_share_id" xorm:"not null default '' comment('唯品会分享ID') index VARCHAR(50)"` + AccVipFreeId string `json:"acc_vip_free_id" xorm:"not null default '' comment('唯品会新人免单ID') VARCHAR(50)"` + AccKaolaSelfId string `json:"acc_kaola_self_id" xorm:"not null default '' comment('考拉自购ID') index VARCHAR(50)"` + AccKaolaShareId string `json:"acc_kaola_share_id" xorm:"not null default '' comment('考拉分享ID') index VARCHAR(50)"` + AccKaolaFreeId string `json:"acc_kaola_free_id" xorm:"not null default '' comment('考拉新人免单ID') VARCHAR(50)"` + AccDuomaiShareId int64 `json:"acc_duomai_share_id" xorm:"not null pk default 0 comment('多麦联盟分享ID') BIGINT(12)"` + AccAlipay string `json:"acc_alipay" xorm:"not null default '' comment('支付宝账号') VARCHAR(50)"` + AccAlipayRealName string `json:"acc_alipay_real_name" xorm:"not null default '' comment('支付宝账号真实姓名') VARCHAR(50)"` + CertTime int `json:"cert_time" xorm:"not null default 0 comment('认证时间') INT(10)"` + CertName string `json:"cert_name" xorm:"not null default '' comment('证件上名字,也是真实姓名') VARCHAR(50)"` + CertNum string `json:"cert_num" xorm:"not null default '' comment('证件号码') VARCHAR(50)"` + CertState int `json:"cert_state" xorm:"not null default 0 comment('认证状态(0为未认证,1为认证中,2为已认证,3为认证失败)') TINYINT(1)"` + FinCommission string `json:"fin_commission" xorm:"not null default 0.0000 comment('累计佣金') DECIMAL(10,4)"` + FinValid string `json:"fin_valid" xorm:"not null default 0.0000 comment('可用余额,fin=>finance财务') DECIMAL(10,4)"` + FinInvalid string `json:"fin_invalid" xorm:"not null default 0.0000 comment('不可用余额,冻结余额') DECIMAL(10,4)"` + FinSelfOrderCount int `json:"fin_self_order_count" xorm:"not null default 0 comment('自购订单数,包括未完成') INT(11)"` + FinSelfOrderCountDone int `json:"fin_self_order_count_done" xorm:"not null default 0 comment('自购已完成订单') INT(11)"` + FinSelfRebate float32 `json:"fin_self_rebate" xorm:"not null default 0.000000 comment('累积自购获得返利金额') FLOAT(14,6)"` + FinTotal float32 `json:"fin_total" xorm:"not null default 0.000000 comment('累计总收益') FLOAT(14,6)"` + Lat float32 `json:"lat" xorm:"not null default 0.000000 comment('纬度') FLOAT(15,6)"` + Lng float32 `json:"lng" xorm:"not null default 0.000000 comment('经度') FLOAT(15,6)"` + Memo string `json:"memo" xorm:"not null default '' comment('用户简述备注') VARCHAR(2048)"` + IsNew int `json:"is_new" xorm:"not null default 1 comment('是否是新用户') TINYINT(1)"` + IsVerify int `json:"is_verify" xorm:"not null default 0 comment('是否有效会员') TINYINT(1)"` + IsOrdered int `json:"is_ordered" xorm:"not null default 0 comment('是否已完成首单(0否,1是)') TINYINT(1)"` + FromWay string `json:"from_way" xorm:"not null default '' comment('注册来源: +no_captcha_phone:免验证码手机号注册; +manual_phone:手动手机验证码注册; +wx:微信授权; +wx_mp:小程序授权; +wx_pub:公众号授权; +wx_bind_phone:微信注册绑定手机号; +admin:管理员添加;taobao_bind_phone:淘宝注册绑定手机号,apple_bind_phone:苹果注册绑定手机号') VARCHAR(16)"` + HidOrder int `json:"hid_order" xorm:"not null default 0 comment('隐藏订单') TINYINT(3)"` + HidContact int `json:"hid_contact" xorm:"not null default 0 comment('隐藏联系方式') TINYINT(4)"` + NewMsgNotice int `json:"new_msg_notice" xorm:"not null default 1 comment('新消息通知') TINYINT(1)"` + WxAccount string `json:"wx_account" xorm:"not null default '' comment('微信号') VARCHAR(100)"` + WxQrcode string `json:"wx_qrcode" xorm:"not null default '' comment('微信二维码') VARCHAR(100)"` + ThirdPartyTaobaoOid string `json:"third_party_taobao_oid" xorm:"not null default '' comment('淘宝第三方登录openID') VARCHAR(100)"` + ThirdPartyTaobaoSid string `json:"third_party_taobao_sid" xorm:"not null default '' comment('淘宝第三方登录sID') VARCHAR(255)"` + ThirdPartyTaobaoAcctoken string `json:"third_party_taobao_acctoken" xorm:"not null default '' comment('淘宝第三方登录topaccesstoken') VARCHAR(100)"` + ThirdPartyTaobaoAuthcode string `json:"third_party_taobao_authcode" xorm:"not null default '' comment('淘宝第三方登录topAuthCode') VARCHAR(100)"` + ThirdPartyAppleToken string `json:"third_party_apple_token" xorm:"not null default '' comment('苹果第三方登录token') VARCHAR(1024)"` + ThirdPartyQqAccessToken string `json:"third_party_qq_access_token" xorm:"not null default '' comment('QQ第三方登录access_token') VARCHAR(255)"` + ThirdPartyQqExpiresIn string `json:"third_party_qq_expires_in" xorm:"not null default '' comment('QQ第三方登录expires_in(剩余时长)') VARCHAR(255)"` + ThirdPartyQqOpenid string `json:"third_party_qq_openid" xorm:"not null default '' comment('QQ第三方登陆openid(不变,用于认证)') VARCHAR(255)"` + ThirdPartyQqUnionid string `json:"third_party_qq_unionid" xorm:"not null default '' comment('QQ第三方登陆unionid') VARCHAR(255)"` + ThirdPartyWechatExpiresIn string `json:"third_party_wechat_expires_in" xorm:"not null default '' comment('微信第三方登录expires_in(剩余时长)') VARCHAR(255)"` + ThirdPartyWechatOpenid string `json:"third_party_wechat_openid" xorm:"not null default '' comment('微信第三方登陆openid(不变,用于认证)') VARCHAR(255)"` + ThirdPartyWechatUnionid string `json:"third_party_wechat_unionid" xorm:"not null default '' comment('微信第三方登陆unionid') VARCHAR(255)"` + ThirdPartyWechatMiniOpenid string `json:"third_party_wechat_mini_openid" xorm:"not null default '' comment('微信小程序登录open_id') VARCHAR(255)"` + ThirdPartyWechatH5Openid string `json:"third_party_wechat_h5_openid" xorm:"not null default '' comment('微信H5登录open_id') VARCHAR(255)"` + FreeRemainTime int `json:"free_remain_time" xorm:"not null default 0 comment('免单剩余次数') INT(11)"` + FreeCumulativeTime int `json:"free_cumulative_time" xorm:"not null default 0 comment('免单累计次数') INT(11)"` + IsDelete int `json:"is_delete" xorm:"not null default 0 comment('是否已删除') TINYINT(1)"` + UpdateAt time.Time `json:"update_at" xorm:"updated not null default CURRENT_TIMESTAMP comment('更新时间') TIMESTAMP"` +} diff --git a/app/db/model/user_relate.go b/app/db/model/user_relate.go new file mode 100644 index 0000000..375562f --- /dev/null +++ b/app/db/model/user_relate.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type UserRelate struct { + Id int64 `json:"id" xorm:"pk autoincr comment('主键') BIGINT(10)"` + ParentUid int `json:"parent_uid" xorm:"not null default 0 comment('上级会员ID') unique(idx_union_u_p_id) INT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('关联UserID') unique(idx_union_u_p_id) INT(20)"` + Level int `json:"level" xorm:"not null default 1 comment('推广等级(1直属,大于1非直属)') INT(10)"` + InviteTime time.Time `json:"invite_time" xorm:"not null default CURRENT_TIMESTAMP comment('邀请时间') TIMESTAMP"` +} diff --git a/app/db/model/user_tag.go b/app/db/model/user_tag.go new file mode 100644 index 0000000..19b3d8f --- /dev/null +++ b/app/db/model/user_tag.go @@ -0,0 +1,13 @@ +package model + +import ( + "time" +) + +type UserTag struct { + TagId int `json:"tag_id" xorm:"not null pk autoincr INT(10)"` + TagName string `json:"tag_name" xorm:"not null default '' comment('tag名') index VARCHAR(16)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID,uid为0表示标签本身,用于标签管理') index INT(11)"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(80)"` + CreateAt time.Time `json:"create_at" xorm:"not null default CURRENT_TIMESTAMP comment('创建时间') TIMESTAMP"` +} diff --git a/app/db/model/user_virtual_amount.go b/app/db/model/user_virtual_amount.go new file mode 100644 index 0000000..73f066c --- /dev/null +++ b/app/db/model/user_virtual_amount.go @@ -0,0 +1,8 @@ +package model + +type UserVirtualAmount struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"index INT(11)"` + CoinId int `json:"coin_id" xorm:"INT(11)"` + Amount string `json:"amount" xorm:"DECIMAL(16,6)"` +} diff --git a/app/db/model/user_virtual_assets.go b/app/db/model/user_virtual_assets.go new file mode 100644 index 0000000..22eaa2c --- /dev/null +++ b/app/db/model/user_virtual_assets.go @@ -0,0 +1,8 @@ +package model + +type UserVirtualAssets struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Uid int `json:"uid" xorm:"default 0 comment('用户id') INT(11)"` + Integral string `json:"integral" xorm:"default 0.0000 comment('用户积分') DECIMAL(12,4)"` + BlockIcons string `json:"block_icons" xorm:"default 0.0000 comment('区块币') DECIMAL(12,4)"` +} diff --git a/app/db/model/user_virtual_coin_flow.go b/app/db/model/user_virtual_coin_flow.go new file mode 100644 index 0000000..2d48b87 --- /dev/null +++ b/app/db/model/user_virtual_coin_flow.go @@ -0,0 +1,21 @@ +package model + +import ( + "time" +) + +type UserVirtualCoinFlow struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Uid int `json:"uid" xorm:"not null comment('用户id') index INT(11)"` + CoinId int `json:"coin_id" xorm:"not null comment('虚拟币id') INT(11)"` + Direction int `json:"direction" xorm:"not null comment('方向:1收入 2支出') TINYINT(255)"` + Title string `json:"title" xorm:"comment('标题') VARCHAR(255)"` + OrdId string `json:"ord_id" xorm:"comment('相关的订单id') VARCHAR(255)"` + Amout string `json:"amout" xorm:"not null comment('变更数量') DECIMAL(16,6)"` + BeforeAmout string `json:"before_amout" xorm:"not null comment('变更前数量') DECIMAL(16,6)"` + AfterAmout string `json:"after_amout" xorm:"not null comment('变更后数量') DECIMAL(16,6)"` + SysFee string `json:"sys_fee" xorm:"not null default 0.000000 comment('手续费') DECIMAL(16,6)"` + CreateTime time.Time `json:"create_time" xorm:"created default 'CURRENT_TIMESTAMP' comment('创建时间') DATETIME"` + TransferType int `json:"transfer_type" xorm:"comment('转账类型:1全球分红,2管理员修改,3消费,4退回,5虚拟币兑换') TINYINT(100)"` + CoinIdTo int `json:"coin_id_to" xorm:"not null default 0 comment('兑换时目标币种id') INT(11)"` +} diff --git a/app/db/model/virtual_coin.go b/app/db/model/virtual_coin.go new file mode 100644 index 0000000..3baaa55 --- /dev/null +++ b/app/db/model/virtual_coin.go @@ -0,0 +1,17 @@ +package model + +type VirtualCoin struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"` + ExchangeRatio string `json:"exchange_ratio" xorm:"not null comment('兑换比例(与金额)') DECIMAL(5,2)"` + IsUse int `json:"is_use" xorm:"comment('是否开启:0否 1是') TINYINT(1)"` + CanExchange string `json:"can_exchange" xorm:"comment('能兑换的虚拟币id和手续费列表json') VARCHAR(255)"` + CanExchangeMoney int `json:"can_exchange_money" xorm:"not null default 0 comment('现金能否兑换:0否 1是') TINYINT(1)"` + IsBlock int `json:"is_block" xorm:"not null default 0 comment('是否区块币:0否 1是') TINYINT(1)"` + FunctionType string `json:"function_type" xorm:"comment('功能类型') VARCHAR(255)"` + CanCny int `json:"can_cny" xorm:"not null default 0 comment('是否能兑换余额:0否 1是') TINYINT(1)"` + CanTransfer int `json:"can_transfer" xorm:"not null default 0 comment('是否能支持转账:0否 1是') TINYINT(1)"` + CanBackout int `json:"can_backout" xorm:"not null default 0 comment('是否能支持转账撤回:0否 1是') TINYINT(1)"` + LimitLevelTransfer string `json:"limit_level_transfer" xorm:"default '' comment('能支持转账的用户等级') VARCHAR(600)"` + LimitLevelBackout string `json:"limit_level_backout" xorm:"comment('能支持撤回的用户等级') VARCHAR(600)"` +} diff --git a/app/db/model/virtual_coin_relate.go b/app/db/model/virtual_coin_relate.go new file mode 100644 index 0000000..a629564 --- /dev/null +++ b/app/db/model/virtual_coin_relate.go @@ -0,0 +1,12 @@ +package model + +type VirtualCoinRelate struct { + Id int64 `json:"id" xorm:"pk autoincr BIGINT(20)"` + Oid int64 `json:"oid" xorm:"not null default 0 comment('订单号') index unique(IDX_ORD) BIGINT(20)"` + Uid int `json:"uid" xorm:"not null default 0 comment('用户ID') unique(IDX_ORD) index INT(10)"` + CoinId int `json:"coin_id" xorm:"comment('虚拟币id') INT(11)"` + Amount string `json:"amount" xorm:"not null default 0.000000 comment('数量') DECIMAL(16,6)"` + Pvd string `json:"pvd" xorm:"not null default '' comment('供应商taobao,jd,pdd,vip,suning,kaola,mall_goods,group_buy') index VARCHAR(255)"` + CreateAt int `json:"create_at" xorm:"not null default 0 comment('订单创建时间') index INT(10)"` + Level int `json:"level" xorm:"not null default 0 comment('0自购 1直推 大于1:间推') INT(10)"` +} diff --git a/app/e/code.go b/app/e/code.go new file mode 100644 index 0000000..cc8be46 --- /dev/null +++ b/app/e/code.go @@ -0,0 +1,236 @@ +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 + + // 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_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: "校验失败", +} diff --git a/app/e/error.go b/app/e/error.go new file mode 100644 index 0000000..2564174 --- /dev/null +++ b/app/e/error.go @@ -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 +} diff --git a/app/e/msg.go b/app/e/msg.go new file mode 100644 index 0000000..ed226ae --- /dev/null +++ b/app/e/msg.go @@ -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() +} diff --git a/app/e/set_cache.go b/app/e/set_cache.go new file mode 100644 index 0000000..45337a1 --- /dev/null +++ b/app/e/set_cache.go @@ -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} +} diff --git a/app/lib/alipay/api.go b/app/lib/alipay/api.go new file mode 100644 index 0000000..9b54aac --- /dev/null +++ b/app/lib/alipay/api.go @@ -0,0 +1,262 @@ +package alipay + +import ( + "applet/app/cfg" + "applet/app/pay/md" + "applet/app/utils/logx" + "fmt" + + "github.com/iGoogle-ink/gopay" + "github.com/iGoogle-ink/gopay/alipay" +) + +// TradeAppPay is 支付宝APP支付 +// 抖音头条小程序使用APP调起 +func TradeAppPay(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS string, paySet *md.PayData) (string, error) { + //初始化支付宝客户端 + // appID 是在支付宝申请的APPID + // priKey 是支付宝私钥 + // subject 是支付订单的主题 + // orderID 是智莺这边生成的订单id + // amount 是付费金额 + // notiURL 通知地址url + // passback_params 回调通知参数 + + client := alipay.NewClient(appID, priKey, true) + client.DebugSwitch = gopay.DebugOn + //判断密钥的类型 + rsa_type := alipay.RSA2 + pkcs_type := alipay.PKCS1 + if RSA == "1" { + rsa_type = alipay.RSA + } + if PKCS == "1" { + pkcs_type = alipay.PKCS8 + } + if paySet.PayAliUseType == "1" { + rsa_type = alipay.RSA2 + pkcs_type = alipay.PKCS8 + } + //配置公共参数 + client.SetCharset("utf-8"). + SetSignType(rsa_type). + SetPrivateKeyType(pkcs_type) + if notiURL != "" { + client.SetNotifyUrl(notiURL) + } + //新支付宝支付 + if paySet.PayAliUseType == "1" { + appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAppCertSn) + fmt.Println("-应用-") + fmt.Println(appCertSN) + if err != nil { + fmt.Println(err) + return "", err + } + if appCertSN == "" { + fmt.Println(err) + return "", err + } + client.SetAppCertSN(appCertSN) + //aliPayRootCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayRootCertSn) + aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6" + client.SetAliPayRootCertSN(aliPayRootCertSN) + aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayrsaPublicKey) + fmt.Println("-公钥-") + fmt.Println(aliPayPublicCertSN) + + if err != nil { + fmt.Println(err) + return "", err + } + if aliPayPublicCertSN == "" { + fmt.Println(err) + return "", err + } + client.SetAliPayPublicCertSN(aliPayPublicCertSN) + } + fmt.Println(client) + //请求参数 + body := make(gopay.BodyMap) + body.Set("subject", subject) + body.Set("body", subject) + body.Set("out_trade_no", orderID) + body.Set("total_amount", amount) + body.Set("timeout_express", "30m") + + // body.Set("passback_params", orderID) + //手机APP支付参数请求 + payParam, err := client.TradeAppPay(body) + if err != nil { + return "", logx.Warn(err) + } + return payParam, nil +} + +// TradeAppPay is 支付宝H5支付 +func TradeWapPay(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS, page_url string, paySet *md.PayData) (string, error) { + fmt.Println("notifyURL is:>>>>>>>>>>", notiURL) + //aliPayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wn1sU/8Q0rYLlZ6sq3enrPZw2ptp6FecHR2bBFLjJ+sKzepROd0bKddgj+Mr1ffr3Ej78mLdWV8IzLfpXUi945DkrQcOUWLY0MHhYVG2jSs/qzFfpzmtut2Cl2TozYpE84zom9ei06u2AXLMBkU6VpznZl+R4qIgnUfByt3Ix5b3h4Cl6gzXMAB1hJrrrCkq+WvWb3Fy0vmk/DUbJEz8i8mQPff2gsHBE1nMPvHVAMw1GMk9ImB4PxucVek4ZbUzVqxZXphaAgUXFK2FSFU+Q+q1SPvHbUsjtIyL+cLA6H/6ybFF9Ffp27Y14AHPw29+243/SpMisbGcj2KD+evBwIDAQAB" + privateKey := priKey + //判断密钥的类型 + rsa_type := alipay.RSA2 + pkcs_type := alipay.PKCS1 + if RSA == "1" { + rsa_type = alipay.RSA + } + if PKCS == "1" { + pkcs_type = alipay.PKCS8 + } + if paySet.PayAliUseType == "1" { + rsa_type = alipay.RSA2 + pkcs_type = alipay.PKCS8 + } + //初始化支付宝客户端 + // appId:应用ID + // privateKey:应用秘钥 + // isProd:是否是正式环境 + client := alipay.NewClient(appID, privateKey, true) + //配置公共参数 + client.SetCharset("utf-8"). + SetSignType(rsa_type). + SetPrivateKeyType(pkcs_type). + SetReturnUrl(page_url). + SetNotifyUrl(notiURL) + //新支付宝支付 + if paySet.PayAliUseType == "1" { + appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAppCertSn) + if err != nil { + fmt.Println("appCertSn_err:>>>>>>>>", err) + return "", err + } + if appCertSN == "" { + fmt.Println(err) + return "", err + } + client.SetAppCertSN(appCertSN) + //aliPayRootCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayRootCertSn) + //if err != nil { + // + // fmt.Println("rootcertsn_err:>>>>>>>>>>", err) + // fmt.Println("rootcertsn_err:>>>>>>>>>>", cfg.WxappletFilepath.URL) + // fmt.Println("rootcertsn_err:>>>>>>>>>>", paySet.PayAlipayRootCertSn) + // return "", err + //} + //if aliPayRootCertSN == "" { + // fmt.Println(err) + // return "", err + //} + aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6" + client.SetAliPayRootCertSN(aliPayRootCertSN) + aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayrsaPublicKey) + if err != nil { + fmt.Println("publicCertSn_err:>>>>>>>>>>>", err) + return "", err + } + if aliPayPublicCertSN == "" { + fmt.Println(err) + return "", err + } + client.SetAliPayPublicCertSN(aliPayPublicCertSN) + } + //请求参数 + body := make(gopay.BodyMap) + body.Set("subject", subject) + body.Set("out_trade_no", orderID) + // quit_url is 用户付款中途退出返回商户网站的地址 + body.Set("quit_url", notiURL) + body.Set("total_amount", amount) + // product_code is 销售产品码,商家和支付宝签约的产品码 + body.Set("product_code", "QUICK_WAP_WAY") + //手机网站支付请求 + payUrl, err := client.TradeWapPay(body) + if err != nil { + return "", logx.Warn(err) + + } + return payUrl, nil +} + +// TradeAppPay is 支付宝小程序本身支付 +func TradeCreate(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS string, paySet *md.PayData) (*alipay.TradeCreateResponse, error) { + //aliPayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wn1sU/8Q0rYLlZ6sq3enrPZw2ptp6FecHR2bBFLjJ+sKzepROd0bKddgj+Mr1ffr3Ej78mLdWV8IzLfpXUi945DkrQcOUWLY0MHhYVG2jSs/qzFfpzmtut2Cl2TozYpE84zom9ei06u2AXLMBkU6VpznZl+R4qIgnUfByt3Ix5b3h4Cl6gzXMAB1hJrrrCkq+WvWb3Fy0vmk/DUbJEz8i8mQPff2gsHBE1nMPvHVAMw1GMk9ImB4PxucVek4ZbUzVqxZXphaAgUXFK2FSFU+Q+q1SPvHbUsjtIyL+cLA6H/6ybFF9Ffp27Y14AHPw29+243/SpMisbGcj2KD+evBwIDAQAB" + privateKey := priKey + rsa_type := alipay.RSA2 + pkcs_type := alipay.PKCS1 + if RSA == "1" { + rsa_type = alipay.RSA + } + if PKCS == "1" { + pkcs_type = alipay.PKCS8 + } + if paySet.PayAliUseType == "1" { + rsa_type = alipay.RSA2 + pkcs_type = alipay.PKCS8 + } + //初始化支付宝客户端 + // appId:应用ID + // privateKey:应用私钥,支持PKCS1和PKCS8 + // isProd:是否是正式环境 + client := alipay.NewClient(appID, privateKey, true) + //配置公共参数 + client.SetCharset("utf-8"). + SetSignType(rsa_type). + SetPrivateKeyType(pkcs_type). + SetNotifyUrl(notiURL) + if paySet.PayAliUseType == "1" { + appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAppCertSn) + if err != nil { + fmt.Println(err) + return nil, err + } + if appCertSN == "" { + fmt.Println(err) + return nil, err + } + client.SetAppCertSN(appCertSN) + //aliPayRootCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayRootCertSn) + //if err != nil { + // fmt.Println(err) + // return nil, err + //} + //if aliPayRootCertSN == "" { + // fmt.Println(err) + // return nil, err + //} + aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6" + client.SetAliPayRootCertSN(aliPayRootCertSN) + aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayrsaPublicKey) + if err != nil { + fmt.Println(err) + return nil, err + } + if aliPayPublicCertSN == "" { + fmt.Println(err) + return nil, err + } + client.SetAliPayPublicCertSN(aliPayPublicCertSN) + } + //请求参数 + body := make(gopay.BodyMap) + body.Set("subject", subject) + // 支付宝小程序支付时 buyer_id 为必传参数,需要提前获取,获取方法如下两种 + // 1、alipay.SystemOauthToken() 返回取值:rsp.SystemOauthTokenResponse.UserId + // 2、client.SystemOauthToken() 返回取值:aliRsp.SystemOauthTokenResponse.UserId + buyer_id, err := client.SystemOauthToken(body) + if err != nil { + return nil, logx.Warn(err) + } + body.Set("buyer_id", buyer_id) + body.Set("out_trade_no", orderID) + body.Set("total_amount", amount) + //创建订单 + aliRsp, err := client.TradeCreate(body) + + if err != nil { + return nil, logx.Warn(err) + } + logx.Warn("aliRsp:", *aliRsp) + logx.Warn("aliRsp.TradeNo:", aliRsp.Response.TradeNo) + return aliRsp, nil + +} diff --git a/app/lib/arkid/api.go b/app/lib/arkid/api.go new file mode 100644 index 0000000..685a0bc --- /dev/null +++ b/app/lib/arkid/api.go @@ -0,0 +1,148 @@ +package arkid + +import ( + "encoding/json" + "errors" + "fmt" + + "applet/app/cfg" + "applet/app/utils" + "applet/app/utils/cache" + "applet/app/utils/logx" +) + +func arkidLogin(args map[string]interface{}) ([]byte, error) { + url := cfg.ArkID.Url + "/siteapi/v1/ucenter/login/" + b, err := json.Marshal(args) + if err != nil { + return nil, logx.Error(err) + } + var d []byte + d, err = utils.CurlPost(url, b, nil) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func arkidLogout(token string) ([]byte, error) { + // fmt.Println(cfg.ArkID.Url) + url := cfg.ArkID.Url + "/siteapi/v1/revoke/token/" + h := map[string]string{"authorization": fmt.Sprintf("token %s", token)} + d, err := utils.CurlPost(url, "", h) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func arkidUserInfo(token string) ([]byte, error) { + url := cfg.ArkID.Url + "/siteapi/v1/auth/token/" + h := map[string]string{"authorization": fmt.Sprintf("token %s", token)} + d, err := utils.CurlGet(url, h) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func arkidRegister(args map[string]interface{}) ([]byte, error) { + url := cfg.ArkID.Url + "/siteapi/oneid/user/" + b, err := json.Marshal(args) + if err != nil { + return nil, logx.Error(err) + } + admin, err := getArkIDAdmin() + if err != nil { + return nil, logx.Error(err) + } + h := map[string]string{"authorization": fmt.Sprintf("token %s", admin.Token)} + var d []byte + d, err = utils.CurlPost(url, b, h) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func arkidAppAccessWhiteList(args map[string]interface{}, permName string) ([]byte, error) { + if permName == "" { + return nil, errors.New("The perm_name arg must required") + } + path := fmt.Sprintf("/siteapi/oneid/perm/%s/owner/", permName) + url := cfg.ArkID.Url + path + b, err := json.Marshal(args) + if err != nil { + return nil, logx.Error(err) + } + admin, err := getArkIDAdmin() + if err != nil { + return nil, logx.Error(err) + } + // fmt.Println(admin.Token) + h := map[string]string{"authorization": fmt.Sprintf("token %s", admin.Token)} + var d []byte + d, err = utils.CurlPatch(url, b, h) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func arkidUserDelete(username string) ([]byte, error) { + if username == "" { + return nil, errors.New("The username arg must required") + } + path := fmt.Sprintf("/siteapi/oneid/user/%s/", username) + url := cfg.ArkID.Url + path + admin, err := getArkIDAdmin() + if err != nil { + return nil, logx.Error(err) + } + // fmt.Println(admin.Token) + h := map[string]string{"authorization": fmt.Sprintf("token %s", admin.Token)} + var d []byte + d, err = utils.CurlDelete(url, nil, h) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func arkidUserUpdate(username string, args map[string]interface{}) ([]byte, error) { + if username == "" { + return nil, errors.New("The username arg must required") + } + b, err := json.Marshal(args) + if err != nil { + return nil, logx.Error(err) + } + path := fmt.Sprintf("/siteapi/oneid/user/%s/", username) + url := cfg.ArkID.Url + path + var admin *ArkIDUser + admin, err = getArkIDAdmin() + if err != nil { + return nil, logx.Error(err) + } + h := map[string]string{"authorization": fmt.Sprintf("token %s", admin.Token)} + d, err := utils.CurlPatch(url, b, h) + if err != nil { + return nil, logx.Error(err) + } + return d, nil +} + +func getArkIDAdmin() (*ArkIDUser, error) { + c, err := cache.Bytes(cache.Get(ARKID_ADMIN_TOKEN)) + if err != nil { + logx.Error(err) + } + if c != nil && err == nil { + admin := new(ArkIDUser) + if err = json.Unmarshal(c, admin); err != nil { + return admin, err + } + return admin, nil + } + return Init() +} diff --git a/app/lib/arkid/base.go b/app/lib/arkid/base.go new file mode 100644 index 0000000..fff9511 --- /dev/null +++ b/app/lib/arkid/base.go @@ -0,0 +1,6 @@ +package arkid + +const ( + BASE_URL = "http://k8s.arkid.izhim.cn" + ARKID_ADMIN_TOKEN = "arkid_admin_token" +) diff --git a/app/lib/arkid/init.go b/app/lib/arkid/init.go new file mode 100644 index 0000000..060537c --- /dev/null +++ b/app/lib/arkid/init.go @@ -0,0 +1,24 @@ +package arkid + +import ( + "applet/app/cfg" + "applet/app/utils" + "applet/app/utils/cache" +) + +// Init is cache token to redis +func Init() (*ArkIDUser, error) { + arkidsdk := NewArkID() + arkadmin := new(ArkIDUser) + err := arkidsdk.SelectFunction("arkid_login").WithArgs(RequestBody{ + Username: cfg.ArkID.Admin, + Password: cfg.ArkID.AdminPassword, + }).Result(arkadmin) + if err != nil { + panic(err) + } + + // token 默认30天过期 + cache.SetEx(ARKID_ADMIN_TOKEN, utils.Serialize(arkadmin), 2592000) + return arkadmin, err +} diff --git a/app/lib/arkid/model.go b/app/lib/arkid/model.go new file mode 100644 index 0000000..82882ab --- /dev/null +++ b/app/lib/arkid/model.go @@ -0,0 +1,62 @@ +package arkid + +type ArkIDUser struct { + Token string `json:"token"` + UserID int `json:"user_id"` + Username string `json:"username"` + Name string `json:"name"` + Email string `json:"email"` + Mobile string `json:"mobile"` + EmployeeNumber string `json:"employee_number"` + Gender int `json:"gender"` + Perms []string `json:"perms"` + Avatar string `json:"avatar"` + Roles []string `json:"roles"` + PrivateEmail string `json:"private_email"` + Position string `json:"position"` + IsSettled bool `json:"is_settled"` + IsManager bool `json:"is_manager"` + IsAdmin bool `json:"is_admin"` + IsExternUser bool `json:"is_extern_user"` + OriginVerbose string `json:"origin_verbose"` + RequireResetPassword bool `json:"require_reset_password"` + HasPassword bool `json:"has_password"` +} + +type RequestBody struct { + Token string `json:"token,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + User struct { + Avatar string `json:"avatar,omitempty"` + Email string `json:"email,omitempty"` + EmployeeNumber string `json:"employee_number,omitempty"` + Gender int `json:"gende,omitemptyr"` + Mobile string `json:"mobile,omitempty"` + Name string `json:"name,omitempty"` + Position string `json:"position,omitempty"` + PrivateEmail string `json:"private_email,omitempty"` + Username string `json:"username,omitempty"` + Depts interface{} `json:"depts,omitempty"` + Roles interface{} `json:"roles,omitempty"` + Nodes []interface{} `json:"nodes,omitempty"` + IsSettled bool `json:"is_settled,omitempty"` + Password string `json:"password,omitempty"` + RequireResetPassword bool `json:"require_reset_password,omitempty"` + HasPassword bool `json:"has_password,omitempty"` + } `json:"user,omitempty"` + NodeUids []string `json:"node_uids,omitempty"` + PermName string `json:"perm_name,omitempty"` + UserPermStatus []struct { + UID string `json:"uid,omitempty"` + Status int `json:"status,omitempty"` + } `json:"user_perm_status,omitempty"` +} + +type AppAccessWhiteListResult struct { + UserPermStatus []struct { + UID string `json:"uid"` + Status int `json:"status"` + } `json:"user_perm_status"` + NodePermStatus []interface{} `json:"node_perm_status"` +} diff --git a/app/lib/arkid/sdk.go b/app/lib/arkid/sdk.go new file mode 100644 index 0000000..59b45c2 --- /dev/null +++ b/app/lib/arkid/sdk.go @@ -0,0 +1,165 @@ +package arkid + +import ( + "applet/app/utils/cache" + "applet/app/utils/logx" + "encoding/json" + "errors" + "fmt" +) + +type SDK struct { + response []byte + fmap map[string]func(RequestBody) + fname string + err error +} + +//Init is init sdk +func (s *SDK) Init() { + s.fmap = make(map[string]func(RequestBody)) +} + +//SelectFunction is choose func +func (s *SDK) SelectFunction(fname string) *SDK { + s.fname = fname + return s +} + +//WithArgs is request args +func (s *SDK) WithArgs(r RequestBody) *SDK { + f := s.fmap[s.fname] + f(r) + return s +} + +//Result is result to p +func (s *SDK) Result(p interface{}) error { + if s.err != nil { + return s.err + } + if string(s.response) == "" { + return nil + } + if err := json.Unmarshal(s.response, p); err != nil { + return logx.Error(string(s.response), err) + } + return nil +} + +// Register is register func +func (s *SDK) Register(name string, f func(RequestBody)) { + s.fmap[name] = f +} + +//getAdmin arkid 用户的信息 ,主要是token +func (s *SDK) arkidLogin(r RequestBody) { + postData := map[string]interface{}{ + "username": r.Username, + "password": r.Password, + } + s.response, s.err = arkidLogin(postData) +} + +func (s *SDK) arkidRegister(r RequestBody) { + postData := map[string]interface{}{} + b, err := json.Marshal(r) + if err != nil { + s.err = err + } + if err := json.Unmarshal(b, &postData); err != nil { + s.err = err + } + s.response, s.err = arkidRegister(postData) +} + +func (s *SDK) arkidAppAccessWhiteList(r RequestBody) { + postData := map[string]interface{}{} + b, err := json.Marshal(r) + if err != nil { + s.err = err + } + if err := json.Unmarshal(b, &postData); err != nil { + s.err = err + } + s.response, s.err = arkidAppAccessWhiteList(postData, r.PermName) +} + +func (s *SDK) arkidUserInfo(r RequestBody) { + s.response, s.err = arkidUserInfo(r.Token) +} + +func (s *SDK) arkidUserDelete(r RequestBody) { + s.response, s.err = arkidUserDelete(r.Username) +} + +func (s *SDK) arkidUserUpdate(r RequestBody) { + postData := map[string]interface{}{} + b, err := json.Marshal(r.User) + if err != nil { + s.err = err + } + if err := json.Unmarshal(b, &postData); err != nil { + s.err = err + } + s.response, s.err = arkidUserUpdate(r.Username, postData) +} + +func (s *SDK) arkidLogout(r RequestBody) { + s.response, s.err = arkidLogout(r.Token) +} + +// NewArkID is con +func NewArkID() *SDK { + sdk := new(SDK) + sdk.Init() + sdk.Register("arkid_login", sdk.arkidLogin) + sdk.Register("arkid_register", sdk.arkidRegister) + sdk.Register("arkid_app_access_white_list", sdk.arkidAppAccessWhiteList) + sdk.Register("arkid_delete_user", sdk.arkidUserDelete) + sdk.Register("arkid_user_info", sdk.arkidUserInfo) + sdk.Register("arkid_user_update", sdk.arkidUserUpdate) + sdk.Register("arkid_logout", sdk.arkidLogout) + return sdk +} + +// GetArkIDUser is get arkid token if redis is existed unless send request to arkid +func GetArkIDUser(username string, MD5passowrd string) (*ArkIDUser, error) { + key := fmt.Sprintf("arkid_user_%s", username) + arkidUser := new(ArkIDUser) + c, err := cache.GetBytes(key) + if c != nil && err == nil { + if err := json.Unmarshal(c, arkidUser); err != nil { + return arkidUser, err + } + if arkidUser.Token == "" { + + return arkidUser, errors.New("Get Arkid User error, Token missing") + } + + return arkidUser, err + } + arkidSdk := NewArkID() + err = arkidSdk.SelectFunction("arkid_login").WithArgs(RequestBody{ + Username: username, + Password: MD5passowrd, + }).Result(arkidUser) + if arkidUser.Token == "" { + return arkidUser, errors.New("Get Arkid User error, Token missing") + } + // 缓存30天 + // cache.SetEx(key, utils.Serialize(arkidUser), 2592000) + return arkidUser, err +} + +// RegisterRollback is 注册时的错误回滚 +func RegisterRollback(username string) error { + sdk := NewArkID() + err := sdk.SelectFunction("arkid_delete_user").WithArgs(RequestBody{ + Username: username, + }).Result(nil) + if err != nil { + return err + } + return nil +} diff --git a/app/lib/auth/base.go b/app/lib/auth/base.go new file mode 100644 index 0000000..dfdc165 --- /dev/null +++ b/app/lib/auth/base.go @@ -0,0 +1,23 @@ +package auth + +import ( + "time" + + "github.com/dgrijalva/jwt-go" +) + +// TokenExpireDuration is jwt 过期时间 +const TokenExpireDuration = time.Hour * 4380 + +var Secret = []byte("zyos") + +// JWTUser 如果想要保存更多信息,都可以添加到这个结构体中 +type JWTUser struct { + UID int `json:"uid"` + Username string `json:"username"` + Phone string `json:"phone"` + AppName string `json:"app_name"` + MiniOpenID string `json:"mini_open_id"` // 小程序的open_id + MiniSK string `json:"mini_session_key"` // 小程序的session_key + jwt.StandardClaims +} diff --git a/app/lib/kd100/kd100.go b/app/lib/kd100/kd100.go new file mode 100644 index 0000000..97b423e --- /dev/null +++ b/app/lib/kd100/kd100.go @@ -0,0 +1,79 @@ +package kd100 + +import ( + "applet/app/utils" + "applet/app/utils/logx" + "crypto/tls" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" +) + +type kd100 struct { + key string //客户授权key + customer string //查询公司编号 +} + +const postUrl = "https://poll.kuaidi100.com/poll/query.do" + +func NewKd100(key, customer string) *kd100 { + return &kd100{ + key: key, + customer: customer, + } +} + +func (kd *kd100) Query(num, comCode string) (map[string]interface{}, error) { + paramData := make(map[string]string) + paramData["com"] = comCode //快递公司编码 + paramData["num"] = num //快递单号 + + str, _ := json.Marshal(paramData) + paramJson := string(str) + sign := kd.getSign(paramJson) + + // 解决 Go http请求报错x509 certificate signed by unknown authority 问题 + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + client := &http.Client{ + Timeout: 15 * time.Second, + Transport: tr, + } + + postRes, postErr := client.PostForm(postUrl, url.Values{"customer": {kd.customer}, "sign": {sign}, "param": {paramJson}}) + if postErr != nil { + _ = logx.Error(postErr) + return nil, errors.New(postErr.Error()) + } + postBody, err := ioutil.ReadAll(postRes.Body) + if err != nil { + _ = logx.Error(postErr) + return nil, errors.New("查询失败,请至快递公司官网自行查询") + } + + fmt.Println(string(postBody)) + + resp := make(map[string]interface{}) + err = json.Unmarshal(postBody, &resp) + if err != nil { + _ = logx.Error(postErr) + return nil, errors.New("查询失败,请至快递公司官网自行查询") + } + + defer func() { + _ = postRes.Body.Close() + }() + + return resp, nil +} + +func (kd *kd100) getSign(params string) string { + return strings.ToUpper(utils.Md5(params + kd.key + kd.customer)) +} diff --git a/app/lib/mob/api.go b/app/lib/mob/api.go new file mode 100644 index 0000000..e0c3b1c --- /dev/null +++ b/app/lib/mob/api.go @@ -0,0 +1,261 @@ +package mob + +import ( + "applet/app/db" + "applet/app/lib/sms" + "applet/app/lib/zhimeng" + "applet/app/utils" + "applet/app/utils/logx" + "bytes" + "crypto/cipher" + "crypto/des" + "crypto/md5" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "sort" + "time" + + "github.com/gin-gonic/gin" + "github.com/tidwall/gjson" +) + +const base string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + +// Mob is mob sdk +var Mob *SDK + +// MobMap is 每个站长都要有自己的mob 对象 +var MobMap map[string]*SDK + +//Init 初始化 +func Init() { + // 后续可能要传请求的上下文来获取对应的配置 + // mob 目前都是我们来管理每个站长的app 所以使用template 库 + //fmt.Println("Mob SDK init ....") + ch := make(chan struct{}) // 只是做信号标志的话 空struct 更省点资源 + MobMap = make(map[string]*SDK) + // 初始化 + for k, e := range db.DBs { + m, err := db.SysCfgGetOne(e, "third_app_push_set") + if err != nil { + logx.Warn(err) + fmt.Println(k + ":init mob err") + continue + } + key := gjson.Get(m.Val, "mobAppKey").String() + secret := gjson.Get(m.Val, "mobAppSecret").String() + if key == "" || secret == "" { + fmt.Println(k + ":mob no config") + continue + } + // fmt.Println(k, key, secret) + mob := new(SDK) + mob.AppKey = key + mob.AppSecret = secret + MobMap[k] = mob + fmt.Println(k + ":mob config success") + } + go func() { + ch <- struct{}{} + }() + + // 定时任务 + go func(MobMap map[string]*SDK, ch chan struct{}) { + <-ch + ticker := time.NewTicker(time.Duration(time.Second * 15)) + //每 15s 一次更新一次mob 配置 + for range ticker.C { + for k, e := range db.DBs { + if err := e.Ping(); err != nil { + logx.Info(err) + continue + } + m, err := db.SysCfgGetOne(e, "third_app_push_set") + if err != nil { + logx.Warn(err) + fmt.Println(k + ":init mob err") + continue + } + key := gjson.Get(m.Val, "mobAppKey").String() + secret := gjson.Get(m.Val, "mobAppSecret").String() + if key == "" || secret == "" { + fmt.Println(k + ":mob no config") + continue + } + // fmt.Println(k, key, secret) + mob := new(SDK) + mob.AppKey = key + mob.AppSecret = secret + MobMap[k] = mob + // fmt.Println(k + ":mob config success") + } + } + }(MobMap, ch) +} + +// GetMobSDK is 获取mob 的sdk +func GetMobSDK(mid string) (*SDK, error) { + selectDB := db.DBs[mid] + m, err := db.SysCfgGetOne(selectDB, "third_app_push_set") + if err != nil { + return nil, err + } + key := gjson.Get(m.Val, "mobAppKey").String() + secret := gjson.Get(m.Val, "mobAppSecret").String() + if key == "" || secret == "" { + return nil, fmt.Errorf("%s mob not config", mid) + } + + return &SDK{AppKey: key, AppSecret: secret}, nil +} + +// SDK is mob_push 的sdk +type SDK struct { + AppKey string + AppSecret string +} + +//MobFreeLogin is 秒验 +func (s *SDK) MobFreeLogin(args map[string]interface{}) (string, error) { + var url string = "http://identify.verify.mob.com/auth/auth/sdkClientFreeLogin" + // https://www.mob.com/wiki/detailed/?wiki=miaoyan_for_fuwuduan_mianmifuwuduanjieru&id=78 + //加appkey + args["appkey"] = s.AppKey + //加签名 + args["sign"] = generateSign(args, s.AppSecret) + b, err := json.Marshal(args) + if err != nil { + return "", logx.Warn(err) + } + // 发送请求 + respBody, err := httpPostBody(url, b) + if err != nil { + return "", logx.Warn(err) + } + // 反序列化 + ret := struct { + Status int `json:"status"` + Error string `json:"error"` + Res interface{} `json:"res"` + }{} + // 要拿 ret 里面 Res 再解密 + if err := json.Unmarshal(respBody, &ret); err != nil { + return "", logx.Warn(err) + } + //fmt.Println(ret) + // ret里面的Res 反序列化为结构体 + res := struct { + IsValid int `json:"isValid"` + Phone string `json:"phone"` + }{} + // 判断是否返回正确 状态码 + if ret.Status == 200 { + decode, _ := base64Decode([]byte(ret.Res.(string))) + decr, _ := desDecrypt(decode, []byte(s.AppSecret)[0:8]) + if err := json.Unmarshal(decr, &res); err != nil { + return "", logx.Warn(err) + } + } + // 有效则拿出res 里的电话号码 + if res.IsValid == 1 { + return res.Phone, nil + } + // Status 不等于200 则返回空 + return "", fmt.Errorf("Mob error , status code %v ", ret.Status) +} + +// MobSMS is mob 的短信验证 +func (s *SDK) MobSMS(c *gin.Context, args map[string]interface{}) (bool, error) { + // mob 的短信验证 + // https://www.mob.com/wiki/detailed/?wiki=SMSSDK_for_yanzhengmafuwuduanxiaoyanjiekou&id=23 + url := "https://webapi.sms.mob.com/sms/verify" + //加appkey + args["appkey"] = s.AppKey + //fmt.Println(args) + // 发送请求 + respBody, err := utils.CurlPost(url, args, nil) + if err != nil { + return false, logx.Warn(err) + } + //fmt.Println(string(respBody)) + code := gjson.GetBytes(respBody, "status").Int() + if code != 200 { + return false, nil + } + if code == 468 { + return false, errors.New("验证码错误") + } + // TODO 成功后扣费暂时先接旧智盟 + sdk, err := sms.NewZhimengSMS(c).SelectFunction("deduction_doing").WithSMSArgs(map[string]interface{}{ + "mobile": args["phone"], + "getmsg": "1", + }).Result() + if err != nil { + return false, logx.Warn(err) + } + zr := sdk.ToInterface().(string) + if zr == "1" { + logx.Infof("旧智盟扣费成功 appkey %s", zhimeng.SMS_APP_KEY) + } + return true, nil +} + +func pkcs5UnPadding(origData []byte) []byte { + length := len(origData) + // 去掉最后一个字节 unpadding 次 + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +func desDecrypt(crypted, key []byte) ([]byte, error) { + block, err := des.NewCipher(key) + if err != nil { + return nil, err + } + blockMode := cipher.NewCBCDecrypter(block, []byte("00000000")) + origData := make([]byte, len(crypted)) + // origData := crypted + blockMode.CryptBlocks(origData, crypted) + origData = pkcs5UnPadding(origData) + // origData = ZeroUnPadding(origData) + return origData, nil +} + +func base64Decode(src []byte) ([]byte, error) { + var coder *base64.Encoding + coder = base64.NewEncoding(base) + return coder.DecodeString(string(src)) +} + +func httpPostBody(url string, msg []byte) ([]byte, error) { + resp, err := http.Post(url, "application/json;charset=utf-8", bytes.NewBuffer(msg)) + if err != nil { + return []byte(""), err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + return body, err +} + +func generateSign(request map[string]interface{}, secret string) string { + ret := "" + var keys []string + for k := range request { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + ret = ret + fmt.Sprintf("%v=%v&", k, request[k]) + } + ret = ret[:len(ret)-1] + secret + + md5Ctx := md5.New() + md5Ctx.Write([]byte(ret)) + cipherStr := md5Ctx.Sum(nil) + return hex.EncodeToString(cipherStr) +} diff --git a/app/lib/mob/main.go b/app/lib/mob/main.go new file mode 100644 index 0000000..e76b515 --- /dev/null +++ b/app/lib/mob/main.go @@ -0,0 +1,15 @@ +package mob + +import "applet/app/svc" + +//NewMobSDK 构建一个Mobsdk对象 +func NewMobSDK() *SDK { + // 后续可能要传请求的上下文来获取对应的配置 + // mob 目前都是我们来管理每个站长的app 所以使用template 库 + key := svc.SysCfgGet(nil, "third_mob_app_key") + secret := svc.SysCfgGet(nil, "third_mob_app_secret") + mob := new(SDK) + mob.AppKey = key + mob.AppSecret = secret + return mob +} diff --git a/app/lib/push/admin_mob_push.go b/app/lib/push/admin_mob_push.go new file mode 100644 index 0000000..818ea9c --- /dev/null +++ b/app/lib/push/admin_mob_push.go @@ -0,0 +1,255 @@ +package push + +import ( + "applet/app/cfg" + "applet/app/db" + "applet/app/db/model" + "applet/app/md" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + "xorm.io/xorm" + + "github.com/tidwall/gjson" +) + +//公共处理推送数据 +func CommAddPush(eg *xorm.Engine, args md.PushParams) { + // 后台mob推送 + pushArgs := make(map[string]string) + if args.DomainWapBase == "" { + args.DomainWapBase = "http://h5." + args.MasterId + ".izhyin.com" + if cfg.Prd { + args.DomainWapBase = "http://h5." + args.MasterId + ".zhiyingos.com" + } + } + if args.PushType != "" { + temp, err := db.SysPushTemplateByType(eg, args.PushType) + if err != nil { + //e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if temp != nil { //处理有模板的数据 + if temp.IsAppPush != 1 { + return + } + args.Title = temp.Title + args.Content = temp.Content + args.Skip = temp.Skip + //替换链接的一些参数 + if strings.Contains(args.Skip, "[replace_APP_URL]") { + args.Skip = strings.Replace(args.Skip, "[replace_APP_URL]", args.DomainWapBase, -1) + } + if strings.Contains(args.Skip, "[replace_masterId]") { + args.Skip = strings.Replace(args.Skip, "[replace_masterId]", args.MasterId, -1) + } + if strings.Contains(args.Skip, "[replace_uid]") { + args.Skip = strings.Replace(args.Skip, "[replace_uid]", utils.IntToStr(args.Uid), -1) + } + if strings.Contains(args.Skip, "skipIdentifier") { + args.Skip = strings.Replace(args.Skip, "skipIdentifier", "skip_identifier", -1) + } + if strings.Contains(args.Skip, "requiredLogin") { + args.Skip = strings.Replace(args.Skip, "requiredLogin", "required_login", -1) + } + if strings.Contains(args.Skip, "requiredTaobaoAuth") { + args.Skip = strings.Replace(args.Skip, "requiredTaobaoAuth", "required_taobao_auth", -1) + } + if strings.Contains(args.Skip, "activityId") { + args.Skip = strings.Replace(args.Skip, "activityId", "activity_id", -1) + } + if strings.Contains(args.Skip, "sourceType") { + args.Skip = strings.Replace(args.Skip, "sourceType", "source_type", -1) + } + if strings.Contains(args.Skip, "brandId") { + args.Skip = strings.Replace(args.Skip, "brandId", "brand_id", -1) + } + args.Content = strings.Replace(args.Content, "[APP名称]", args.AppName, -1) + args.Content = strings.Replace(args.Content, "[会员昵称]", args.Nickname, -1) + args.Content = strings.Replace(args.Content, "[付款金额]", args.Payment, -1) + args.Content = strings.Replace(args.Content, "[下单人昵称]", args.OrderNickname, -1) + args.Content = strings.Replace(args.Content, "[订单号]", args.OrderNo, -1) + args.Content = strings.Replace(args.Content, "[下单时间]", args.OrderTime, -1) + args.Content = strings.Replace(args.Content, "[预估佣金]", args.Fcommission, -1) + args.Content = strings.Replace(args.Content, "[注册人昵称]", args.RegNickname, -1) + args.Content = strings.Replace(args.Content, "[注册人手机号]", args.RegPhone, -1) + args.Content = strings.Replace(args.Content, "[升级等级名称]", args.LvName, -1) + args.Content = strings.Replace(args.Content, "[提现金额]", args.WithdrawMoney, -1) + args.Content = strings.Replace(args.Content, "[原因]", args.Reason, -1) + args.Content = strings.Replace(args.Content, "[金额]", args.Money, -1) + args.Content = strings.Replace(args.Content, "[时间]", args.Times, -1) + } + } + pushArgs["push_title"] = args.Title + pushArgs["push_content"] = args.Content + pushArgs["push_user"] = fmt.Sprintf("%s_%d", args.MasterId, args.Uid) + pushArgs["push_receive_user_id"] = utils.IntToStr(args.Uid) + pushArgs["push_type"] = args.PushType + pushArgs["masterId"] = args.MasterId + pushArgs["push_skip"] = args.Skip + if err := AdminMobPush(eg, pushArgs); err != nil { + //e.OutErr(c, e.ERR_ADMIN_PUSH, err) + return + } + return +} + +// AdminMobPush mob is 推送 t 有下面几种类型模板类型 | 推送类型;public;:普通推送;activity:活动通知;order_self:新订单提醒(导购自购新订单),order_team:新订单提醒(团队新订单),order_share:新订单提醒(导购分享新订单),member_register:团队成员注册成功,level_upgrade:团队成员等级升级成功,withdraw_fail:提现失败提醒,withdraw_success:提现成功提醒,comission_settle_success:佣金结算提醒(平台结算) +func AdminMobPush(eg *xorm.Engine, pushArgs map[string]string) error { + var ( + host string + port string + ) + + // 开始准备推送 + mdb, err := db.GetDatabaseByMasterID(db.Db, pushArgs["masterId"]) + fmt.Println("============================") + fmt.Println(pushArgs) + fmt.Println(mdb) + if err != nil { + return err + } + if mdb.DbHost == "" { + mdb.DbHost = cfg.DB.Host + } + //fmt.Println(mdb.DbHost) + dbc := strings.Split(mdb.DbHost, ":") + if len(dbc) != 2 && mdb.DbHost != "" { + return errors.New("db mapping db_host is formt error") + } + + host = dbc[0] + port = dbc[1] + + args := struct { + Content string `json:"content"` + Db struct { + DbHost string `json:"db_host"` + DbName string `json:"db_name"` + DbPassword string `json:"db_password"` + DbPort string `json:"db_port"` + DbUsername string `json:"db_username"` + } `json:"db"` + Plats int64 `json:"plats"` + Skip string `json:"skip"` + Title string `json:"title"` + User string `json:"user"` + }{ + Content: pushArgs["push_content"], + Db: struct { + DbHost string "json:\"db_host\"" + DbName string "json:\"db_name\"" + DbPassword string "json:\"db_password\"" + DbPort string "json:\"db_port\"" + DbUsername string "json:\"db_username\"" + }{ + DbHost: host, + DbPort: port, + DbName: mdb.DbName, + DbUsername: mdb.DbUsername, + DbPassword: mdb.DbPassword, + }, + Plats: 1, + Skip: pushArgs["push_skip"], + Title: pushArgs["push_title"], + User: pushArgs["push_user"], + } + + // 插入推送记录 + log := &model.SysPushApp{ + Title: pushArgs["push_title"], + Content: pushArgs["push_content"], + Provider: "mob", + Type: pushArgs["push_type"], + State: 1, + SendAt: 0, + DeviceProvider: 1, + Target: 2, + TargetCondition: fmt.Sprintf(`{"type":"user","val":["%s"]}`, pushArgs["push_receive_user_id"]), + Skip: pushArgs["push_skip"], + CreateAt: time.Now(), + UpdateAt: time.Now(), + } + if _, err := db.InertSysPushAppOne(eg, log); err != nil { + // e.OutErr(c, e.ERR_DB_ORM, err) + // return + logx.Warn(err) + } + var url string + var domain = "http://login.izhyin.com" + if cfg.Prd { + domain = "http://zhios-admin" + } + url = domain + "/appapi/Push/mobPush" + aesData, err := json.Marshal(&args) + if err != nil { + // e.OutErr(c, e.ERR_ADMIN_API, err) + return err + } + //fmt.Println(string(aesData)) + fmt.Println(url) + fmt.Println(aesData) + if aesData == nil { + return nil + } + // 推送 + resp, err := utils.AesAdminCurlPOST(string(aesData), url) + fmt.Println("==========推送============") + fmt.Println(resp) + fmt.Println(gjson.Get(string(resp), "message").String()) + fmt.Println(err) + if err != nil { + // e.OutErr(c, e.ERR_ADMIN_API, err) + return err + } + if resp == nil { + return err + } + //fmt.Println(string(resp)) + // 推送成功更新推送状态 + if gjson.GetBytes(resp, "code").Int() == 0 { + // 发送成功处理 + log.State = 2 + if _, err := db.UpdateSysPushApp(eg, log); err != nil { + logx.Warn(err) + } + } + + sendData := struct { + Alias string `json:"alias"` + Content string `json:"content"` + Error string `json:"error"` + Title string `json:"title"` + }{ + Alias: pushArgs["push_user"], + Content: pushArgs["push_content"], + Error: gjson.Get(string(resp), "message").String(), + Title: pushArgs["push_content"], + } + fmt.Println(sendData.Error) + bendData, _ := json.Marshal(sendData) + fmt.Println("插入到通知记录") + // 插入到通知记录 + _, err = db.SysPushUserInsertOne(eg, &model.SysPushUser{ + PushId: int(log.Id), + Uid: utils.StrToInt(pushArgs["push_receive_user_id"]), + State: func() int { + if gjson.GetBytes(resp, "code").Int() == 0 { + return 1 + } + return 0 + }(), + Time: time.Now(), + SendData: string(bendData), + Provider: "mob", + Type: pushArgs["push_type"], + }) + if err != nil { + logx.Warn(err) + } + return nil +} diff --git a/app/lib/qiniu/bucket_create.go b/app/lib/qiniu/bucket_create.go new file mode 100644 index 0000000..28d8106 --- /dev/null +++ b/app/lib/qiniu/bucket_create.go @@ -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) +} diff --git a/app/lib/qiniu/bucket_delete.go b/app/lib/qiniu/bucket_delete.go new file mode 100644 index 0000000..6d41521 --- /dev/null +++ b/app/lib/qiniu/bucket_delete.go @@ -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) +} diff --git a/app/lib/qiniu/bucket_get_domain.go b/app/lib/qiniu/bucket_get_domain.go new file mode 100644 index 0000000..f4cee3a --- /dev/null +++ b/app/lib/qiniu/bucket_get_domain.go @@ -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 +} diff --git a/app/lib/qiniu/init.go b/app/lib/qiniu/init.go new file mode 100644 index 0000000..1d4346a --- /dev/null +++ b/app/lib/qiniu/init.go @@ -0,0 +1,22 @@ +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) +} diff --git a/app/lib/qiniu/req_img_upload.go b/app/lib/qiniu/req_img_upload.go new file mode 100644 index 0000000..d24f69b --- /dev/null +++ b/app/lib/qiniu/req_img_upload.go @@ -0,0 +1,55 @@ +package qiniu + +import ( + "time" + + "github.com/qiniu/api.v7/v7/auth/qbox" + _ "github.com/qiniu/api.v7/v7/conf" + "github.com/qiniu/api.v7/v7/storage" + + "applet/app/md" + "applet/app/utils" +) + +// 请求图片上传地址信息 +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 : 待上传文件 +*/ diff --git a/app/lib/sms/sms.go b/app/lib/sms/sms.go new file mode 100644 index 0000000..2aafc1f --- /dev/null +++ b/app/lib/sms/sms.go @@ -0,0 +1,21 @@ +package sms + +import ( + "applet/app/lib/zhimeng" + "applet/app/svc" + "applet/app/utils/logx" + + "github.com/gin-gonic/gin" +) + +// NewZhimengSMS is 智盟的短信服务 +func NewZhimengSMS(c *gin.Context) *zhimeng.SDK { + sms := new(zhimeng.SDK) + key := svc.SysCfgGet(c, "third_zm_sms_key") + secret := svc.SysCfgGet(c, "third_zm_sms_secret") + if key == "" || secret == "" { + _ = logx.Warn("短信服务配置错误") + } + sms.Init("send_msg", key, secret) + return sms +} diff --git a/app/lib/wxpay/.gitignore b/app/lib/wxpay/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/app/lib/wxpay/api.go b/app/lib/wxpay/api.go new file mode 100644 index 0000000..16d5408 --- /dev/null +++ b/app/lib/wxpay/api.go @@ -0,0 +1,305 @@ +package wxpay + +import ( + "applet/app/utils" + "applet/app/utils/logx" + "fmt" + "github.com/iGoogle-ink/gopay" + "github.com/iGoogle-ink/gopay/pkg/util" + "github.com/iGoogle-ink/gopay/wechat" + v3 "github.com/iGoogle-ink/gopay/wechat/v3" + "strconv" + "time" +) + +func NewClient(appId, mchId, apiKey string, isProd bool) *wechat.Client { + // 初始化微信客户端 + // appId:应用ID + // mchId:商户ID + // apiKey:API秘钥值 + // isProd:是否是正式环境 + client := wechat.NewClient(appId, mchId, apiKey, isProd) + // 打开Debug开关,输出请求日志,默认关闭 + client.DebugSwitch = gopay.DebugOn + // 设置国家:不设置默认 中国国内 + // wechat.China:中国国内 + // wechat.China2:中国国内备用 + // wechat.SoutheastAsia:东南亚 + // wechat.Other:其他国家 + client.SetCountry(wechat.China) + // 添加微信证书 Path 路径 + // certFilePath:apiclient_cert.pem 路径 + // keyFilePath:apiclient_key.pem 路径 + // pkcs12FilePath:apiclient_cert.p12 路径 + // 返回err + //client.AddCertFilePath() + + // 添加微信证书内容 Content + // certFileContent:apiclient_cert.pem 内容 + // keyFileContent:apiclient_key.pem 内容 + // pkcs12FileContent:apiclient_cert.p12 内容 + // 返回err + //client.AddCertFileContent() + return client +} + +// TradeAppPay is 微信APP支付 +func TradeAppPay(client *wechat.Client, subject, orderID, amount, notifyUrl string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_App). + Set("sign_type", wechat.SignType_MD5) + /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + //if !ok { + // return nil, errors.New("验签失败") + //} + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + res := map[string]string{ + "appid": client.AppId, + "partnerid": client.MchId, + "prepayid": wxRsp.PrepayId, + "sign": paySign, + "package": "Sign=WXPay", + "noncestr": wxRsp.NonceStr, + "timestamp": timeStamp, + } + return res, nil +} + +// TradeAppPay is 微信H5支付 +func TradeH5Pay(client *wechat.Client, subject, orderID, amount, notifyUrl, ip string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + //Set("spbill_create_ip", "121.196.29.49"). + Set("spbill_create_ip", ip). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_H5). + Set("sign_type", wechat.SignType_MD5). + SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("wap_url", "https://www.fumm.cc") + bm.Set("wap_name", "zyos") + }) + }) + /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + // 预下单 + fmt.Println(bm) + wxRsp, err := client.UnifiedOrder(bm) + fmt.Println(wxRsp) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + packages := "prepay_id=" + wxRsp.PrepayId + paySign := wechat.GetH5PaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey) + fmt.Println("paySign===", paySign) + r := map[string]string{ + "redirect_url": wxRsp.MwebUrl, + } + return r, nil +} + +// TradeMiniProgPay is 微信小程序支付 ☑️ +func TradeMiniProgPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("openid", openid). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_Mini). + Set("sign_type", wechat.SignType_MD5) + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + packages := "prepay_id=" + wxRsp.PrepayId + paySign := wechat.GetMiniPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey) + res := map[string]string{ + "appId": client.AppId, + "paySign": paySign, + "signType": wechat.SignType_MD5, + "package": packages, + "nonceStr": wxRsp.NonceStr, + "timeStamp": timeStamp, + } + return res, nil +} + +// TradeAppPayV3 is 微信APP支付v3 +func TradeAppPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (map[string]string, error) { + // 初始化 BodyMap + amountNew := utils.AnyToFloat64(amount) * 100 + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amountNew). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_App). + Set("sign_type", wechat.SignType_MD5) + /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + //// 预下单 + //wxRsp, err := v3.UnifiedOrder(bm) + //if err != nil { + // _ = logx.Warn(err) + // return nil, err + //} + //_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + //if err != nil { + // _ = logx.Warn(err) + // return nil, err + //} + ////if !ok { + //// return nil, errors.New("验签失败") + ////} + //timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + //paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + //res := map[string]string{ + // "appid": client.AppId, + // "partnerid": client.MchId, + // "prepayid": wxRsp.PrepayId, + // "sign": paySign, + // "package": "Sign=WXPay", + // "noncestr": wxRsp.NonceStr, + // "timestamp": timeStamp, + //} + //return res, nil + return nil, nil +} + +//// TradeJSAPIPay is 微信JSAPI支付 +func TradeJSAPIPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "121.196.29.49"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_JsApi). + Set("sign_type", wechat.SignType_MD5). + Set("openid", openid). + SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("wap_url", "https://www.fumm.cc") + bm.Set("wap_name", "zyos") + }) + }) + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + if err != nil { + _ = logx.Warn(err) + return nil, err + } + //if !ok { + // return nil, errors.New("验签失败") + //} + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + //paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + packages := "prepay_id=" + wxRsp.PrepayId + paySign := wechat.GetJsapiPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey) + + logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId) + logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId) + logx.Info("wxRsp.PrepayId:" + openid) + res := map[string]string{ + "appid": client.AppId, + "partnerid": client.MchId, + "prepayid": wxRsp.PrepayId, + "sign": paySign, + "package": "prepay_id=" + wxRsp.PrepayId, + "noncestr": wxRsp.NonceStr, + "timestamp": timeStamp, + } + return res, nil +} + +// TradeH5PayV3 is 微信H5支付v3 +func TradeH5PayV3(client *wechat.Client, subject, orderID, amount, notifyUrl string) (string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_App). + Set("device_info", "WEB"). + Set("sign_type", wechat.SignType_MD5). + SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("wap_url", "https://www.fumm.cc") + bm.Set("wap_name", "H5测试支付") + }) + }) /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = logx.Warn(err) + return "", err + } + // ====APP支付 paySign==== + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + // 获取APP支付的 paySign + // 注意:package 参数因为是固定值,无需开发者再传入 + // appId:AppID + // partnerid:partnerid + // nonceStr:随机字符串 + // prepayId:统一下单成功后得到的值 + // signType:签名方式,务必与统一下单时用的签名方式一致 + // timeStamp:时间 + // apiKey:API秘钥值 + paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + return paySign, nil +} + +// TradeMiniProgPayV3 is 微信小程序支付v3 +func TradeMiniProgPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (string, error) { + return "", nil +} diff --git a/app/lib/zhimeng/api.go b/app/lib/zhimeng/api.go new file mode 100644 index 0000000..bbd0dfc --- /dev/null +++ b/app/lib/zhimeng/api.go @@ -0,0 +1,67 @@ +package zhimeng + +import ( + "fmt" + "sort" + "strconv" + "time" + + "applet/app/utils" +) + +var StatusSuc int = 1 + +type ZM struct { + AK string + SK string + SMS_AK string + SMS_SK string +} + +// 智盟接口, 可以调取京东, 拼多多等 +const ZM_HOST = "http://www.izhim.com/" + +var ( + ZM_BASE_URL = ZM_HOST + "?mod=api&act=%s&ctrl=%s" + APP_KEY = "300000001" + SECRET_KEY = "95c347002b2750dbd4b6a03bd4196c18" + SMS_APP_KEY = "300000175" + SMS_SECRET_KEY = "6cf1dcd1820a576ff2cbecbe00d31df2" +) + +func Send(act, op string, args map[string]interface{}) ([]byte, error) { + router := fmt.Sprintf(ZM_BASE_URL, act, op) + // args["appkey"] = APP_KEY + args["time"] = strconv.Itoa(int(time.Now().Unix())) + args["sign"] = sign(args, args["secret_key"].(string)) + // b, _ := json.Marshal(args) + // fmt.Println(string(b)) + return utils.CurlPost(router, args, nil) +} + +// SMSend is 发送短信用的key 和签名 +func SMSend(act, op, key, secret string, args map[string]interface{}) ([]byte, error) { + router := fmt.Sprintf(ZM_BASE_URL, act, op) + + args["appkey"] = key + args["time"] = strconv.Itoa(int(time.Now().Unix())) + args["sign"] = sign(args, secret) + + return utils.CurlPost(router, args, nil) +} + +func sign(m map[string]interface{}, SK string) string { + // key sort + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + str := "" + for _, k := range keys { + str += k + utils.AnyToString(m[k]) + } + // merge string + str = SK + str + SK + "方诺科技" + return utils.Md5(str) +} diff --git a/app/mall/db/model/demo.go b/app/mall/db/model/demo.go new file mode 100644 index 0000000..8b53790 --- /dev/null +++ b/app/mall/db/model/demo.go @@ -0,0 +1 @@ +package model diff --git a/app/mall/hdl/hdl_demo.go b/app/mall/hdl/hdl_demo.go new file mode 100644 index 0000000..dfe4f95 --- /dev/null +++ b/app/mall/hdl/hdl_demo.go @@ -0,0 +1,81 @@ +package hdl + +import ( + "applet/app/e" + //"applet/app/utils" + "applet/app/utils/logx" + "fmt" + "github.com/gin-gonic/gin" +) + +// Demo 测试 +func Demo(c *gin.Context) { + str := `{"appid":"wx598aaef252cd78e4","bank_type":"OTHERS","cash_fee":"1","fee_type":"CNY","is_subscribe":"N","master_id":"22255132","mch_id":"1534243971","nonce_str":"xiUZXdrEkpY9UdfCGEcBSE2jy7yWmQsk","openid":"odmKs6kNQBnujHv_S8YyME8g0-6c","order_type":"mall_goods","out_trade_no":"570761162512383595","pay_method":"wxpay","result_code":"SUCCESS","return_code":"SUCCESS","sign":"A5C7B43A8437E6AD72BB4FDAA8532A59","time_end":"20210701151722","total_fee":"1","trade_type":"APP","transaction_id":"4200001143202107010591333162"}` + c.Set("data", str) + var tmp map[string]interface{} + err := c.ShouldBindJSON(&tmp) + if err != nil { + _ = logx.Error(err) + return + } + fmt.Println(tmp["master_id"]) + + e.OutSuc(c, "hello mall", nil) +} + +func Demo1(c *gin.Context) { + //eg := commDb.DBs[c.GetString("mid")] + //sess := eg.NewSession() + ////r, err := eg.Table("user_profile").Where("uid=21699").Incr("fin_valid", 10).Exec() + //sql := "update user_profile set fin_valid=fin_valid+? WHERE uid=?" + //r, err := sess.Exec(sql, 10, 21699) + //if err != nil { + // return + //} + //sess.Commit() + // + //fmt.Println("res",utils.SerializeStr(r)) + + + + + /*engine := commDb.DBs[c.GetString("mid")] + now := time.Now() //获取当前时间 + var startDate = now.Format("2006-01-02 15:00:00") + var endDate = now.Add(time.Hour * 2).Format("2006-01-02 15:00:00") + res := svc2.HandleSecondsKillForDate(engine, c.GetString("mid"), startDate, endDate) + startTime := utils.AnyToString(now.Hour()) + endTime := utils.AnyToString(now.Add(time.Hour * 2).Hour()) + res = svc2.HandleSecondsKillForTime(engine, c.GetString("mid"), startDate, endDate) + + res = svc2.HandleSecondsKillForDateTime(engine, c.GetString("mid"), startDate, endDate, startTime, endTime)*/ + //reqList := make([]*md.CommissionReq, 0, 10) + // + //req := md.CommissionReq{ + // CommissionParam: md.CommissionParam{Commission: "10.00"}, + // Uid: "21699", + // IsShare: 0, + // Provider: "mall_goods", + // IsAllLevelReturn: 0, + // GoodsId: "3", + //} + // + //for i := 0; i < 10; i++ { + // req := req + // req.GoodsId = utils.AnyToString(i + 1) + // reqList = append(reqList, &req) + //} + // + //fmt.Println(utils.SerializeStr(reqList)) + // + //api, err := svc.BatchGetCommissionByCommApi("123456", reqList) + //if err != nil { + // _ = logx.Error(err) + // fmt.Println(err) + // e.OutErr(c, e.ERR, err) + // return + //} + + //e.OutSuc(c, res, nil) + +} diff --git a/app/mall/md/demo.go b/app/mall/md/demo.go new file mode 100644 index 0000000..7af5617 --- /dev/null +++ b/app/mall/md/demo.go @@ -0,0 +1 @@ +package md diff --git a/app/mall/svc/svc_demo.go b/app/mall/svc/svc_demo.go new file mode 100644 index 0000000..b3463c0 --- /dev/null +++ b/app/mall/svc/svc_demo.go @@ -0,0 +1 @@ +package svc diff --git a/app/mall/tool/json.go b/app/mall/tool/json.go new file mode 100644 index 0000000..2986f73 --- /dev/null +++ b/app/mall/tool/json.go @@ -0,0 +1,131 @@ +package tool + +import ( + "bytes" + "encoding/json" + "log" + "regexp" + "strconv" + "strings" + "unicode" +) + +/*************************************** 下划线json ***************************************/ +type JsonSnakeCase struct { + Value interface{} +} + +func MarshalJSON(marshalJson []byte) []byte { + // Regexp definitions + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + var wordBarrierRegex = regexp.MustCompile(`(\w)([A-Z])`) + converted := keyMatchRegex.ReplaceAllFunc( + marshalJson, + func(match []byte) []byte { + return bytes.ToLower(wordBarrierRegex.ReplaceAll( + match, + []byte(`${1}_${2}`), + )) + }, + ) + return converted +} + +/*************************************** 驼峰json ***************************************/ +type JsonCamelCase struct { + Value interface{} +} + +func (c JsonCamelCase) MarshalJSON() ([]byte, error) { + var keyMatchRegex = regexp.MustCompile(`\"(\w+)\":`) + marshalled, err := json.Marshal(c.Value) + converted := keyMatchRegex.ReplaceAllFunc( + marshalled, + func(match []byte) []byte { + matchStr := string(match) + key := matchStr[1 : len(matchStr)-2] + resKey := Lcfirst(Case2Camel(key)) + return []byte(`"` + resKey + `":`) + }, + ) + return converted, err +} + +/*************************************** 其他方法 ***************************************/ +// 驼峰式写法转为下划线写法 +func Camel2Case(name string) string { + buffer := NewBuffer() + for i, r := range name { + if unicode.IsUpper(r) { + if i != 0 { + buffer.Append('_') + } + buffer.Append(unicode.ToLower(r)) + } else { + buffer.Append(r) + } + } + return buffer.String() +} + +// 下划线写法转为驼峰写法 +func Case2Camel(name string) string { + name = strings.Replace(name, "_", " ", -1) + name = strings.Title(name) + return strings.Replace(name, " ", "", -1) +} + +// 首字母大写 +func Ucfirst(str string) string { + for i, v := range str { + return string(unicode.ToUpper(v)) + str[i+1:] + } + return "" +} + +// 首字母小写 +func Lcfirst(str string) string { + for i, v := range str { + return string(unicode.ToLower(v)) + str[i+1:] + } + return "" +} + +// 内嵌bytes.Buffer,支持连写 +type Buffer struct { + *bytes.Buffer +} + +func NewBuffer() *Buffer { + return &Buffer{Buffer: new(bytes.Buffer)} +} + +func (b *Buffer) Append(i interface{}) *Buffer { + switch val := i.(type) { + case int: + b.append(strconv.Itoa(val)) + case int64: + b.append(strconv.FormatInt(val, 10)) + case uint: + b.append(strconv.FormatUint(uint64(val), 10)) + case uint64: + b.append(strconv.FormatUint(val, 10)) + case string: + b.append(val) + case []byte: + b.Write(val) + case rune: + b.WriteRune(val) + } + return b +} + +func (b *Buffer) append(s string) *Buffer { + defer func() { + if err := recover(); err != nil { + log.Println("*****内存不够了!******") + } + }() + b.WriteString(s) + return b +} diff --git a/app/mall/tool/time2s.go b/app/mall/tool/time2s.go new file mode 100644 index 0000000..58dc021 --- /dev/null +++ b/app/mall/tool/time2s.go @@ -0,0 +1,21 @@ +package tool + +import "time" + +func Time2String(date time.Time, format string) string { + if format == "" { + format = "2006-01-02 15:04:05" + } + timeS := date.Format(format) + if timeS == "0001-01-01 00:00:00" { + return "" + } + return timeS +} +func String2Time(timeStr string) time.Time { + toTime, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) + if err != nil { + return time.Now() + } + return toTime +} diff --git a/app/md/agent_order.go b/app/md/agent_order.go new file mode 100644 index 0000000..48626dc --- /dev/null +++ b/app/md/agent_order.go @@ -0,0 +1,23 @@ +package md + +type InsertRegionalAgentOrdBelongData struct { + Uid int `json:"uid"` + IsOpen int `json:"is_open"` + Pvd string `json:"pvd"` + CommPvd string `json:"comm_pvd"` + Commission string `json:"commission"` + OrderId int64 `json:"order_id"` + RegionRate float64 `json:"region_rate"` + GlobalRate float64 `json:"global_rate"` + Status string `json:"status"` +} + +type InsertCapitalPoolOrdBelongData struct { + Uid string `json:"uid" remark:用户id` + Pvd string `json:"pvd" remark:订单渠道:自营,导购,o2o。。。。` + OrdId string `json:"ord_id" remark:订单id` + Commission string `json:"commission" remark:订单总佣金` + CommissionType string `json:"commission_type" remark:佣金类型(CNY,虚拟币1Id,虚拟币2Id)` + CapitalPoolRate string `json:"capital_pool_rate" remark:资金池存入比例` + DepositValue string `json:"deposit_value" remark:存入金额` +} diff --git a/app/md/app_redis_key.go b/app/md/app_redis_key.go new file mode 100644 index 0000000..77d0964 --- /dev/null +++ b/app/md/app_redis_key.go @@ -0,0 +1,12 @@ +package md + +// 缓存key统一管理, %s格式化为masterId +const ( + AppCfgCacheKey = "%s:cfg_cache:%s" // 占位符: masterId, key的第一个字母 + VirtualCoinCfgCacheKey = "%s:virtual_coin_cfg" + PlanRewardCfgCacheKey = "%s:plan_reward_cfg" + + UserFinValidUpdateLock = "%s:user_fin_valid_update_lock:%s" // 用户余额更新锁(能拿到锁才能更新余额) + + CfgCacheTime = 86400 +) diff --git a/app/md/applet.go b/app/md/applet.go new file mode 100644 index 0000000..f69bd1d --- /dev/null +++ b/app/md/applet.go @@ -0,0 +1,16 @@ +package md + +const ( + /* 微信京东小程序 */ + APPLET_JD_ID = "wx13e41a437b8a1d2e" + APPLET_JD_URL = "pages/proxy/union/union?customerinfo=hry180611&spreadUrl=%s" + /* 唯品会 */ + APPLET_VIP_ID = "wxe9714e742209d35f" + /* 考拉 */ + APPLET_KL_ID = "wx9180a45a676eed94" + APPLET_KL_URL = "package-product/pages/index?dastr=__da_dad3e203_5d016fd6a5b92c00&zkTargetUrl=https%3A%2F%2Fm-goods.kaola.com%2Fproduct%2F{ITEM_ID}.html" + APPLET_KL_ACTIVITY_URL = "package-product/pages/index?dastr=__da_dad3e203_5d016fd6a5b92c00&zkTargetUrl={URL}" + + /* 拼多多 */ + APPLET_PDD_ID = "wx32540bd863b27570" +) diff --git a/app/md/cfg_key.go b/app/md/cfg_key.go new file mode 100644 index 0000000..6bd72b7 --- /dev/null +++ b/app/md/cfg_key.go @@ -0,0 +1,92 @@ +package md + +// 获取用户的缓存key +const ( + KEY_SYS_CFG_CACHE = "sys_cfg_cache" + + // 文件缓存的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. + // 智盟 + KEY_CFG_ZM_JD_SITE_ID = "third_zm_jd_site_id" // 智盟京东联盟id + KEY_CFG_ZM_WEB_ID = "third_zm_web_id" // 智盟网站ID + KEY_CFG_ZM_AK = "third_zm_app_key" + KEY_CFG_ZM_SK = "third_zm_app_secret" + KEY_CFG_ZM_SMS_AK = "third_zm_sms_ak" + KEY_CFG_ZM_SMS_SK = "third_zm_sms_sk" + KEY_CFG_APP_NAME = "app_name" + + KEY_CFG_WHITELIST = "api_cfg_whitelist" // API允许的访问的设置白名单 + + // 淘宝 + KEY_CFG_TB_AUTH_AK = "third_taobao_auth_ak" + KEY_CFG_TB_AUTH_SK = "third_taobao_auth_sk" + KEY_CFG_TB_INVITER_CODE = "third_taobao_auth_inviter_code" + KEY_CFG_TB_AK = "third_taobao_ak" + KEY_CFG_TB_SK = "third_taobao_sk" + KEY_CFG_TB_PID = "third_taobao_pid" // 淘宝推广ID,如:mm_123_456_789,123是联盟ID,456是site_id,789是adzone_id + KEY_CFG_TB_SID = "third_taobao_sid" // 淘宝session id ,又称access_token + + // 苏宁 + KEY_CFG_SN_AK = "third_suning_ak" + KEY_CFG_SN_SK = "third_suning_sk" + + KEY_CFG_JD_AK = "" + KEY_CFG_JD_SK = "" + + KEY_CFG_KL_AK = "third_kaola_ak" + KEY_CFG_KL_SK = "third_kaola_sk" + + KEY_CFG_VIP_AK = "" + KEY_CFG_VIP_SK = "" + + // 自动任务配置 + KEY_CFG_CRON_TB = "cron_order_taobao" + KEY_CFG_CRON_JD = "cron_order_jd" + KEY_CFG_CRON_PDD = "cron_order_pdd" + KEY_CFG_CRON_SN = "cron_order_suning" + KEY_CFG_CRON_VIP = "cron_order_vip" + KEY_CFG_CRON_KL = "cron_order_kaola" + KEY_CFG_CRON_DUOMAI = "cron_order_duomai" + KEY_CFG_CRON_HIS = "cron_order_his" // 迁移到历史订单 + KEY_CFG_CRON_SETTLE = "cron_order_settle" // 迁移到历史订单 + KEY_CFG_CRON_PUBLISHER = "cron_taobao_publisher" // 跟踪淘宝备案信息绑定会员运营id 针对小程序 + KEY_CFG_CRON_MEITUAN = "cron_order_meituan" //美团 + KEY_CFG_CRON_OILSTATION = "cron_order_oilstation" //加油 + KEY_CFG_CRON_KFC = "cron_order_kfc" //肯德基 + KEY_CFG_CRON_CINEMA = "cron_order_cinema" //电影票 + KEY_CFG_CRON_OilRequest = "cron_order_oilrequest" //加入主动请求抓单 + KEY_CFG_CRON_AGOTB = "cron_order_agotaobao" //n天前的淘宝订单 + KEY_CFG_CRON_CREDIT_CARD = "cron_order_credit_card" + KEY_CFG_CRON_ORDER_STAT = "cron_order_stat" // 订单统计任务 + KEY_CFG_CRON_CARD_UPDATE = "cron_card_update" // 权益卡更新 + KEY_CFG_CRON_USER_LV_UP_SETTLE = "cron_user_lv_up_settle" //会员费订单结算 + KEY_CFG_CRON_PRIVILEGE_CARD_SETTLE = "cron_privilege_card_settle" //权益卡订单结算 + KEY_CFG_CRON_CARD_RETURN = "cron_card_return" //权益卡退款 + KEY_CFG_CRON_PUBLISHER_RELATION = "cron_taobao_publisher_relation" //获取淘宝渠道 + KEY_CFG_CRON_DTKBRAND = "cron_dtk_brand" //大淘客品牌信息 + KEY_CFG_CRON_PUBLISHER_RELATION_BIND = "cron_taobao_publisher_relation_bind" //获取淘宝渠道绑定 + + // 自动任务运行时设置 + KEY_CFG_CRON_TIME_TB = "crontab_order_time_taobao" + KEY_CFG_CRON_TIME_JD = "crontab_order_time_jd" + KEY_CFG_CRON_TIME_PDD = "crontab_order_time_pdd" + KEY_CFG_CRON_TIME_SN = "crontab_order_time_suning" + KEY_CFG_CRON_TIME_VIP = "crontab_order_time_vip" + KEY_CFG_CRON_TIME_KL = "crontab_order_time_kaola" + KEY_CFG_CRON_TIME_DUOMAI = "crontab_order_time_duomai" + KEY_CFG_CRON_TIME_PUBLISHER = "crontab_taobao_time_publisher" // 跟踪淘宝备案信息绑定会员运营id 针对小程序 + KEY_CFG_CRON_TIME_MEITUAN = "crontab_order_time_meituan" //美团 + KEY_CFG_CRON_TIME_OILSTATION = "crontab_order_time_oilstation" //加油 + KEY_CFG_CRON_TIME_KFC = "crontab_order_time_kfc" //肯德基 + KEY_CFG_CRON_TIME_CINEMA = "crontab_order_time_cinema" //电影票 + +) diff --git a/app/md/commission.go b/app/md/commission.go new file mode 100644 index 0000000..5bce6cd --- /dev/null +++ b/app/md/commission.go @@ -0,0 +1,45 @@ +package md + +// LvUser 分佣返回结果 +type LvUser struct { + Uid int // 用户ID + Lv int // 等级 + NewLv int // 升级后等级 针对会员费分佣 + LevelWeight int // 权重 + + Profit float64 // 利润 + SubsidyFee float64 // 补贴 + ProfitList []*VirtualCoinCommission + SubsidyFeeList []*VirtualCoinCommission + + /*IntegralProfit float64 // 积分利润 + IntegralSubsidyFee float64 // 积分补贴 + BlockIconsProfit float64 // 区块币利润 + BlockIconsSubsidyFee float64 // 区块币补贴*/ + + OwnbuyReturnType int //0有返利 1没有返利 + Diff int // 与当前用户级别差 + ParentUser *LvUser // 父用户 +} + +// 虚拟币分佣结构体 +type VirtualCoinCommission struct { + Cid string `json:"cid"` // 虚拟币id + Val float64 `json:"val"` // 数量 +} + +// CommissionReq 分佣请求结构体 +type CommissionReq struct { + CommissionParam CommissionParam `json:"commission_param"` + Uid string `json:"uid"` // 用户id + IsShare int `json:"is_share"` // 是否是分享 + Provider string `json:"provider"` // 类型 + IsAllLevelReturn int `json:"is_all_level_return"` // 是否返回所有层级 + GoodsId string `json:"goods_id,omitempty"` // 批量请求用于标记是哪个商品 + OldLv string `json:"old_lv"` + NewLv string `json:"new_lv"` +} + +type CommissionParam struct { + Commission string `json:"commission"` // 总佣金 +} diff --git a/app/md/file.go b/app/md/file.go new file mode 100644 index 0000000..db52eea --- /dev/null +++ b/app/md/file.go @@ -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"` +} diff --git a/app/md/img.go b/app/md/img.go new file mode 100644 index 0000000..97428bf --- /dev/null +++ b/app/md/img.go @@ -0,0 +1,11 @@ +package md + +import ( + "regexp" +) + +func ReplaceImgUrl(val, host string) string { + pattern := "\"([" + `\w` + ".=-\u4e00-\u9fa5]+).(png|jpg|jpeg|gif)" + r := regexp.MustCompile(pattern) + return r.ReplaceAllString(val, `"`+host+"/$1.$2") +} diff --git a/app/md/mod.go b/app/md/mod.go new file mode 100644 index 0000000..382ad66 --- /dev/null +++ b/app/md/mod.go @@ -0,0 +1,51 @@ +package md + +type MultiNav struct { + CateName string `json:"cate_name"` + CateTag string `json:"cate_tag"` + Data struct { + Url string `json:"url"` + AppId string `json:"app_id"` + ActivityId string `json:"activity_id"` + Id string `json:"id"` + AdName string `json:"ad_name"` + AndroidAdID string `json:"android_ad_id"` + AndroidMediaID string `json:"android_media_id"` + AutoClickAd string `json:"auto_click_ad"` + Autoplay string `json:"autoplay"` + BrandID string `json:"brand_id"` + Conditions string `json:"conditions"` + CreateAt string `json:"create_at"` + EndTime string `json:"end_time"` + Img string `json:"img"` + IosAdID string `json:"ios_ad_id"` + IosMediaID string `json:"ios_media_id"` + IsRecommend interface{} `json:"is_recommend"` + LevelLimitID string `json:"level_limit_id"` + LevelLimitName string `json:"level_limit_name"` + LevelWeight string `json:"level_weight"` + NeedLocation int64 `json:"need_location"` + SdkType string `json:"sdk_type"` + SourceType string `json:"source_type"` + StartTime string `json:"start_time"` + UpdateAt string `json:"update_at"` + VisitCount string `json:"visit_count"` + } `json:"data"` + ID string `json:"id"` + Img string `json:"img"` + ImgURL string `json:"img_url"` + Index int64 `json:"index"` + IsEnd string `json:"is_end"` + IsJump string `json:"is_jump"` + Name string `json:"name"` + RequiredLogin string `json:"required_login"` + RequiredTaobaoAuth string `json:"required_taobao_auth"` + RightIcon string `json:"right_icon"` + RightIconURL string `json:"right_icon_url"` + SkipIdentifier string `json:"skip_identifier"` + SkipName string `json:"skip_name"` + SubTitle string `json:"sub_title"` + Title string `json:"title"` + TypeListKey string `json:"type_list_key"` + URL string `json:"url"` +} diff --git a/app/md/platform.go b/app/md/platform.go new file mode 100644 index 0000000..d71a113 --- /dev/null +++ b/app/md/platform.go @@ -0,0 +1,38 @@ +package md + +const ( + /*********** DEVICE ***********/ + PLATFORM_WX_APPLET = "wx_applet" // 小程序 + PLATFORM_TOUTIAO_APPLET = "toutiao_applet" + PLATFORM_TIKTOK_APPLET = "tiktok_applet" + PLATFORM_BAIDU_APPLET = "baidu_applet" + PLATFORM_ALIPAY_APPLET = "alipay_applet" + PLATFORM_WAP = "wap" //h5 + PLATFORM_ANDROID = "android" + PLATFORM_IOS = "ios" + PLATFORM_JSAPI = "jsapi" // 公众号 +) + +const WX_PAY_BROWSER = "wx_pay_browser" // 用于判断显示支付方式 + +var PlatformList = map[string]struct{}{ + PLATFORM_WX_APPLET: {}, + PLATFORM_TOUTIAO_APPLET: {}, + PLATFORM_TIKTOK_APPLET: {}, + PLATFORM_BAIDU_APPLET: {}, + PLATFORM_ALIPAY_APPLET: {}, + PLATFORM_WAP: {}, + PLATFORM_ANDROID: {}, + PLATFORM_IOS: {}, +} + +var PlatformMap = map[string]string{ + "android": "2", + "ios": "2", + "wap": "4", // 和小程序公用模板 + "wx_applet": "4", //微信小程序 + "tiktok_applet": "4", + "baidu_applet": "4", + "alipay_applet": "4", + "toutiao_applet": "4", +} diff --git a/app/md/provider.go b/app/md/provider.go new file mode 100644 index 0000000..90518aa --- /dev/null +++ b/app/md/provider.go @@ -0,0 +1,92 @@ +package md + +const ( + PVD_TB = "taobao" + PVD_JD = "jd" + PVD_SN = "suning" + PVD_VIP = "vip" + PVD_PDD = "pdd" + PVD_KL = "kaola" + PVD_TM = "tmall" + PVD_DTK = "dataoke" + PVD_HDK = "haodanku" + PVD_JTT = "jingtuitui" + // 特殊活动免单方案 + PVD_FREE = "free" + PVD_MEITUAN = "meituan" + PVD_OILSTATION = "oil" + PVD_KFC = "kfc" + PVD_CINEMA = "cinema" + PVD_CREDIT_CARD = "credit_card" + PVD_CARD_REFUND = "card_refund" //权益卡退款 + PVD_CARD = "privilege_card" //权益卡 + PVD_USER_LV_UP = "userlvup" //会员费用 + PVD_OPEN_CARD = "privilege_open_card" // 权益卡开卡 + PVD_DUOMAI = "duomai" // 多麦 + PVD_GUIDE = "GUIDE" //导购 + PVD_SELF_MALL = "SELF_MALL" //自营 + PVD_O2O = "O2O" //O2O + PVD_REGIONAL_AGENT_PAY = "regional_agent_pay" //O2O + PVD_COMMON = "COMMON" //COMMON +) + +var PVD_LIST = map[string]string{ + PVD_TB: "淘宝", + PVD_JD: "京东", + PVD_SN: "苏宁", + PVD_VIP: "唯品会", + PVD_PDD: "拼多多", + PVD_KL: "考拉", + PVD_CREDIT_CARD: "信用卡", + PVD_TM: "天猫", + PVD_USER_LV_UP: "会员升级", + PVD_CARD_REFUND: "权益卡退款", + PVD_CARD: "权益卡", + PVD_MEITUAN: "美团", + PVD_OILSTATION: "加油", + PVD_KFC: "肯德基", + PVD_CINEMA: "电影票", + "ele": "饿了么", + "user_lv_up": "会员升级", +} + +var PVD_LIST_ICON = map[string]string{ + PVD_TB: "provider-square-icon-taobao.png", + PVD_JD: "provider-square-icon-jd.png", + PVD_SN: "provider-square-icon-suning.png", + PVD_VIP: "provider-square-icon-vip.png", + PVD_PDD: "provider-square-icon-pdd.png", + PVD_KL: "provider-square-icon-kaola.png", +} + +var ZHIMENG_CFG_LIST = []string{KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_WEB_ID} + +var PVD_CFG_LIST = map[string][]string{ + PVD_TB: { + "third_taobao_sid", + "third_taobao_web_ak", + "third_taobao_web_sk", + "third_taobao_svc_ak", + "third_taobao_svc_sk", + "third_taobao_svc_sid", + // 推广位 + "third_taobao_share_inviter_code", + "third_taobao_share_pid_android", + "third_taobao_share_pid_ios", + "third_taobao_share_pid_web", + // 推广位 + "third_taobao_self_inviter_code", + "third_taobao_self_pid_android", + "third_taobao_self_pid_ios", + "third_taobao_self_pid_web", + }, + PVD_JD: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_JD_SITE_ID, KEY_CFG_ZM_WEB_ID}, + PVD_VIP: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_JD_SITE_ID, KEY_CFG_ZM_WEB_ID}, + PVD_PDD: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_JD_SITE_ID, KEY_CFG_ZM_WEB_ID}, + PVD_SN: {KEY_CFG_SN_AK, KEY_CFG_SN_SK}, + PVD_KL: {KEY_CFG_KL_AK, KEY_CFG_KL_SK}, + PVD_MEITUAN: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_WEB_ID}, + PVD_OILSTATION: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_WEB_ID}, + PVD_KFC: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_WEB_ID}, + PVD_CINEMA: {KEY_CFG_ZM_AK, KEY_CFG_ZM_SK, KEY_CFG_ZM_SMS_AK, KEY_CFG_ZM_SMS_SK, KEY_CFG_ZM_WEB_ID}, +} diff --git a/app/md/push.go b/app/md/push.go new file mode 100644 index 0000000..865ba1b --- /dev/null +++ b/app/md/push.go @@ -0,0 +1,29 @@ +package md + +type PushParams struct { + Uid int `json:"uid"` + Title string `json:"title"` + DomainWapBase string `json:"domain_wap_base"` + Content string `json:"content"` + Skip string `json:"content"` + PushType string `json:"push_type"` + SkipIdentifier string `json:"skipIdentifier"` + RequiredLogin string `json:"requiredLogin"` + RequiredTaobaoAuth string `json:"requiredTaobaoAuth"` + AppName string `json:"app_name"` //APP名称 + Nickname string `json:"nickname"` //会员昵称 + Payment string `json:"payment"` //付款金额 + OrderNickname string `json:"order_nickname"` //下单人昵称 + OrderNo string `json:"order_no"` //订单号 + OrderTime string `json:"order_time"` //下单时间 + Fcommission string `json:"fcommission"` //预估佣金 + RegNickname string `json:"reg_nickname"` //注册人昵称 + RegPhone string `json:"reg_phone"` //注册人手机号 + LvName string `json:"lv_name"` //升级等级名称 + WithdrawMoney string `json:"withdraw_money"` //提现金额 + Reason string `json:"reason"` //原因 + Money string `json:"money"` //金额 + Times string `json:"times"` //时间 + MasterId string `json:"master_id"` //站长id + +} diff --git a/app/md/smartycustomer.go b/app/md/smartycustomer.go new file mode 100644 index 0000000..4e743a0 --- /dev/null +++ b/app/md/smartycustomer.go @@ -0,0 +1,39 @@ +package md + +// 淘口令 + +type SmartyCustomer struct { + OfficialTel interface{} `json:"official_tel"` + OfficialQq interface{} `json:"official_qq"` + OfficialWx interface{} `json:"official_wx"` + ContactCustomerButton interface{} `json:"contact_customer_button"` + HintLanguage string `json:"hint_language"` +} + +type MallInfo struct { + Name string `json:"name"` + Desc string `json:"desc"` +} + +type ResponseCustomerDetail struct { + SmartyCustomer + MallInfo +} + +//UserProfileResponse is userprofile response +type UserProfileResponse1 struct { + UserName string `json:"username"` + Avatar string `json:"avatar"` + InviteCode string `json:"InviteCode"` + UserLvName string `json:"user_lv_name"` + UserLvIcon string `json:"user_lv_icon"` + IsTaobaoAuth bool `json:"is_taobao_auth"` + IsWxTaobaoAuth bool `json:"is_wx_taobao_auth"` + GridViews []UserProfileResponseGridView1 `json:"grid_views"` +} + +//UserProfileResponseGridView is for UserProfileResponse +type UserProfileResponseGridView1 struct { + Name string `json:"name"` + Value string `json:"value"` +} \ No newline at end of file diff --git a/app/md/split_db.go b/app/md/split_db.go new file mode 100644 index 0000000..f60d962 --- /dev/null +++ b/app/md/split_db.go @@ -0,0 +1,42 @@ +package md + +import ( + "regexp" + + "xorm.io/xorm" +) + +type DbInfo struct { + User string + Psw string + Name string + Host string +} + +func SplitDbInfo(eg *xorm.Engine) *DbInfo { + if eg == nil { + return &DbInfo{ + User: "nil", + Psw: "nil", + Host: "nil", + Name: "nil", + } + } + pattern := `(\w+):(.*)@tcp\(([\w\.\-\:\_]+)\)\/(\w+)` + reg := regexp.MustCompile(pattern).FindStringSubmatch(eg.DataSourceName()) + + if len(reg) < 5 { + return &DbInfo{ + User: "unknown", + Psw: "unknown", + Host: "unknown", + Name: "unknown", + } + } + return &DbInfo{ + User: reg[1], + Psw: reg[2], + Host: reg[3], + Name: reg[4], + } +} diff --git a/app/md/url.go b/app/md/url.go new file mode 100644 index 0000000..ffd5743 --- /dev/null +++ b/app/md/url.go @@ -0,0 +1,27 @@ +package md + +type ExtraData struct { + UnionId string `json:"unionId"` + Tc1 string `json:"tc1"` + Tc2 string `json:"tc2"` +} + +// 转链后链接 +type ConvertedUrls struct { + ItemId string `json:"item_id"` + URL string `json:"url"` // 短链接 + ShortenURL string `json:"open_app_url"` // 会打开app的长链接 + NoOpenAppURL string `json:"no_open_app_url"` // 不会打开app的长链接 + AppURL string `json:"app_url"` // app 链接 pinduoduo:// + HasCoupon bool `json:"has_coupon"` // 是否有优惠券 + CommissionRate string `json:"commission_rate"` // 利润比例 + ShareURL string `json:"share_url"` // 分享的URL + WeChatTaobaoURL string `json:"wechat_taobao_url"` // 淘宝分享到微信用的url + WeChatMiniURL string `json:"wechat_mini_url"` // 微信小程序转链地址 + WeChatMiniAPPID string `json:"wechat_mini_appid"` // 微信appid + PID string `json:"pid"` // 推广位 + PvdId string `json:"pvd_id"` // 供应商联盟ID + TaoBaoWord string `json:"taobao_word"` // 淘口令TaoBaoWord string `json:"taobao_word"` // 淘口令 + ExtraData ExtraData `json:"extraData"` //考拉用来跟踪用户的 + TbShareId int64 `json:"tb_share_id"` //淘宝活动时的渠道id 组合方式 +} diff --git a/app/md/user_info.go b/app/md/user_info.go new file mode 100644 index 0000000..b76da41 --- /dev/null +++ b/app/md/user_info.go @@ -0,0 +1,33 @@ +package md + +import ( + "applet/app/db/model" + "applet/app/lib/arkid" +) + +type UserInfoResponse struct { + Avatar string `json:"avatar"` + NickName string `json:"nickname"` + Gender string `json:"gender"` + Birthday string `json:"birthday"` + RegisterTime string `json:"register_time"` + FileBucketURL string `json:"file_bucket_url"` + FileFormat string `json:"file_format"` + IsNoChange string `json:"is_no_change"` + IsUpLoadWx string `json:"is_upload_wx"` +} + +type User struct { + Ark *arkid.ArkIDUser + Info *model.User + Profile *model.UserProfile + Level *model.UserLevel + Tags []string +} + +type UserRelation struct { + Uid int + CurUid int + Diff int // 与当前用户级别差 + Level int // 用户当前等级 +} diff --git a/app/md/userprofile.go b/app/md/userprofile.go new file mode 100644 index 0000000..ab33001 --- /dev/null +++ b/app/md/userprofile.go @@ -0,0 +1,19 @@ +package md + +//UserProfileResponse is userprofile response +type UserProfileResponse struct { + UserName string `json:"username"` + Avatar string `json:"avatar"` + InviteCode string `json:"InviteCode"` + UserLvName string `json:"user_lv_name"` + UserLvIcon string `json:"user_lv_icon"` + IsTaobaoAuth bool `json:"is_taobao_auth"` + IsWxTaobaoAuth bool `json:"is_wx_taobao_auth"` + GridViews []UserProfileResponseGridView `json:"grid_views"` +} + +//UserProfileResponseGridView is for UserProfileResponse +type UserProfileResponseGridView struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/app/md/word.go b/app/md/word.go new file mode 100644 index 0000000..b5ddc4c --- /dev/null +++ b/app/md/word.go @@ -0,0 +1,11 @@ +package md + +// 淘口令 + +type TaobaoWord struct { + Text string // 文本 + Code string // 短码 +} +type TaobaoWordGid struct { + Gid string // 商品id +} diff --git a/app/mw/mw_access_log.go b/app/mw/mw_access_log.go new file mode 100644 index 0000000..84f6b52 --- /dev/null +++ b/app/mw/mw_access_log.go @@ -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), + ) +} diff --git a/app/mw/mw_auth.go b/app/mw/mw_auth.go new file mode 100644 index 0000000..645dbe3 --- /dev/null +++ b/app/mw/mw_auth.go @@ -0,0 +1,72 @@ +package mw + +import ( + "errors" + + "applet/app/db" + "applet/app/e" + "applet/app/lib/arkid" + "applet/app/md" + "applet/app/utils" + + "github.com/gin-gonic/gin" +) + +// 检查权限, 签名等等 +func Auth(c *gin.Context) { + + for k, v := range c.Request.Header { + c.Set(k, v[0]) + } + token, ok := c.Get("Token") + if !ok { + e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("没有找到token")) + return + } + if token == "" { + e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 不能为空")) + return + } + tokenStr := utils.AnyToString(token) + arkIdSdk := arkid.NewArkID() + var err error + signUser := &md.User{} + arkIdUser := new(arkid.ArkIDUser) + if err = arkIdSdk.SelectFunction("arkid_user_info"). + WithArgs(arkid.RequestBody{Token: tokenStr}). + Result(arkIdUser); err != nil { + e.OutErr(c, e.ERR_TOKEN_AUTH, err) //token 不存在 + return + } + if arkIdUser.Username == "" { + e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("Token error")) + return + } + if err = arkIdSdk.SelectFunction("arkid_login"). + WithArgs(arkid.RequestBody{Username: arkIdUser.Username, Password: utils.Md5(arkIdUser.Username)}). + Result(arkIdUser); err != nil { + e.OutErr(c, e.ERR_TOKEN_AUTH, err) + return + } + signUser.Ark = arkIdUser + if signUser.Ark == nil { + e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("无效token")) + return + } + signUser.Info, err = db.UserFindByArkidUserName(db.DBs[c.GetString("mid")], arkIdUser.Username) + if err != nil { + e.OutErr(c, e.ERR_TOKEN_AUTH, err) + return + } + if signUser.Info == nil { + e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("无效token")) + return + } + signUser.Profile, err = db.UserProfileFindByArkID(db.DBs[c.GetString("mid")], utils.IntToStr(arkIdUser.UserID)) + if err != nil { + e.OutErr(c, e.ERR_TOKEN_AUTH, err) + return + } + c.Set("user", signUser) + c.Next() +} diff --git a/app/mw/mw_auth_jwt.go b/app/mw/mw_auth_jwt.go new file mode 100644 index 0000000..7e9638b --- /dev/null +++ b/app/mw/mw_auth_jwt.go @@ -0,0 +1,96 @@ +package mw + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/md" + "applet/app/utils" + "applet/app/utils/cache" + "applet/app/utils/logx" + "errors" + "fmt" + "strings" + + "github.com/gin-gonic/gin" +) + +// AuthJWT is jwt middleware +func AuthJWT(c *gin.Context) { + + authHeader := c.Request.Header.Get("Authorization") + if authHeader == "" { + e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 不能为空")) + return + } + + // 按空格分割 + parts := strings.SplitN(authHeader, " ", 2) + if !(len(parts) == 2 && parts[0] == "Bearer") { + e.OutErr(c, e.ERR_TOKEN_FORMAT, errors.New("token 格式不对")) + return + } + // parts[1]是token + mc, err := utils.ParseToken(parts[1]) + if err != nil { + e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 过期或无效")) + return + } + //fmt.Println(mc.UID) + // 获取user + u, err := db.UserFindByID(db.DBs[c.GetString("mid")], mc.UID) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if u == nil { + e.OutErr(c, e.ERR_UNAUTHORIZED, errors.New("token 过期或无效")) + return + } + // 检验账号是否未激活或被冻结 + switch u.State { + case 0: + e.OutErr(c, e.ERR_USER_NO_ACTIVE) + return + case 2: + e.OutErr(c, e.ERR_USER_IS_BAN) + return + } + + // 校验是否和缓存的token一致,只能有一个token 是真实有效 + key := fmt.Sprintf("%s:token:%s", c.GetString("mid"), u.Username) + //fmt.Println(key) + cjwt, err := cache.GetString(key) + //fmt.Println(cjwt) + if err != nil { + logx.Warn(err) + goto NOCACHE + } + if parts[1] != cjwt { + e.OutErr(c, e.ERR_TOKEN_AUTH, errors.New("token expired")) + return + } +NOCACHE: + // 获取user profile + up, err := db.UserProfileFindByID(db.DBs[c.GetString("mid")], mc.UID) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + // 获取user 等级 + ul, err := db.UserLevelByID(db.DBs[c.GetString("mid")], u.Level) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + user := &md.User{ + Info: u, + Profile: up, + Level: ul, + } + + // 将当前请求的username信息保存到请求的上下文c上 + c.Set("user", user) + // 异步处理 有效会员和新会员 + c.Next() // 后续的处理函数可以用过c.Get("user")来获取当前请求的用户信息 + +} diff --git a/app/mw/mw_breaker.go b/app/mw/mw_breaker.go new file mode 100644 index 0000000..fefc078 --- /dev/null +++ b/app/mw/mw_breaker.go @@ -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 + }) +} diff --git a/app/mw/mw_change_header.go b/app/mw/mw_change_header.go new file mode 100644 index 0000000..4a5aefa --- /dev/null +++ b/app/mw/mw_change_header.go @@ -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() +} diff --git a/app/mw/mw_check_sign.go b/app/mw/mw_check_sign.go new file mode 100644 index 0000000..e3bf3c2 --- /dev/null +++ b/app/mw/mw_check_sign.go @@ -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() +} diff --git a/app/mw/mw_checker.go b/app/mw/mw_checker.go new file mode 100644 index 0000000..44ee434 --- /dev/null +++ b/app/mw/mw_checker.go @@ -0,0 +1,22 @@ +package mw + +import ( + "strings" + + "github.com/gin-gonic/gin" + + "applet/app/e" + "applet/app/md" +) + +// 检查设备等, 把头部信息下放到hdl可以获取 +func Checker(c *gin.Context) { + // 校验平台支持 + platform := strings.ToLower(c.GetHeader("Platform")) + //fmt.Println(platform) + if _, ok := md.PlatformList[platform]; !ok { + e.OutErr(c, e.ERR_PLATFORM) + return + } + c.Next() +} diff --git a/app/mw/mw_cors.go b/app/mw/mw_cors.go new file mode 100644 index 0000000..3433553 --- /dev/null +++ b/app/mw/mw_cors.go @@ -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() +} diff --git a/app/mw/mw_csrf.go b/app/mw/mw_csrf.go new file mode 100644 index 0000000..b15619b --- /dev/null +++ b/app/mw/mw_csrf.go @@ -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 +} diff --git a/app/mw/mw_db.go b/app/mw/mw_db.go new file mode 100644 index 0000000..d1af0cf --- /dev/null +++ b/app/mw/mw_db.go @@ -0,0 +1,93 @@ +package mw + +import ( + "applet/app/svc" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "strings" + + "applet/app/db" + "applet/app/e" + "applet/app/md" +) + +// 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 + if masterID == "" { + platform := c.GetHeader("Platform") + if platform == md.PLATFORM_WAP { + // H5 要根据域名去获取mid + hostList := strings.Split(c.Request.Host, ".") + if len(hostList) == 4 && (hostList[2]+"."+hostList[3] == "zhiyingos.com" || hostList[2]+"."+hostList[3] == "izhyin.com") { + // 官方域名 + masterID = hostList[1] + } else { + // 自定义域名 + masterID = svc.GetWebSiteDomainMasterId(md.PLATFORM_WAP, c.Request.Host) + } + //requestURL := cfg.Official.URL + "/api/user/check" + //fmt.Println(c.Request.Host) + //client := &http.Client{ + // Timeout: time.Duration(time.Second * 2), + //} + //data := []byte(fmt.Sprintf(`{"domain":"%s"}`, c.Request.Host)) + //body := bytes.NewReader(data) + //request, err := http.NewRequest("POST", requestURL, body) + //if err != nil { + // e.OutErr(c, e.ERR_MASTER_ID, errors.New("not found master_id in DBs")) + // return + //} + //request.Header.Set("Content-Type", "application/json;charset=UTF-8") + //resp, err := client.Do(request.WithContext(context.TODO())) + //if err != nil { + // e.OutErr(c, e.ERR_MASTER_ID, err) + // return + //} + //defer resp.Body.Close() + //respBytes, err := ioutil.ReadAll(resp.Body) + //if err != nil { + // e.OutErr(c, e.ERR_MASTER_ID, err) + // return + //} + //mid := gjson.GetBytes(respBytes, "data.db_master_id").String() + //if mid == "" { + // e.OutErr(c, e.ERR_MASTER_ID, errors.New("not found master_id in DBs")) + // return + //} + //masterID = mid + } + } + } + + _, ok := db.DBs[masterID] + if !ok { + e.OutErr(c, e.ERR_MASTER_ID, errors.New("not found master_id in DBs")) + return + } + fmt.Println("master_id", masterID) + c.Set("mid", masterID) + //判断是否有独立域名 + domain_wap_base := svc.GetWebSiteDomainInfo(c, "wap") + + httpStr := "http://" + if c.GetHeader("Platform") == md.PLATFORM_WX_APPLET || c.GetHeader("Platform") == md.PLATFORM_ALIPAY_APPLET || c.GetHeader("Platform") == md.PLATFORM_BAIDU_APPLET || c.GetHeader("Platform") == md.PLATFORM_TOUTIAO_APPLET || c.GetHeader("Platform") == md.PLATFORM_TIKTOK_APPLET { + httpStr = "https://" + domain_wap_base = strings.Replace(domain_wap_base, "http://", httpStr, 1) + } + c.Set("domain_wap_base", domain_wap_base) + c.Set("http_host", httpStr) + + c.Set("h5_api_secret_key", svc.SysCfgGet(c, "h5_api_secret_key")) + c.Set("app_api_secret_key", svc.SysCfgGet(c, "app_api_secret_key")) + c.Set("applet_api_secret_key", svc.SysCfgGet(c, "applet_api_secret_key")) + c.Next() +} diff --git a/app/mw/mw_limiter.go b/app/mw/mw_limiter.go new file mode 100644 index 0000000..4eb5299 --- /dev/null +++ b/app/mw/mw_limiter.go @@ -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) +} diff --git a/app/mw/mw_recovery.go b/app/mw/mw_recovery.go new file mode 100644 index 0000000..b32cc82 --- /dev/null +++ b/app/mw/mw_recovery.go @@ -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() + } +} diff --git a/app/pay/hdl/hdl_pay.go b/app/pay/hdl/hdl_pay.go new file mode 100644 index 0000000..8f561cf --- /dev/null +++ b/app/pay/hdl/hdl_pay.go @@ -0,0 +1,36 @@ +package hdl + +import ( + "applet/app/e" + "applet/app/pay/svc" + "github.com/gin-gonic/gin" +) + +// Pay 整合所有支付 +func Pay(c *gin.Context) { + orderType := c.Param("orderType") + payMethod := c.Param("payMethod") + if orderType == "" || payMethod == "" { + e.OutErr(c, e.ERR_INVALID_ARGS) + return + } + payFunc, ok := svc.PayFuncList[orderType][payMethod] + if !ok || payFunc == nil { + e.OutErr(c, e.ERR, e.NewErr(500, "不存在该支付方式")) + return + } + r, err := payFunc(c) + if err != nil { + switch err.(type) { + case e.E: + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + default: + e.OutErr(c, e.ERR_PAY_ERR, e.NewErr(e.ERR_PAY_ERR, err.Error())) + return + } + } + e.OutSuc(c, r, nil) + return +} diff --git a/app/pay/hdl/hdl_pay_callback.go b/app/pay/hdl/hdl_pay_callback.go new file mode 100644 index 0000000..cd282b3 --- /dev/null +++ b/app/pay/hdl/hdl_pay_callback.go @@ -0,0 +1,40 @@ +package hdl + +import ( + "applet/app/e" + "applet/app/pay/svc" + "applet/app/utils" + "applet/app/utils/logx" + "errors" + "fmt" + "github.com/gin-gonic/gin" +) + +// PayCallback 整合所有支付回调 +func PayCallback(c *gin.Context) { + fmt.Println("pay call back start >>>>>>>>>>") + // 统一处理回调参数 + data, orderType, payMethod := svc.CheckAllCallbackParams(c) + if data == nil { + fmt.Println("pay call back data is nil >>>>>>>>>>") + e.OutErr(c, e.ERR_INVALID_ARGS) + return + } + fmt.Println("pay call back data >>>>>>>>>>", utils.SerializeStr(data)) + + c.Set("callback", data) + payCallbackFunc, ok := svc.PayCallbackFuncList[orderType][payMethod] + if !ok || payCallbackFunc == nil { + _ = logx.Warn(errors.New("回调不存在")) + return + } + fmt.Println("pay call back start1 >>>>>>>>>>") + payCallbackFunc(c) + //e.OutSuc(c, "success", nil) + //return + fmt.Println("pay call back end >>>>>>>>>>") + + //TODO::此处需要直接输出 success,避免支付宝重复回调 + //c.Header("Content-Type", "text/html; charset=utf-8") + c.String(200, "success") +} diff --git a/app/pay/hdl/hdl_pay_status.go b/app/pay/hdl/hdl_pay_status.go new file mode 100644 index 0000000..ca96357 --- /dev/null +++ b/app/pay/hdl/hdl_pay_status.go @@ -0,0 +1,24 @@ +package hdl + +import ( + "applet/app/e" + "applet/app/pay/svc" + "github.com/gin-gonic/gin" +) + +// PayStatus 整合所有支付状态 +func PayStatus(c *gin.Context) { + orderType := c.Param("orderType") + //payMethod := c.Param("payMethod") + if orderType == "" { + e.OutErr(c, e.ERR_INVALID_ARGS) + return + } + r, ok := svc.PayStatusFuncList[orderType] + if !ok { + e.OutErr(c, e.ERR, e.NewErr(500, "不存在该支付方式")) + return + } + e.OutSuc(c, r, nil) + return +} diff --git a/app/pay/md/alipay.go b/app/pay/md/alipay.go new file mode 100644 index 0000000..d7b12e4 --- /dev/null +++ b/app/pay/md/alipay.go @@ -0,0 +1,48 @@ +package md + +// AliPayCallback 支付宝的回调结构体 +type AliPayCallback struct { + AppID string `json:"app_id"` + AuthAppID string `json:"auth_app_id"` + BuyerID string `json:"buyer_id"` + BuyerLogonID string `json:"buyer_logon_id"` + BuyerPayAmount string `json:"buyer_pay_amount"` + Charset string `json:"charset"` + FundBillList string `json:"fund_bill_list"` + GmtCreate string `json:"gmt_create"` + GmtPayment string `json:"gmt_payment"` + InvoiceAmount string `json:"invoice_amount"` + OrderType string `json:"order_type"` + MasterID string `json:"master_id"` + NotifyID string `json:"notify_id"` + NotifyTime string `json:"notify_time"` + NotifyType string `json:"notify_type"` + OutTradeNo string `json:"out_trade_no"` + PassbackParams string `json:"passback_params"` + PointAmount string `json:"point_amount"` + ReceiptAmount string `json:"receipt_amount"` + SellerEmail string `json:"seller_email"` + SellerID string `json:"seller_id"` + Sign string `json:"sign"` + SignType string `json:"sign_type"` + Subject string `json:"subject"` + TotalAmount string `json:"total_amount"` + TradeNo string `json:"trade_no"` + TradeStatus string `json:"trade_status"` + Version string `json:"version"` + PayMethod string `json:"pay_method"` +} + +type AliPayPayParams struct { + Subject string `json:"subject" binding:"required"` + Amount string `json:"amount" binding:"required"` + OrderType string `json:"order_type" binding:"required"` + OrdId string `json:"ord_id"` +} +type PayData struct { + PayAppCertSn string `json:"pay_app_cert_sn"` + PayAlipayRootCertSn string `json:"pay_alipay_root_cert_sn"` + PayAlipayrsaPublicKey string `json:"pay_alipayrsa_public_key"` + PayAliUseType string `json:"pay_ali_use_type"` + PriKey string `json:"pay_ali_new_private_key"` +} diff --git a/app/pay/md/pay.go b/app/pay/md/pay.go new file mode 100644 index 0000000..20ccb4d --- /dev/null +++ b/app/pay/md/pay.go @@ -0,0 +1,70 @@ +package md + +const ( + CALLBACK_URL = "%s/api/v1/mall/pay/callback?master_id=%s&order_type=%s&pay_method=%s" + BALANCE_PAY = "balance_pay" + ALIPAY = "alipay" + WX_PAY = "wxpay" + SecKillStockLock = "%s:mall_sec:%s:%s" // 秒杀库存锁Redis Key +) + +//订单类型:1普通订单 2拼团订单 3秒杀 4超级拼团 +const ( + ORDINARY_ORDER_TYPE = 1 + GROUP_BUY_ORDER_TYPE = 2 + SEC_KILL_ORDER_TYPE = 3 + SUPER_GROUP_BUY_ORDER_TYPE = 4 +) + +//团购购买方式 `open_group_buy_type` : 开团购买 , `add_group_buy_type` : `参团购买` +const ( + OPEN_GROUP_BUY_TYPE = "open_group_buy_type" + ADD_GROUP_BUY_TYPE = "add_group_buy_type" +) + +const MallSecondsKillBuy = "mall_seconds_kill_buy" //秒杀活动 +const MallGroupBuyActivity = "mall_group_buy" //团购活动 +const MallBargainingBuyActivity = "mall_bargaining_buy" //砍价活动 +const SuperGroupActivity = "super_group_buy" // 超级拼团 +const MallGoodsActivityVipGift = "mall_goods_activity_vip_gift" // 会员礼包 + +var CouponSpecialActivity = map[string]int{ + MallSecondsKillBuy: 2, + MallGroupBuyActivity: 1, + MallBargainingBuyActivity: 3, + SuperGroupActivity: 4, + MallGoodsActivityVipGift: 5, +} + +// PayMethod 支付方式名称 +var PayMethod = map[string]string{ + BALANCE_PAY: "余额支付", + ALIPAY: "支付宝支付", + WX_PAY: "微信支付", +} + +// PayMethodIDs 支付方式ID +var PayMethodIDs = map[string]int{ + BALANCE_PAY: 1, + ALIPAY: 2, + WX_PAY: 3, +} + +// MallGoods 支付类型 +const ( + MallGoods = "mall_goods" // 直接提交订单支付(多种商品) + MallGoodsSub = "mall_goods_sub" // 从待支付订单支付(一种商品) + MallGroupBuy = "mall_group_buy" // 从拼团提交订单支付(一种商品) + SuperGroupBuy = "super_group_buy" // 超级拼团提交(一种商品) + MallSecondsKill = "mall_seconds_kill" // 从秒杀提交订单支付(一种商品) +) + +// NeedPayPart 支付类型名称 +var NeedPayPart = map[string]string{ + MallGoods: "自营商城商品", + MallGoodsSub: "自营商城商品", + MallGroupBuy: "拼团购买商品", + SuperGroupBuy: "超级拼团购买商品", + MallSecondsKill: "秒杀购买商品", + ALIPAY: "自营商城商品购物", +} diff --git a/app/pay/md/wxpay.go b/app/pay/md/wxpay.go new file mode 100644 index 0000000..88a9f8d --- /dev/null +++ b/app/pay/md/wxpay.go @@ -0,0 +1,30 @@ +package md + +type WxPayParams struct { + Subject string `json:"subject" binding:"required"` + Amount string `json:"amount" binding:"required"` + OrderType string `json:"order_type" binding:"required"` + OrdId string `json:"ord_id"` +} + +type WxPayCallback struct { + AppId string `json:"appid"` + BankType string `json:"bank_type"` + CashFee string `json:"cash_fee"` + FeeType string `json:"fee_type"` + IsSubscribe string `json:"is_subscribe"` + MasterID string `json:"master_id"` + MchID string `json:"mch_id"` + NonceStr string `json:"nonce_str"` + Openid string `json:"openid"` + OrderType string `json:"order_type"` + OutTradeNo string `json:"out_trade_no"` + PayMethod string `json:"pay_method"` + ResultCode string `json:"result_code"` + ReturnCode string `json:"return_code"` + Sign string `json:"sign"` + TimeEnd string `json:"time_end"` + TotalFee string `json:"total_fee"` + TradeType string `json:"trade_type"` + TransactionID string `json:"transaction_id"` +} diff --git a/app/pay/svc/svc_alipay_callback.go b/app/pay/svc/svc_alipay_callback.go new file mode 100644 index 0000000..77b9227 --- /dev/null +++ b/app/pay/svc/svc_alipay_callback.go @@ -0,0 +1,28 @@ +package svc + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/pay/md" + "applet/app/utils/logx" + "github.com/gin-gonic/gin" +) + +// 支付宝回调处理 +func AlipayCallback(c *gin.Context) (string, error) { + data, ok := c.Get("callback") + if data == nil || !ok { + return "", e.NewErrCode(e.ERR_INVALID_ARGS) + } + args := data.(*md.AliPayCallback) + _, ok = db.DBs[args.MasterID] + if !ok { + return "", logx.Warn("Alipay Failed : master_id not found") + } + c.Set("mid", args.MasterID) + // 回调交易状态失败 + if args.TradeStatus != "TRADE_SUCCESS" { + return "", logx.Warn("Alipay Failed : trade status failed") + } + return args.OutTradeNo, nil +} diff --git a/app/pay/svc/svc_alipay_pay.go b/app/pay/svc/svc_alipay_pay.go new file mode 100644 index 0000000..18e72e9 --- /dev/null +++ b/app/pay/svc/svc_alipay_pay.go @@ -0,0 +1,62 @@ +package svc + +import ( + "applet/app/e" + "applet/app/lib/alipay" + mdComm "applet/app/md" + "applet/app/pay/md" + svcComm "applet/app/svc" + "applet/app/utils" + "fmt" + "github.com/gin-gonic/gin" + "strings" +) + +// PrepareAlipayCode 生成支付参数 +func PrepareAlipayCode(c *gin.Context, p *md.AliPayPayParams) (string, error) { + // 获取私钥和APPID + privateKey := svcComm.SysCfgGet(c, "pay_ali_private_key") + appID := svcComm.SysCfgGet(c, "pay_ali_app_id") + rsa := svcComm.SysCfgGet(c, "pay_ali_key_len_type") + pkcs := svcComm.SysCfgGet(c, "pay_ali_key_format_type") + var paySet = &md.PayData{ + PayAppCertSn: svcComm.SysCfgGet(c, "pay_app_cert_sn"), + PayAlipayRootCertSn: svcComm.SysCfgGet(c, "pay_alipay_root_cert_sn"), + PayAlipayrsaPublicKey: svcComm.SysCfgGet(c, "pay_alipayrsa_public_key"), + PayAliUseType: svcComm.SysCfgGet(c, "pay_ali_use_type"), + PriKey: svcComm.SysCfgGet(c, "pay_ali_new_private_key"), + } + if paySet.PayAliUseType == "1" { + privateKey = paySet.PriKey + appID = svcComm.SysCfgGet(c, "pay_ali_new_app_id") + } + if privateKey == "" || appID == "" { + return "", e.NewErrCode(e.ERR_ALIPAY_SETTING) + } + reqHost := c.Request.Host + if strings.Contains(reqHost, "zhios-mall:5002") { // if is inner addr, change to outside + reqHost = "api.zhiyingos.com" + } + notifyURL := fmt.Sprintf(md.CALLBACK_URL, reqHost, c.GetString("mid"), p.OrderType, md.ALIPAY) + // switch判断类型支付 pzy + platform := c.GetHeader("Platform") + page_url := c.Query("page_url") + + var param interface{} + var err error + switch platform { + case mdComm.PLATFORM_ALIPAY_APPLET: + param, err = alipay.TradeCreate(appID, privateKey, p.Subject, p.OrdId, p.Amount, notifyURL, rsa, pkcs, paySet) + case mdComm.PLATFORM_WAP: + param, err = alipay.TradeWapPay(appID, privateKey, p.Subject, p.OrdId, p.Amount, notifyURL, rsa, pkcs, page_url, paySet) + case mdComm.PLATFORM_ANDROID, mdComm.PLATFORM_IOS: + param, err = alipay.TradeAppPay(appID, privateKey, p.Subject, p.OrdId, p.Amount, notifyURL, rsa, pkcs, paySet) + default: + return "", e.NewErrCode(e.ERR_PLATFORM) + } + //param, err = alipay.TradeAppPay(appID, privateKey, p.Subject, p.OrdId, p.Amount, notifyURL) + if err != nil { + return "", e.NewErrCode(e.ERR_ALIPAY_ORDER_ERR) + } + return utils.AnyToString(param), nil +} diff --git a/app/pay/svc/svc_balance_pay.go b/app/pay/svc/svc_balance_pay.go new file mode 100644 index 0000000..f16ab21 --- /dev/null +++ b/app/pay/svc/svc_balance_pay.go @@ -0,0 +1,10 @@ +package svc + +import ( + "github.com/gin-gonic/gin" +) + +// BalancePay 余额支付 +func BalancePay(c *gin.Context, money string, types string, ordId int64) error { +return nil +} diff --git a/app/pay/svc/svc_pay.go b/app/pay/svc/svc_pay.go new file mode 100644 index 0000000..a89a725 --- /dev/null +++ b/app/pay/svc/svc_pay.go @@ -0,0 +1,146 @@ +package svc + +import ( + "applet/app/pay/md" + svcComm "applet/app/svc" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "encoding/xml" + "fmt" + "github.com/gin-gonic/gin" + "github.com/iGoogle-ink/gopay" + "io/ioutil" + "net/url" +) + +// PayFuncList 支付参数 +var PayFuncList = map[string]map[string]func(*gin.Context) (interface{}, error){ + +} + +// PayCallbackFuncList 回调 +var PayCallbackFuncList = map[string]map[string]func(*gin.Context){ + +} + +// PayRefundFuncList 退款 +var PayRefundFuncList = map[string]map[string]func(*gin.Context, string){ + // 自营商品商品 + md.MallGoods: { + md.BALANCE_PAY: nil, + md.ALIPAY: nil, + md.WX_PAY: nil, + }, +} + +// PayStatusFuncList 支付状态 +var PayStatusFuncList = map[string]func(*gin.Context) (interface{}, error){ + // 商学院 + // md.BusinessCollege: PayStatus, +} + +// GetAllPayMethodList 获取支付列表 +func GetAllPayMethodList(c *gin.Context) map[string]map[string]string { + user := svcComm.GetUser(c) + return map[string]map[string]string{ + md.BALANCE_PAY: { + "name": md.PayMethod[md.BALANCE_PAY], + "sub_name": "当前余额:¥" + user.Profile.FinValid, + "type": md.BALANCE_PAY, + "icon": svcComm.OffImageFormat(c, "balance.png"), + }, + md.WX_PAY: { + "name": md.PayMethod[md.WX_PAY], + "sub_name": "使用微信支付", + "type": md.WX_PAY, + "icon": svcComm.OffImageFormat(c, "wx_pay.png"), + }, + md.ALIPAY: { + "name": md.PayMethod[md.ALIPAY], + "sub_name": "使用支付宝支付", + "type": md.ALIPAY, + "icon": svcComm.OffImageFormat(c, "alipay.png"), + }, + } +} + +// GetAllPayMethodArray 获取支付列表--接口用 +func GetAllPayMethodArray(c *gin.Context) []map[string]string { + user := svcComm.GetUser(c) + return []map[string]string{ + { + "name": md.PayMethod[md.BALANCE_PAY], + "sub_name": "当前余额:¥" + user.Profile.FinValid, + "type": md.BALANCE_PAY, + "icon": svcComm.ImageFormat(c, "balance.png"), + }, + { + "name": md.PayMethod[md.WX_PAY], + "sub_name": "使用微信支付", + "type": md.WX_PAY, + "icon": svcComm.ImageFormat(c, "wx_pay.png"), + }, + { + "name": md.PayMethod[md.ALIPAY], + "sub_name": "使用支付宝支付", + "type": md.ALIPAY, + "icon": svcComm.ImageFormat(c, "alipay.png"), + }, + } +} + +func CheckAllCallbackParams(c *gin.Context) (interface{}, string, string) { + body, _ := ioutil.ReadAll(c.Request.Body) + dataAlipay, _ := GetAlipayCallbackParams(body) + if dataAlipay != nil && dataAlipay.PayMethod == md.ALIPAY { + return dataAlipay, dataAlipay.OrderType, dataAlipay.PayMethod + } + dataWxPay, _ := GetWxPayCallbackParams(body) + if dataWxPay != nil && dataWxPay.PayMethod == md.WX_PAY { + return dataWxPay, dataWxPay.OrderType, dataWxPay.PayMethod + } + return nil, "", "" +} + +// GetAlipayCallbackParams 支付宝参数解析 +func GetAlipayCallbackParams(body []byte) (*md.AliPayCallback, error) { + //decodeArgs := "order_type=privilege_card&pay_method=alipay&master_id=123456&gmt_create=2021-01-06+15%3A33%3A18&charset=utf-8&seller_email=1666296478%40qq.com&subject=%E6%B5%8B%E8%AF%95&sign=ILbQtP7E51hcdKaroi%2FKlefltw%2BaSOeaqgvBhYIcIuRy5yy440OIOdxPEvhTITA%2BHnA1Lgf3STepzzHVmvvTFIGeKL%2FVvz%2FLSI7vXGpUBxcw2entVZCzhmblGAB8hiK4EOHBIfUAQiyo9ePWl63p%2B%2BvS4CQuZe8SFXrFaj2bTnRyBPemxekudU8TP8tZd630SzHx7kOHYqxTYZ7kHMrLK0fjIdSWBHtSD2cocm%2FxVQ3wYXUyyfZwwTpcXLW9ao97Uj1hZSnknuFRtMfvE57D5f4W3cB%2Bnp%2B39xpuvfbBlbXXllzKUfkWVBqV6zebYMnzaogwVN%2FDXgp74BQOzkJYvA%3D%3D&buyer_id=2088012352245491&invoice_amount=0.01¬ify_id=2021010600222153319045491449160553&fund_bill_list=%5B%7B%22amount%22%3A%220.01%22%2C%22fundChannel%22%3A%22PCREDIT%22%7D%5D¬ify_type=trade_status_sync&trade_status=TRADE_SUCCESS&receipt_amount=0.01&app_id=2016120103683451&buyer_pay_amount=0.01&sign_type=RSA2&seller_id=2088221294035253&gmt_payment=2021-01-06+15%3A33%3A18¬ify_time=2021-01-06+15%3A33%3A19&passback_params=706160991839039994&version=1.0&out_trade_no=791161468268877545&total_amount=0.01&trade_no=2021010622001445491401511921&auth_app_id=2016120103683451&buyer_logon_id=150****0420&point_amount=0.00" + decodeArgs, err := url.QueryUnescape(string(body)) + if err != nil { + _ = logx.Warn(err) + return nil, logx.Warn("回调参数解码错误") + } + data, err := url.ParseQuery(decodeArgs) + if err != nil { + return nil, err + } + dataMap := make(map[string]interface{}) + for k := range data { + dataMap[k] = data.Get(k) + } + callbackStr := utils.Serialize(dataMap) + fmt.Println("支付宝回调数据", string(callbackStr)) + var args md.AliPayCallback + if err := json.Unmarshal(callbackStr, &args); err != nil { + return nil, logx.Warn(err) + } + return &args, nil +} + +// GetWxPayCallbackParams 微信回调参数解析 +func GetWxPayCallbackParams(body []byte) (*md.WxPayCallback, error) { + dataMap := make(gopay.BodyMap) + if err := xml.Unmarshal(body, &dataMap); err != nil { + return nil, fmt.Errorf("xml.Unmarshal(%s):%w", string(body), err) + } + callbackStr := utils.Serialize(dataMap) + fmt.Println("微信回调数据", string(callbackStr)) + var args md.WxPayCallback + err := json.Unmarshal(callbackStr, &args) + if err != nil { + fmt.Println("err3") + return nil, logx.Warn(err) + } + return &args, nil +} diff --git a/app/pay/svc/svc_wxpay.go b/app/pay/svc/svc_wxpay.go new file mode 100644 index 0000000..2e775cc --- /dev/null +++ b/app/pay/svc/svc_wxpay.go @@ -0,0 +1,140 @@ +package svc + +import ( + "applet/app/e" + "applet/app/lib/wxpay" + "applet/app/pay/md" + svcComm "applet/app/svc" + + "fmt" + "github.com/gin-gonic/gin" + "github.com/iGoogle-ink/gopay" + v3 "github.com/iGoogle-ink/gopay/wechat/v3" + "github.com/iGoogle-ink/gotil/xlog" + "github.com/tidwall/gjson" +) + +// app支付v2 +func WxAppPay(c *gin.Context, params *md.WxPayParams) (map[string]string, error) { + appId := svcComm.SysCfgGet(c, "pay_wx_appid") + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + apiKey := svcComm.SysCfgGet(c, "pay_wx_api_key") + client := wxpay.NewClient(appId, mchId, apiKey, true) + notifyUrl := fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params.OrderType, md.WX_PAY) + r, err := wxpay.TradeAppPay(client, params.Subject, params.OrdId, params.Amount, notifyUrl) + return r, err +} + +// H5支付v2 +func WxH5Pay(c *gin.Context, params *md.WxPayParams) (map[string]string, error) { + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + apiKey := svcComm.SysCfgGet(c, "pay_wx_api_key") + //读公众号的 + appId := svcComm.SysCfgGet(c, "wx_official_account_app_id") + fmt.Println(appId) + fmt.Println(mchId) + fmt.Println(apiKey) + fmt.Println(params) + ip := c.ClientIP() + fmt.Println(ip) + + client := wxpay.NewClient(appId, mchId, apiKey, true) + notifyUrl := fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params.OrderType, md.WX_PAY) + r, err := wxpay.TradeH5Pay(client, params.Subject, params.OrdId, params.Amount, notifyUrl, ip) + return r, err +} + +// 小程序v2 +func WxMiniProgPay(c *gin.Context, params *md.WxPayParams) (map[string]string, error) { + //读取小程序设置的 + wxAppletKey := svcComm.SysCfgGet(c, "wx_applet_key") + var appId string + if wxAppletKey != "" { + appId = gjson.Get(wxAppletKey, "appId").String() + } + fmt.Println(appId) + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + apiKey := svcComm.SysCfgGet(c, "pay_wx_api_key") + client := wxpay.NewClient(appId, mchId, apiKey, true) + notifyUrl := fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params.OrderType, md.WX_PAY) + // 兼容未登录支付 api/v1/unlogin/pay/:payMethod/:orderType(因为该路由未经过jwt-auth中间件) + user, err := svcComm.CheckUser(c) + if user == nil || err != nil { + return nil, e.NewErr(403000, "需要登录才能支付") + } + + if user.Profile.ThirdPartyWechatMiniOpenid == "" { + return nil, e.NewErr(403000, "openid error") + } + r, err := wxpay.TradeMiniProgPay(client, params.Subject, params.OrdId, params.Amount, notifyUrl, user.Profile.ThirdPartyWechatMiniOpenid) + return r, err +} + +// app支付V3 +func WxAppPayV3(c *gin.Context, params *md.WxPayParams) (map[string]string, error) { + appId := svcComm.SysCfgGet(c, "pay_wx_miniprog_appid") + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + SerialNo := svcComm.SysCfgGet(c, "pay_wx_api_key") + ApiV3Key := svcComm.SysCfgGet(c, "pay_wx_api_key") + PKContent := svcComm.SysCfgGet(c, "pay_wx_api_key") + client, err := v3.NewClientV3(appId, mchId, SerialNo, ApiV3Key, PKContent) + if err != nil { + xlog.Error(err) + return nil, err + } + client.DebugSwitch = gopay.DebugOff + notifyUrl := fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params.OrderType, md.WX_PAY) + r, err := wxpay.TradeAppPayV3(client, params.Subject, params.OrdId, params.Amount, notifyUrl) + return r, err +} + +// 微信JSAPI支付 +func WxAppJSAPIPay(c *gin.Context, params *md.WxPayParams) (map[string]string, error) { + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + apiKey := svcComm.SysCfgGet(c, "pay_wx_api_key") + //读公众号的 + appId := svcComm.SysCfgGet(c, "wx_official_account_app_id") + client := wxpay.NewClient(appId, mchId, apiKey, true) + notifyUrl := fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params.OrderType, md.WX_PAY) + // 兼容未登录支付 api/v1/unlogin/pay/:payMethod/:orderType(因为该路由未经过jwt-auth中间件) + user, err := svcComm.CheckUser(c) + if user == nil || err != nil { + return nil, e.NewErr(403000, "需要登录才能支付") + } + + if user.Profile.ThirdPartyWechatH5Openid == "" { + return nil, e.NewErr(403000, "openid error") + } + r, err := wxpay.TradeJSAPIPay(client, params.Subject, params.OrdId, params.Amount, notifyUrl, user.Profile.ThirdPartyWechatH5Openid) + return r, err +} + +// H5支付V3 +func WxH5PayV3(c *gin.Context, params *md.WxPayParams) (string, error) { + appId := svcComm.SysCfgGet(c, "pay_wx_appid") + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + apiKey := svcComm.SysCfgGet(c, "pay_wx_api_key") + client := wxpay.NewClient(appId, mchId, apiKey, false) + notifyUrl := "" + ip := c.ClientIP() + _, err := wxpay.TradeH5Pay(client, params.Subject, params.OrdId, params.Amount, notifyUrl, ip) + return "", err +} + +// 小程序V3 +func WxMiniProgPayV3(c *gin.Context, params *md.WxPayParams) (string, error) { + appId := svcComm.SysCfgGet(c, "pay_wx_miniprog_appid") + mchId := svcComm.SysCfgGet(c, "pay_wx_mch_id") + SerialNo := svcComm.SysCfgGet(c, "pay_wx_api_key") + ApiV3Key := svcComm.SysCfgGet(c, "pay_wx_api_key") + PKContent := svcComm.SysCfgGet(c, "pay_wx_api_key") + client, err := v3.NewClientV3(appId, mchId, SerialNo, ApiV3Key, PKContent) + if err != nil { + xlog.Error(err) + return "", err + } + client.DebugSwitch = gopay.DebugOff + notifyUrl := fmt.Sprintf(md.CALLBACK_URL, c.Request.Host, c.GetString("mid"), params.OrderType, md.WX_PAY) + r, err := wxpay.TradeMiniProgPayV3(client, params.Subject, params.OrdId, params.Amount, notifyUrl) + return r, err +} diff --git a/app/pay/svc/svc_wxpay_callback.go b/app/pay/svc/svc_wxpay_callback.go new file mode 100644 index 0000000..097bb0a --- /dev/null +++ b/app/pay/svc/svc_wxpay_callback.go @@ -0,0 +1,28 @@ +package svc + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/pay/md" + "applet/app/utils/logx" + "github.com/gin-gonic/gin" +) + +// 微信支付回调处理 +func wxPayCallback(c *gin.Context) (string, error) { + data, ok := c.Get("callback") + if data == nil || !ok { + return "", e.NewErrCode(e.ERR_INVALID_ARGS) + } + args := data.(*md.WxPayCallback) + _, ok = db.DBs[args.MasterID] + if !ok { + return "", logx.Warn("wxpay Failed : master_id not found") + } + c.Set("mid", args.MasterID) + //回调交易状态失败 + if args.ResultCode != "SUCCESS" || args.ReturnCode != "SUCCESS" { + return "", logx.Warn("wxpay Failed : trade status failed") + } + return args.OutTradeNo, nil +} diff --git a/app/router/router.go b/app/router/router.go new file mode 100644 index 0000000..c28cb61 --- /dev/null +++ b/app/router/router.go @@ -0,0 +1,78 @@ +package router + +import ( + "applet/app/cfg" + "applet/app/mall/hdl" + "applet/app/mw" + pay "applet/app/pay/hdl" + _ "applet/docs" + "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) + route(r.Group("/api/v1/mall")) + rInApi(r.Group("/inapi/mall")) + return r +} + +func route(r *gin.RouterGroup) { + // 通用支付回调 + r.Any("/pay/callback", pay.PayCallback) + r.Use(mw.DB) // 以下接口需要用到数据库 + { + r.GET("/test", hdl.Demo1) + } + + r.Use(mw.Checker) // 以下接口需要检查Header: platform + { + } + + r.Use(mw.AuthJWT) // 以下接口需要JWT验证 + { + // 通用支付 + r.POST("/user/pay/:payMethod/:orderType", pay.Pay) + // 支付状态 + r.POST("/user/paystatus/:orderType", pay.PayStatus) + } +} + +func rInApi(r *gin.RouterGroup) { + //TODO::该分组中所有的接口,支持开放平台调用 + r.Use(mw.DB) // 以下接口需要用到数据库 + { + + } + r.Use(mw.AuthJWT) // 以下接口需要JWT验证 + { + + } +} diff --git a/app/svc/svc_auth.go b/app/svc/svc_auth.go new file mode 100644 index 0000000..5e571d9 --- /dev/null +++ b/app/svc/svc_auth.go @@ -0,0 +1,73 @@ +package svc + +import ( + "applet/app/db" + "applet/app/md" + "applet/app/utils" + "errors" + "strings" + + "github.com/gin-gonic/gin" +) + +// 因为在mw_auth已经做完所有校验, 因此在此不再做任何校验 +//GetUser is get user model +func GetUser(c *gin.Context) *md.User { + user, _ := c.Get("user") + return user.(*md.User) +} + +func GetUid(c *gin.Context) string { + user, _ := c.Get("user") + u := user.(*md.User) + return utils.IntToStr(u.Info.Uid) +} + +func CheckUser(c *gin.Context) (*md.User, error) { + token := c.GetHeader("Authorization") + if token == "" { + return nil, errors.New("token not exist") + } + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if !(len(parts) == 2 && parts[0] == "Bearer") { + return nil, errors.New("token format error") + } + // parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它 + mc, err := utils.ParseToken(parts[1]) + if err != nil { + return nil, err + } + + // 获取user + u, err := db.UserFindByID(db.DBs[c.GetString("mid")], mc.UID) + if err != nil { + return nil, err + } + if u == nil { + return nil, errors.New("token can not find user") + } + // 获取user profile + up, err := db.UserProfileFindByID(db.DBs[c.GetString("mid")], mc.UID) + if err != nil { + return nil, err + } + // 获取user 等级 + ul, err := db.UserLevelByID(db.DBs[c.GetString("mid")], u.Level) + if err != nil { + return nil, err + } + + // 获取用户标签 + tags, err := db.UserTagsByUid(db.DBs[c.GetString("mid")], mc.UID) + if err != nil { + return nil, err + } + user := &md.User{ + Info: u, + Profile: up, + Level: ul, + Tags: tags, + } + return user, nil +} diff --git a/app/svc/svc_db.go b/app/svc/svc_db.go new file mode 100644 index 0000000..99b1e0d --- /dev/null +++ b/app/svc/svc_db.go @@ -0,0 +1,11 @@ +package svc + +import ( + "applet/app/db" + "github.com/gin-gonic/gin" + "xorm.io/xorm" +) + +func MasterDb(c *gin.Context) *xorm.Engine { + return db.DBs[c.GetString("mid")] +} diff --git a/app/svc/svc_domain_info.go b/app/svc/svc_domain_info.go new file mode 100644 index 0000000..a27bc2a --- /dev/null +++ b/app/svc/svc_domain_info.go @@ -0,0 +1,58 @@ +package svc + +import ( + "applet/app/db" + "applet/app/db/model" + "applet/app/utils" + "applet/app/utils/logx" + "github.com/gin-gonic/gin" + "github.com/tidwall/gjson" + "strings" +) + +// 获取指定类型的域名:admin、wap、api +func GetWebSiteDomainInfo(c *gin.Context, domainType string) string { + if domainType == "" { + domainType = "wap" + } + + domainSetting := SysCfgGet(c, "domain_setting") + + domainTypePath := domainType + ".type" + domainSslPath := domainType + ".isOpenHttps" + domainPath := domainType + ".domain" + + domainTypeValue := gjson.Get(domainSetting, domainTypePath).String() + domainSslValue := gjson.Get(domainSetting, domainSslPath).String() + domain := gjson.Get(domainSetting, domainPath).String() + + scheme := "http://" + if domainSslValue == "1" { + scheme = "https://" + } + + // 有自定义域名 返回自定义的 + if domainTypeValue == "own" && domain != "" { + return scheme + domain + } + // 否则返回官方的 + official, err := db.GetOfficialDomainInfoByType(db.Db, c.GetString("mid"), domainType) + if err != nil { + _ = logx.Errorf("Get Official Domain Fail! %s", err) + return "" + } + if strings.Contains(official, "http") { + return official + } + return scheme + official +} + +// 获取指定类型的域名对应的masterId:admin、wap、api +func GetWebSiteDomainMasterId(domainType string, host string) string { + obj := new(model.UserAppDomain) + has, err := db.Db.Where("domain=? and type=?", host, domainType).Get(obj) + if err != nil || !has { + return "" + } + return utils.AnyToString(obj.Uuid) +} diff --git a/app/svc/svc_file_domain.go b/app/svc/svc_file_domain.go new file mode 100644 index 0000000..fa7d984 --- /dev/null +++ b/app/svc/svc_file_domain.go @@ -0,0 +1,18 @@ +package svc + +import ( + "applet/app/md" + + "github.com/gin-gonic/gin" +) + +// 文件host +func FileImgHost(c *gin.Context) string { + res := SysCfgFind(c, md.KEY_CFG_FILE_SCHEME, md.KEY_CFG_FILE_HOST) + return res[md.KEY_CFG_FILE_SCHEME] + "://" + res[md.KEY_CFG_FILE_HOST] + "/" +} + +// 获取缩略图参数 +func FileImgThumbnail(c *gin.Context) string { + return SysCfgGet(c, md.KEY_CFG_FILE_AVATAR_THUMBNAIL) +} diff --git a/app/svc/svc_file_img_format.go b/app/svc/svc_file_img_format.go new file mode 100644 index 0000000..c14f066 --- /dev/null +++ b/app/svc/svc_file_img_format.go @@ -0,0 +1,71 @@ +package svc + +import ( + "applet/app/utils" + "fmt" + "strings" + + "github.com/gin-gonic/gin" +) + +//ImageFormat is 格式化 图片 +func ImageFormat(c *gin.Context, name string) string { + if strings.Contains(name, "https:") || strings.Contains(name, "http:") { + return name + } + scheme := SysCfgGet(c, "file_bucket_scheme") + domain := SysCfgGet(c, "file_bucket_host") + return fmt.Sprintf("%s://%s/%s", scheme, domain, name) +} + +//OffImageFormat is 格式化官方 图片 +func OffImageFormat(c *gin.Context, name string) string { + if strings.Contains(name, "https:") || strings.Contains(name, "http:") { + return name + } + return fmt.Sprintf("%s://%s/%s", "http", "ossq.izhyin.cn", name) +} + +// ImageBucket is 获取域名 +func ImageBucket(c *gin.Context) (string, string) { + return SysCfgGet(c, "file_bucket_scheme"), SysCfgGet(c, "file_bucket_host") +} + +// ImageBucketNew is 获取域名 +func ImageBucketNew(c *gin.Context) (string, string, string, map[string]string) { + var list = make(map[string]string, 0) + for i := 1; i < 10; i++ { + keys := "file_bucket_sub_host" + utils.IntToStr(i) + list[keys] = SysCfgGet(c, keys) + } + return SysCfgGet(c, "file_bucket_scheme"), SysCfgGet(c, "file_bucket_host"), SysCfgGet(c, "file_bucket_sub_host"), list +} + +// ImageFormatWithBucket is 格式化成oss 域名 +func ImageFormatWithBucket(scheme, domain, name string) string { + if strings.Contains(name, "http") { + return name + } + return fmt.Sprintf("%s://%s/%s", scheme, domain, name) +} + +// ImageFormatWithBucket is 格式化成oss 域名 +func ImageFormatWithBucketNew(scheme, domain, subDomain string, moreSubDomain map[string]string, name string) string { + if strings.Contains(name, "http") { + return name + } + if strings.Contains(name, "{{subhost}}") && subDomain != "" { //读副域名 有可能是其他平台的 + domain = subDomain + } + //为了兼容一些客户自营商城导到不同系统 并且七牛云不一样 + for i := 1; i < 10; i++ { + keys := "file_bucket_sub_host" + utils.IntToStr(i) + if strings.Contains(name, "{{subhost"+utils.IntToStr(i)+"}}") && moreSubDomain[keys] != "" { + domain = moreSubDomain[keys] + } + name = strings.ReplaceAll(name, "{{subhost"+utils.IntToStr(i)+"}}", "") + } + name = strings.ReplaceAll(name, "{{host}}", "") + name = strings.ReplaceAll(name, "{{subhost}}", "") + return fmt.Sprintf("%s://%s/%s", scheme, domain, name) +} diff --git a/app/svc/svc_file_img_upload.go b/app/svc/svc_file_img_upload.go new file mode 100644 index 0000000..e00a374 --- /dev/null +++ b/app/svc/svc_file_img_upload.go @@ -0,0 +1,104 @@ +package svc + +import ( + "fmt" + "strings" + + "applet/app/e" + "applet/app/lib/qiniu" + "applet/app/md" + "applet/app/utils" + "applet/app/utils/logx" + + "github.com/gin-gonic/gin" +) + +// 请求文件上传 +func ImgReqUpload(c *gin.Context, uid, dirName, fname, callbackUrl string, fsize int64) (interface{}, error) { + ext := utils.FileExt(fname) + if err := initStg(c, fsize, ext); err != nil { + return nil, err + } + // logx.Warn(uid) + newName := dirName + "_" + fmt.Sprintf("%010s", uid) + + // if dirName == md.FILE_DIR_FEEDBACK || dirName == md.FILE_DIR_STYLE { + // newName += "_" + utils.FormatNanoUnix() + utils.RandString(4, "0123456789") + // } + // 默认都加时间戳 + newName += "_" + utils.FormatNanoUnix() + utils.RandString(4, "0123456789") + newName += ".png" // 因为可能存在多种图像格式,这里统一后缀为png + + f := &md.FileCallback{ + Uid: uid, + DirId: md.FileUserDir[dirName], + FileName: newName, + } + // logx.Warn(f.Uid) + return qiniu.ReqImgUpload(f, callbackUrl), nil +} + +func initStg(c *gin.Context, fsize int64, ext string) error { + // 获取上传配置 + stgInfo := SysCfgFind( + c, + md.KEY_CFG_FILE_BUCKET, + md.KEY_CFG_FILE_HOST, + md.KEY_CFG_FILE_AK, + md.KEY_CFG_FILE_SK, + md.KEY_CFG_FILE_PVD, + md.KEY_CFG_FILE_REGION, + md.KEY_CFG_FILE_MAX_SIZE, + md.KEY_CFG_FILE_EXT, + md.KEY_CFG_FILE_SCHEME, + md.KEY_CFG_FILE_AVATAR_THUMBNAIL, + ) + //?imageView2/1/w/120/h/120/format/webp/interlace/1 + if stgInfo == nil { + return e.NewErrCode(e.ERR_CFG) + } + // todo 目前仅支持七牛 + if v, ok := stgInfo[md.KEY_CFG_FILE_PVD]; !ok || v != "qiniu" { + return e.NewErrCode(e.ERR_CFG) + } + if v, ok := stgInfo[md.KEY_CFG_FILE_REGION]; !ok || v == "" { + return e.NewErrCode(e.ERR_CFG) + } + if v, ok := stgInfo[md.KEY_CFG_FILE_AK]; !ok || v == "" { + return e.NewErrCode(e.ERR_CFG) + } + if v, ok := stgInfo[md.KEY_CFG_FILE_SK]; !ok || v == "" { + return e.NewErrCode(e.ERR_CFG) + } + if v, ok := stgInfo[md.KEY_CFG_FILE_BUCKET]; !ok || v == "" { + return e.NewErrCode(e.ERR_CFG) + } + if v, ok := stgInfo[md.KEY_CFG_FILE_SCHEME]; !ok || v == "" { + stgInfo[md.KEY_CFG_FILE_SCHEME] = "http" + SysCfgSet(c, md.KEY_CFG_FILE_SCHEME, stgInfo[md.KEY_CFG_FILE_SCHEME], "文件域名HTTP协议") + } + qiniu.Init(stgInfo[md.KEY_CFG_FILE_AK], stgInfo[md.KEY_CFG_FILE_SK], stgInfo[md.KEY_CFG_FILE_BUCKET], stgInfo[md.KEY_CFG_FILE_REGION], stgInfo[md.KEY_CFG_FILE_SCHEME]) + if v, ok := stgInfo[md.KEY_CFG_FILE_HOST]; !ok || v == "" { + var err error + stgInfo[md.KEY_CFG_FILE_HOST], err = qiniu.BucketGetDomain(stgInfo[md.KEY_CFG_FILE_BUCKET]) + if err != nil { + logx.Error(err) + return e.NewErrCode(e.ERR_CFG) + } + SysCfgSet(c, md.KEY_CFG_FILE_HOST, stgInfo[md.KEY_CFG_FILE_HOST], "文件域名地址") + } + // 头像缩略图参数 + if v, ok := stgInfo[md.KEY_CFG_FILE_AVATAR_THUMBNAIL]; !ok || v == "" { + SysCfgSet(c, md.KEY_CFG_FILE_AVATAR_THUMBNAIL, "?imageView2/1/w/200/h/200/format/png", "文件用户头像缩略图参数") + } + + // 检查文件大小限制 + if v, ok := stgInfo[md.KEY_CFG_FILE_MAX_SIZE]; ok && v != "" && utils.StrToInt64(v) < fsize { + return e.NewErrCode(e.ERR_FILE_MAX_SIZE) + } + // 检查文件后缀 + if v, ok := stgInfo[md.KEY_CFG_FILE_EXT]; ok && v != "" && !strings.Contains(v, ext) { + return e.NewErrCode(e.ERR_FILE_EXT) + } + return nil +} diff --git a/app/svc/svc_file_save.go b/app/svc/svc_file_save.go new file mode 100644 index 0000000..1ddc1d7 --- /dev/null +++ b/app/svc/svc_file_save.go @@ -0,0 +1,46 @@ +package svc + +import ( + "time" + + "applet/app/db" + "applet/app/db/model" + "applet/app/e" + "applet/app/lib/qiniu" + "applet/app/md" + "applet/app/utils" + + "github.com/gin-gonic/gin" +) + +func FileSave(c *gin.Context, f *md.FileCallback) error { + // todo 校验时间是否超时, 目前没必要做时间校验,如果已经上传,但超时,那么会造成三方存储存在,可我方表不存在,导致冗余 + // 校验签名是否正确 + if qiniu.Sign(f.Time) != f.Sign { + return e.NewErrCode(e.ERR_SIGN) + } + newFile := &model.SysFile{ + ParentFid: utils.StrToInt64(f.DirId), + FileType: 1, + ShowName: f.FileName, + SaveName: f.FileName, + Uid: utils.StrToInt(f.Uid), + Ext: utils.FileExt(f.FileName), + Hash: f.Hash, + Mime: f.Mime, + Provider: f.Provider, + Width: utils.StrToInt(f.Width), + Height: utils.StrToInt(f.Height), + Bucket: f.Bucket, + FileSize: utils.StrToInt64(f.FileSize), + CreateAt: int(time.Now().Unix()), + } + + file, _ := db.FileGetByPFidAndName(db.DBs[c.GetString("mid")], f.DirId, f.FileName) + if file != nil { + newFile.Fid = file.Fid + // 更新数据 + return db.FileUpdate(db.DBs[c.GetString("mid")], newFile) + } + return db.FileInsert(db.DBs[c.GetString("mid")], newFile) +} diff --git a/app/svc/svc_redis_mutex_lock.go b/app/svc/svc_redis_mutex_lock.go new file mode 100644 index 0000000..396e15c --- /dev/null +++ b/app/svc/svc_redis_mutex_lock.go @@ -0,0 +1,85 @@ +package svc + +import ( + "applet/app/md" + "applet/app/utils" + "applet/app/utils/cache" + "errors" + "fmt" + "math/rand" + "reflect" + "time" +) + +const redisMutexLockExpTime = 15 + +// TryGetDistributedLock 分布式锁获取 +// requestId 用于标识请求客户端,可以是随机字符串,需确保唯一 +func TryGetDistributedLock(lockKey, requestId string, isNegative bool) bool { + if isNegative { // 多次尝试获取 + retry := 1 + for { + ok, err := cache.Do("SET", lockKey, requestId, "EX", redisMutexLockExpTime, "NX") + // 获取锁成功 + if err == nil && ok == "OK" { + return true + } + // 尝试多次没获取成功 + if retry > 10 { + return false + } + time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) + retry += 1 + } + } else { // 只尝试一次 + ok, err := cache.Do("SET", lockKey, requestId, "EX", redisMutexLockExpTime, "NX") + // 获取锁成功 + if err == nil && ok == "OK" { + return true + } + + return false + } +} + +// ReleaseDistributedLock 释放锁,通过比较requestId,用于确保客户端只释放自己的锁,使用lua脚本保证操作的原子型 +func ReleaseDistributedLock(lockKey, requestId string) (bool, error) { + luaScript := ` + if redis.call("get",KEYS[1]) == ARGV[1] + then + return redis.call("del",KEYS[1]) + else + return 0 + end` + + do, err := cache.Do("eval", luaScript, 1, lockKey, requestId) + fmt.Println(reflect.TypeOf(do)) + fmt.Println(do) + + if utils.AnyToInt64(do) == 1 { + return true, err + } else { + return false, err + } +} + +func GetDistributedLockRequestId(prefix string) string { + return prefix + utils.IntToStr(rand.Intn(100000000)) +} + +// HandleBalanceDistributedLock 处理余额更新时获取锁和释放锁 如果加锁成功,使用语句 ` defer cb() ` 释放锁 +func HandleBalanceDistributedLock(masterId, uid, requestIdPrefix string) (cb func(), err error) { + // 获取余额更新锁 + balanceLockKey := fmt.Sprintf(md.UserFinValidUpdateLock, masterId, uid) + requestId := GetDistributedLockRequestId(requestIdPrefix) + balanceLockOk := TryGetDistributedLock(balanceLockKey, requestId, true) + if !balanceLockOk { + return nil, errors.New("系统繁忙,请稍后再试") + } + + cb = func() { + _, _ = ReleaseDistributedLock(balanceLockKey, requestId) + } + + return cb, nil +} diff --git a/app/svc/svc_sys_cfg_get.go b/app/svc/svc_sys_cfg_get.go new file mode 100644 index 0000000..3a51e34 --- /dev/null +++ b/app/svc/svc_sys_cfg_get.go @@ -0,0 +1,171 @@ +package svc + +import ( + "errors" + "fmt" + "github.com/gin-gonic/gin" + "strings" + "xorm.io/xorm" + + "applet/app/cfg" + "applet/app/db" + "applet/app/md" + "applet/app/utils" + "applet/app/utils/cache" +) + +// 单挑记录获取 +func SysCfgGet(c *gin.Context, key string) string { + mid := c.GetString("mid") + eg := db.DBs[mid] + return db.SysCfgGetWithDb(eg, mid, key) +} + +// 多条记录获取 +func SysCfgFind(c *gin.Context, keys ...string) map[string]string { + e := db.DBs[c.GetString("mid")] + res := map[string]string{} + cacheKey := fmt.Sprintf(md.AppCfgCacheKey, c.GetString("mid")) + err := cache.GetJson(cacheKey, &res) + if err != nil || len(res) == 0 { + cfgList, _ := db.SysCfgGetAll(e) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + // 先不设置缓存 + cache.SetJson(cacheKey, res, md.CfgCacheTime) + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} + +// 多条记录获取 +func EgSysCfgFind(keys ...string) map[string]string { + var e *xorm.Engine + res := map[string]string{} + if len(res) == 0 { + cfgList, _ := db.SysCfgGetAll(e) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + // 先不设置缓存 + // cache.SetJson(md.KEY_SYS_CFG_CACHE, res, 60) + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} + +// 清理系统配置信息 +func SysCfgCleanCache() { + cache.Del(md.KEY_SYS_CFG_CACHE) +} + +// 写入系统设置 +func SysCfgSet(c *gin.Context, key, val, memo string) bool { + cfg, err := db.SysCfgGetOne(db.DBs[c.GetString("mid")], key) + if err != nil || cfg == nil { + return db.SysCfgInsert(db.DBs[c.GetString("mid")], key, val, memo) + } + if memo != "" && cfg.Memo != memo { + cfg.Memo = memo + } + SysCfgCleanCache() + return db.SysCfgUpdate(db.DBs[c.GetString("mid")], key, val, cfg.Memo) +} + +// 多条记录获取 +func SysCfgFindByIds(eg *xorm.Engine, keys ...string) map[string]string { + key := utils.Md5(eg.DataSourceName() + md.KEY_SYS_CFG_CACHE) + res := map[string]string{} + c, ok := cfg.MemCache.Get(key).(map[string]string) + if !ok || len(c) == 0 { + cfgList, _ := db.DbsSysCfgGetAll(eg) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + cfg.MemCache.Put(key, res, 10) + } else { + res = c + } + if len(keys) == 0 { + return res + } + tmp := map[string]string{} + for _, v := range keys { + if val, ok := res[v]; ok { + tmp[v] = val + } else { + tmp[v] = "" + } + } + return tmp +} + +// 支付配置 +func SysCfgFindPayment(c *gin.Context) ([]map[string]string, error) { + platform := c.GetHeader("platform") + payCfg := SysCfgFind(c, "pay_wx_pay_img", "pay_ali_pay_img", "pay_balance_img", "pay_type") + if payCfg["pay_wx_pay_img"] == "" || payCfg["pay_ali_pay_img"] == "" || payCfg["pay_balance_img"] == "" || payCfg["pay_type"] == "" { + return nil, errors.New("lack of payment config") + } + payCfg["pay_wx_pay_img"] = ImageFormat(c, payCfg["pay_wx_pay_img"]) + payCfg["pay_ali_pay_img"] = ImageFormat(c, payCfg["pay_ali_pay_img"]) + payCfg["pay_balance_img"] = ImageFormat(c, payCfg["pay_balance_img"]) + + var result []map[string]string + + if strings.Contains(payCfg["pay_type"], "aliPay") && platform != md.PLATFORM_WX_APPLET { + item := make(map[string]string) + item["pay_channel"] = "alipay" + item["img"] = payCfg["pay_ali_pay_img"] + item["name"] = "支付宝支付" + result = append(result, item) + } + + if strings.Contains(payCfg["pay_type"], "wxPay") { + item := make(map[string]string) + item["pay_channel"] = "wx" + item["img"] = payCfg["pay_wx_pay_img"] + item["name"] = "微信支付" + result = append(result, item) + } + + if strings.Contains(payCfg["pay_type"], "walletPay") { + item := make(map[string]string) + item["pay_channel"] = "fin" + item["img"] = payCfg["pay_balance_img"] + item["name"] = "余额支付" + result = append(result, item) + } + + return result, nil +} diff --git a/app/svc/svc_validate_common.go b/app/svc/svc_validate_common.go new file mode 100644 index 0000000..08e1e9d --- /dev/null +++ b/app/svc/svc_validate_common.go @@ -0,0 +1,26 @@ +package svc + +import ( + "applet/app/e" + "applet/app/utils" + "applet/app/utils/logx" + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" +) + +// HandleValidateErr 通用请求参数错误处理 +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) + transMsgOne := transMsgMap[utils.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)) + } +} diff --git a/app/task/init.go b/app/task/init.go new file mode 100644 index 0000000..54b3536 --- /dev/null +++ b/app/task/init.go @@ -0,0 +1,117 @@ +package task + +import ( + taskMd "applet/app/task/md" + "time" + + "github.com/robfig/cron/v3" + "xorm.io/xorm" + + "applet/app/cfg" + "applet/app/db" + "applet/app/db/model" + "applet/app/md" + "applet/app/utils" + "applet/app/utils/logx" +) + +var ( + timer *cron.Cron + jobs = map[string]func(*xorm.Engine, string){} + baseEntryId cron.EntryID + entryIds []cron.EntryID + taskCfgList map[string]*[]model.SysCfg + ch = make(chan int, 30) + workerNum = 15 // 智盟跟单并发数量 + otherCh = make(chan int, 30) + otherWorkerNum = 18 // 淘宝, 苏宁, 考拉并发量 +) + +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() { + // 重新初始化数据库 + db.InitMapDbs(cfg.DB, cfg.Prd) + + if len(taskCfgList) == 0 { + taskCfgList = map[string]*[]model.SysCfg{} + } + + // 获取所有站长的配置信息 + for dbName, v := range db.DBs { + if conf := db.MapCrontabCfg(v); conf != nil { + if cfg.Debug { + dbInfo := md.SplitDbInfo(v) + // 去掉模版库 + if dbName == "000000" { + continue + } + _ = logx.Debugf("【MasterId】%s, 【Host】%s, 【Name】%s, 【User】%s, 【prd】%v, 【Task】%v\n", dbName, dbInfo.Host, dbInfo.Name, dbInfo.User, cfg.Prd, utils.SerializeStr(*conf)) + } + taskCfgList[dbName] = conf + } + } + if len(taskCfgList) > 0 { + // 删除原有所有任务 + if len(entryIds) > 0 { + for _, v := range entryIds { + if v != baseEntryId { + timer.Remove(v) + } + } + entryIds = nil + } + var ( + entryId cron.EntryID + err error + ) + // 添加任务 + for dbName, v := range taskCfgList { + for _, vv := range *v { + if _, ok := jobs[vv.Key]; ok && vv.Val != "" { + // fmt.Println(vv.Val) + if entryId, err = timer.AddFunc(vv.Val, doTask(dbName, vv.Key)); err == nil { + entryIds = append(entryIds, entryId) + } + } + } + } + + } +} + +func doTask(dbName, fnName string) func() { + return func() { + begin := time.Now().Local() + jobs[fnName](db.DBs[dbName], dbName) + end := time.Now().Local() + logx.Infof( + "[%s] AutoTask <%s> started at <%s>, ended at <%s> duration <%s>", + dbName, + 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.MallCronOrderCancel] = taskCancelOrder // 取消订单 +} diff --git a/app/task/md/cron_key.go b/app/task/md/cron_key.go new file mode 100644 index 0000000..b38ccc8 --- /dev/null +++ b/app/task/md/cron_key.go @@ -0,0 +1,5 @@ +package md + +const ( + MallCronOrderCancel = "mall_cron_order_cancel" // 取消订单任务 +) diff --git a/app/task/svc/svc_cancel_order.go b/app/task/svc/svc_cancel_order.go new file mode 100644 index 0000000..0c35b5f --- /dev/null +++ b/app/task/svc/svc_cancel_order.go @@ -0,0 +1,64 @@ +package svc + +import ( + "applet/app/db" + "applet/app/utils" + "applet/app/utils/logx" + "errors" + "fmt" + "time" + "xorm.io/xorm" +) + +func CancelOrder(eg *xorm.Engine, dbName string) { + fmt.Println("cancel order...") + defer func() { + if err := recover(); err != nil { + _ = logx.Error(err) + } + }() + + timeStr, err := getCancelCfg(eg, dbName) + if err != nil { + fmt.Println(err.Error()) + return + } + + now := time.Now() + // x 分钟后取消订单 + expTime := now.Add(-time.Hour * time.Duration(utils.StrToInt64(timeStr))) + expTimeStr := utils.Time2String(expTime, "") + + page := 1 + + for { + isEmpty, err := handleOnePage(eg, dbName, expTimeStr) + if err != nil { + _ = logx.Error(err) + break + } + if isEmpty { + break + } + + if page > 100 { + break + } + + page += 1 + + } +} + +func handleOnePage(eg *xorm.Engine, dbName, expTimeStr string) (isEmpty bool, err error) { + return false, nil +} + +func getCancelCfg(eg *xorm.Engine, masterId string) (string, error) { + cfg := db.SysCfgGetWithDb(eg, masterId, "order_expiration_time") + + if cfg == "" { + return "", errors.New("order_expiration_time no found") + } + return cfg, nil +} diff --git a/app/task/task_cancel_order.go b/app/task/task_cancel_order.go new file mode 100644 index 0000000..2e45bbb --- /dev/null +++ b/app/task/task_cancel_order.go @@ -0,0 +1,23 @@ +package task + +import ( + "applet/app/task/svc" + "math/rand" + "time" + "xorm.io/xorm" +) + +// 取消订单 +func taskCancelOrder(eg *xorm.Engine, dbName string) { + for { + if len(ch) > workerNum { + time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) + } else { + goto START + } + } +START: + ch <- 1 + svc.CancelOrder(eg, dbName) + <-ch +} diff --git a/app/utils/aes.go b/app/utils/aes.go new file mode 100644 index 0000000..efbb0bc --- /dev/null +++ b/app/utils/aes.go @@ -0,0 +1,172 @@ +package utils + +import ( + "applet/app/cfg" + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "encoding/json" + "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)] +} + +// AesAdminCurlPOST is 与后台接口加密交互 +func AesAdminCurlPOST(aesData string, url string) ([]byte, error) { + adminKey := cfg.Admin.AesKey + adminVI := cfg.Admin.AesIV + crypto := AesCrypt{ + Key: []byte(adminKey), + Iv: []byte(adminVI), + } + + encrypt, err := crypto.Encrypt([]byte(aesData)) + if err != nil { + return nil, err + } + + // 发送请求到后台 + postData := map[string]string{ + "postData": base64.StdEncoding.EncodeToString(encrypt), + } + fmt.Println(adminKey) + fmt.Println(adminVI) + fmt.Println("=======ADMIN请求=====") + fmt.Println(postData) + postDataByte, _ := json.Marshal(postData) + rdata, err := CurlPost(url, postDataByte, nil) + fmt.Println(err) + + if err != nil { + return nil, err + } + fmt.Println(rdata) + + pass, err := base64.StdEncoding.DecodeString(string(rdata)) + if err != nil { + return nil, err + } + fmt.Println(pass) + + decrypt, err := crypto.Decrypt(pass) + fmt.Println(err) + + if err != nil { + return nil, err + } + return decrypt, nil +} diff --git a/app/utils/auth.go b/app/utils/auth.go new file mode 100644 index 0000000..d7bd9ae --- /dev/null +++ b/app/utils/auth.go @@ -0,0 +1,46 @@ +package utils + +import ( + "errors" + "time" + + "applet/app/lib/auth" + + "github.com/dgrijalva/jwt-go" +) + +// GenToken 生成JWT +func GenToken(uid int, username, phone, appname, MiniOpenID, MiniSK string) (string, error) { + // 创建一个我们自己的声明 + c := auth.JWTUser{ + uid, + username, + phone, + appname, + MiniOpenID, + MiniSK, + jwt.StandardClaims{ + ExpiresAt: time.Now().Add(auth.TokenExpireDuration).Unix(), // 过期时间 + Issuer: "zyos", // 签发人 + }, + } + // 使用指定的签名方法创建签名对象 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + // 使用指定的secret签名并获得完整的编码后的字符串token + return token.SignedString(auth.Secret) +} + +// ParseToken 解析JWT +func ParseToken(tokenString string) (*auth.JWTUser, error) { + // 解析token + token, err := jwt.ParseWithClaims(tokenString, &auth.JWTUser{}, func(token *jwt.Token) (i interface{}, err error) { + return auth.Secret, nil + }) + if err != nil { + return nil, err + } + if claims, ok := token.Claims.(*auth.JWTUser); ok && token.Valid { // 校验token + return claims, nil + } + return nil, errors.New("invalid token") +} diff --git a/app/utils/base64.go b/app/utils/base64.go new file mode 100644 index 0000000..ee16553 --- /dev/null +++ b/app/utils/base64.go @@ -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 + } +} diff --git a/app/utils/boolean.go b/app/utils/boolean.go new file mode 100644 index 0000000..d64c876 --- /dev/null +++ b/app/utils/boolean.go @@ -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()) +} \ No newline at end of file diff --git a/app/utils/cache/base.go b/app/utils/cache/base.go new file mode 100644 index 0000000..64648dd --- /dev/null +++ b/app/utils/cache/base.go @@ -0,0 +1,421 @@ +package cache + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +const ( + redisDialTTL = 10 * time.Second + redisReadTTL = 3 * time.Second + redisWriteTTL = 3 * time.Second + redisIdleTTL = 10 * time.Second + redisPoolTTL = 10 * time.Second + redisPoolSize int = 512 + redisMaxIdleConn int = 64 + redisMaxActive int = 512 +) + +var ( + ErrNil = errors.New("nil return") + ErrWrongArgsNum = errors.New("args num error") + ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64") +) + +// 以下为提供类型转换 + +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return reply, nil + case int8: + return int(reply), nil + case int16: + return int(reply), nil + case int32: + return int(reply), nil + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case uint: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int(reply), nil + case uint16: + return int(reply), nil + case uint32: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint64: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 0) + return int(n), err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply) +} + +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return int64(reply), nil + case int8: + return int64(reply), nil + case int16: + return int64(reply), nil + case int32: + return int64(reply), nil + case int64: + return reply, nil + case uint: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int64(reply), nil + case uint16: + return int64(reply), nil + case uint32: + return int64(reply), nil + case uint64: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply) +} + +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case uint: + return uint64(reply), nil + case uint8: + return uint64(reply), nil + case uint16: + return uint64(reply), nil + case uint32: + return uint64(reply), nil + case uint64: + return reply, nil + case int: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int8: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int16: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int32: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int64: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply) +} + +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + + var value float64 + err = nil + switch v := reply.(type) { + case float32: + value = float64(v) + case float64: + value = v + case int: + value = float64(v) + case int8: + value = float64(v) + case int16: + value = float64(v) + case int32: + value = float64(v) + case int64: + value = float64(v) + case uint: + value = float64(v) + case uint8: + value = float64(v) + case uint16: + value = float64(v) + case uint32: + value = float64(v) + case uint64: + value = float64(v) + case []byte: + data := string(v) + if len(data) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(string(v), 64) + case string: + if len(v) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(v, 64) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v) + } + + return value, err +} + +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case bool: + return reply, nil + case int64: + return reply != 0, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(data) + case string: + if len(reply) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(reply) + case nil: + return false, ErrNil + case error: + return false, reply + } + return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply) +} + +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + if len(reply) == 0 { + return nil, ErrNil + } + return reply, nil + case string: + data := []byte(reply) + if len(data) == 0 { + return nil, ErrNil + } + return data, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply) +} + +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + + value := "" + err = nil + switch v := reply.(type) { + case string: + if len(v) == 0 { + return "", ErrNil + } + + value = v + case []byte: + if len(v) == 0 { + return "", ErrNil + } + + value = string(v) + case int: + value = strconv.FormatInt(int64(v), 10) + case int8: + value = strconv.FormatInt(int64(v), 10) + case int16: + value = strconv.FormatInt(int64(v), 10) + case int32: + value = strconv.FormatInt(int64(v), 10) + case int64: + value = strconv.FormatInt(v, 10) + case uint: + value = strconv.FormatUint(uint64(v), 10) + case uint8: + value = strconv.FormatUint(uint64(v), 10) + case uint16: + value = strconv.FormatUint(uint64(v), 10) + case uint32: + value = strconv.FormatUint(uint64(v), 10) + case uint64: + value = strconv.FormatUint(v, 10) + case float32: + value = strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + value = strconv.FormatFloat(v, 'f', -1, 64) + case bool: + value = strconv.FormatBool(v) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v) + } + + return value, err +} + +func Strings(reply interface{}, err error) ([]string, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([]string, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + switch subReply := reply[i].(type) { + case string: + result[i] = subReply + case []byte: + result[i] = string(subReply) + default: + return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i]) + } + } + return result, nil + case []string: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply) +} + +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply) +} diff --git a/app/utils/cache/cache/cache.go b/app/utils/cache/cache/cache.go new file mode 100644 index 0000000..e43c5f0 --- /dev/null +++ b/app/utils/cache/cache/cache.go @@ -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) +} diff --git a/app/utils/cache/cache/conv.go b/app/utils/cache/cache/conv.go new file mode 100644 index 0000000..6b700ae --- /dev/null +++ b/app/utils/cache/cache/conv.go @@ -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 +} diff --git a/app/utils/cache/cache/file.go b/app/utils/cache/cache/file.go new file mode 100644 index 0000000..5c4e366 --- /dev/null +++ b/app/utils/cache/cache/file.go @@ -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) +} diff --git a/app/utils/cache/cache/memory.go b/app/utils/cache/cache/memory.go new file mode 100644 index 0000000..0cc5015 --- /dev/null +++ b/app/utils/cache/cache/memory.go @@ -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) +} diff --git a/app/utils/cache/redis.go b/app/utils/cache/redis.go new file mode 100644 index 0000000..2199787 --- /dev/null +++ b/app/utils/cache/redis.go @@ -0,0 +1,409 @@ +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) +} \ No newline at end of file diff --git a/app/utils/cache/redis_cluster.go b/app/utils/cache/redis_cluster.go new file mode 100644 index 0000000..901f30c --- /dev/null +++ b/app/utils/cache/redis_cluster.go @@ -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 +} diff --git a/app/utils/cache/redis_pool.go b/app/utils/cache/redis_pool.go new file mode 100644 index 0000000..ca38b3f --- /dev/null +++ b/app/utils/cache/redis_pool.go @@ -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 +} diff --git a/app/utils/cache/redis_pool_cluster.go b/app/utils/cache/redis_pool_cluster.go new file mode 100644 index 0000000..cd1911b --- /dev/null +++ b/app/utils/cache/redis_pool_cluster.go @@ -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 +} diff --git a/app/utils/convert.go b/app/utils/convert.go new file mode 100644 index 0000000..a638d37 --- /dev/null +++ b/app/utils/convert.go @@ -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) +} diff --git a/app/utils/crypto.go b/app/utils/crypto.go new file mode 100644 index 0000000..56289c5 --- /dev/null +++ b/app/utils/crypto.go @@ -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)) +} diff --git a/app/utils/curl.go b/app/utils/curl.go new file mode 100644 index 0000000..0a45607 --- /dev/null +++ b/app/utils/curl.go @@ -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) +} diff --git a/app/utils/debug.go b/app/utils/debug.go new file mode 100644 index 0000000..bb2e9d3 --- /dev/null +++ b/app/utils/debug.go @@ -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) +} diff --git a/app/utils/duplicate.go b/app/utils/duplicate.go new file mode 100644 index 0000000..17cea88 --- /dev/null +++ b/app/utils/duplicate.go @@ -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 +} diff --git a/app/utils/file.go b/app/utils/file.go new file mode 100644 index 0000000..70a2927 --- /dev/null +++ b/app/utils/file.go @@ -0,0 +1,11 @@ +package utils + +import ( + "path" + "strings" +) + +// 获取文件后缀 +func FileExt(fname string) string { + return strings.ToLower(strings.TrimLeft(path.Ext(fname), ".")) +} diff --git a/app/utils/file_and_dir.go b/app/utils/file_and_dir.go new file mode 100644 index 0000000..93141f9 --- /dev/null +++ b/app/utils/file_and_dir.go @@ -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) +} diff --git a/app/utils/format.go b/app/utils/format.go new file mode 100644 index 0000000..997fe80 --- /dev/null +++ b/app/utils/format.go @@ -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 +} diff --git a/app/utils/json.go b/app/utils/json.go new file mode 100644 index 0000000..998bcec --- /dev/null +++ b/app/utils/json.go @@ -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) +} diff --git a/app/utils/logx/log.go b/app/utils/logx/log.go new file mode 100644 index 0000000..ca11223 --- /dev/null +++ b/app/utils/logx/log.go @@ -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 +} diff --git a/app/utils/logx/output.go b/app/utils/logx/output.go new file mode 100644 index 0000000..ef33f0b --- /dev/null +++ b/app/utils/logx/output.go @@ -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 +} diff --git a/app/utils/logx/sugar.go b/app/utils/logx/sugar.go new file mode 100644 index 0000000..ab380fc --- /dev/null +++ b/app/utils/logx/sugar.go @@ -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) +} diff --git a/app/utils/map.go b/app/utils/map.go new file mode 100644 index 0000000..d9f3b7a --- /dev/null +++ b/app/utils/map.go @@ -0,0 +1,9 @@ +package utils + +// GetOneKeyOfMapString 取出Map的一个key +func GetOneKeyOfMapString(collection map[string]string) string { + for k := range collection { + return k + } + return "" +} diff --git a/app/utils/map_and_struct.go b/app/utils/map_and_struct.go new file mode 100644 index 0000000..34904ce --- /dev/null +++ b/app/utils/map_and_struct.go @@ -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] +} diff --git a/app/utils/md5.go b/app/utils/md5.go new file mode 100644 index 0000000..52c108d --- /dev/null +++ b/app/utils/md5.go @@ -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)) +} diff --git a/app/utils/qrcode/decodeFile.go b/app/utils/qrcode/decodeFile.go new file mode 100644 index 0000000..f50fb28 --- /dev/null +++ b/app/utils/qrcode/decodeFile.go @@ -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 +} diff --git a/app/utils/qrcode/getBase64.go b/app/utils/qrcode/getBase64.go new file mode 100644 index 0000000..11d149c --- /dev/null +++ b/app/utils/qrcode/getBase64.go @@ -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) +} diff --git a/app/utils/qrcode/saveFile.go b/app/utils/qrcode/saveFile.go new file mode 100644 index 0000000..4854783 --- /dev/null +++ b/app/utils/qrcode/saveFile.go @@ -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 +} diff --git a/app/utils/qrcode/writeWeb.go b/app/utils/qrcode/writeWeb.go new file mode 100644 index 0000000..57e1e92 --- /dev/null +++ b/app/utils/qrcode/writeWeb.go @@ -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 +} diff --git a/app/utils/rand.go b/app/utils/rand.go new file mode 100644 index 0000000..0024fd0 --- /dev/null +++ b/app/utils/rand.go @@ -0,0 +1,31 @@ +package utils + +import ( + crand "crypto/rand" + "fmt" + "math/big" + "math/rand" + "time" +) + +func RandString(l int, c ...string) string { + var ( + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + str string + num *big.Int + ) + if len(c) > 0 { + chars = c[0] + } + chrLen := int64(len(chars)) + for len(str) < l { + num, _ = crand.Int(crand.Reader, big.NewInt(chrLen)) + str += string(chars[num.Int64()]) + } + return str +} + +func RandNum() string { + seed := time.Now().UnixNano() + rand.Int63() + return fmt.Sprintf("%05v", rand.New(rand.NewSource(seed)).Int31n(1000000)) +} diff --git a/app/utils/rsa.go b/app/utils/rsa.go new file mode 100644 index 0000000..fb8274a --- /dev/null +++ b/app/utils/rsa.go @@ -0,0 +1,170 @@ +package utils + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + "log" + "os" +) + +// 生成私钥文件 TODO 未指定路径 +func RsaKeyGen(bits int) error { + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return err + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + priFile, err := os.Create("private.pem") + if err != nil { + return err + } + err = pem.Encode(priFile, block) + priFile.Close() + if err != nil { + return err + } + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + pubFile, err := os.Create("public.pem") + if err != nil { + return err + } + err = pem.Encode(pubFile, block) + pubFile.Close() + if err != nil { + return err + } + return nil +} + +// 生成私钥文件, 返回 privateKey , publicKey, error +func RsaKeyGenText(bits int) (string, string, error) { // bits 字节位 1024/2048 + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return "", "", err + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + priBuff := bytes.NewBuffer(nil) + err = pem.Encode(priBuff, block) + if err != nil { + return "", "", err + } + // 生成公钥文件 + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", "", err + } + block = &pem.Block{ + Type: "PUBLIC KEY", + Bytes: derPkix, + } + pubBuff := bytes.NewBuffer(nil) + err = pem.Encode(pubBuff, block) + if err != nil { + return "", "", err + } + return priBuff.String(), pubBuff.String(), nil +} + +// 加密 +func RsaEncrypt(rawData, publicKey []byte) ([]byte, error) { + block, _ := pem.Decode(publicKey) + if block == nil { + return nil, errors.New("public key error") + } + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + pub := pubInterface.(*rsa.PublicKey) + return rsa.EncryptPKCS1v15(rand.Reader, pub, rawData) +} + +// 公钥加密 +func RsaEncrypts(data, keyBytes []byte) []byte { + //解密pem格式的公钥 + block, _ := pem.Decode(keyBytes) + if block == nil { + panic(errors.New("public key error")) + } + // 解析公钥 + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + panic(err) + } + // 类型断言 + pub := pubInterface.(*rsa.PublicKey) + //加密 + ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data) + if err != nil { + panic(err) + } + return ciphertext +} + +// 解密 +func RsaDecrypt(cipherText, privateKey []byte) ([]byte, error) { + block, _ := pem.Decode(privateKey) + if block == nil { + return nil, errors.New("private key error") + } + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.DecryptPKCS1v15(rand.Reader, priv, cipherText) +} + +// 从证书获取公钥 +func OpensslPemGetPublic(pathOrString string) (interface{}, error) { + var certPem []byte + var err error + if IsFile(pathOrString) && Exists(pathOrString) { + certPem, err = ioutil.ReadFile(pathOrString) + if err != nil { + return nil, err + } + if string(certPem) == "" { + return nil, errors.New("empty pem file") + } + } else { + if pathOrString == "" { + return nil, errors.New("empty pem string") + } + certPem = StringToSlice(pathOrString) + } + block, rest := pem.Decode(certPem) + if block == nil || block.Type != "PUBLIC KEY" { + //log.Fatal("failed to decode PEM block containing public key") + return nil, errors.New("failed to decode PEM block containing public key") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Got a %T, with remaining data: %q", pub, rest) + return pub, nil +} diff --git a/app/utils/serialize.go b/app/utils/serialize.go new file mode 100644 index 0000000..1ac4d80 --- /dev/null +++ b/app/utils/serialize.go @@ -0,0 +1,23 @@ +package utils + +import ( + "encoding/json" +) + +func Serialize(data interface{}) []byte { + res, err := json.Marshal(data) + if err != nil { + return []byte{} + } + return res +} + +func Unserialize(b []byte, dst interface{}) { + if err := json.Unmarshal(b, dst); err != nil { + dst = nil + } +} + +func SerializeStr(data interface{}, arg ...interface{}) string { + return string(Serialize(data)) +} diff --git a/app/utils/shuffle.go b/app/utils/shuffle.go new file mode 100644 index 0000000..2c845a8 --- /dev/null +++ b/app/utils/shuffle.go @@ -0,0 +1,48 @@ +package utils + +import ( + "math/rand" + "time" +) + +// 打乱随机字符串 +func ShuffleString(s *string) { + if len(*s) > 1 { + b := []byte(*s) + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(b), func(x, y int) { + b[x], b[y] = b[y], b[x] + }) + *s = string(b) + } +} + +// 打乱随机slice +func ShuffleSliceBytes(b []byte) { + if len(b) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(b), func(x, y int) { + b[x], b[y] = b[y], b[x] + }) + } +} + +// 打乱slice int +func ShuffleSliceInt(i []int) { + if len(i) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(i), func(x, y int) { + i[x], i[y] = i[y], i[x] + }) + } +} + +// 打乱slice interface +func ShuffleSliceInterface(i []interface{}) { + if len(i) > 1 { + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(i), func(x, y int) { + i[x], i[y] = i[y], i[x] + }) + } +} diff --git a/app/utils/sign_check.go b/app/utils/sign_check.go new file mode 100644 index 0000000..798f63d --- /dev/null +++ b/app/utils/sign_check.go @@ -0,0 +1,125 @@ +package utils + +import ( + "applet/app/utils/logx" + "fmt" + "github.com/forgoer/openssl" + "github.com/gin-gonic/gin" + "github.com/syyongx/php2go" + "strings" +) + +var publicKey = []byte(`-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFQD7RL2tDNuwdg0jTfV0zjAzh +WoCWfGrcNiucy2XUHZZU2oGhHv1N10qu3XayTDD4pu4sJ73biKwqR6ZN7IS4Sfon +vrzaXGvrTG4kmdo3XrbrkzmyBHDLTsJvv6pyS2HPl9QPSvKDN0iJ66+KN8QjBpw1 +FNIGe7xbDaJPY733/QIDAQAB +-----END PUBLIC KEY-----`) + +var privateKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCFQD7RL2tDNuwdg0jTfV0zjAzhWoCWfGrcNiucy2XUHZZU2oGh +Hv1N10qu3XayTDD4pu4sJ73biKwqR6ZN7IS4SfonvrzaXGvrTG4kmdo3Xrbrkzmy +BHDLTsJvv6pyS2HPl9QPSvKDN0iJ66+KN8QjBpw1FNIGe7xbDaJPY733/QIDAQAB +AoGADi14wY8XDY7Bbp5yWDZFfV+QW0Xi2qAgSo/k8gjeK8R+I0cgdcEzWF3oz1Q2 +9d+PclVokAAmfj47e0AmXLImqMCSEzi1jDBUFIRoJk9WE1YstE94mrCgV0FW+N/u ++L6OgZcjmF+9dHKprnpaUGQuUV5fF8j0qp8S2Jfs3Sw+dOECQQCQnHALzFjmXXIR +Ez3VSK4ZoYgDIrrpzNst5Hh6AMDNZcG3CrCxlQrgqjgTzBSr3ZSavvkfYRj42STk +TqyX1tQFAkEA6+O6UENoUTk2lG7iO/ta7cdIULnkTGwQqvkgLIUjk6w8E3sBTIfw +rerTEmquw5F42HHE+FMrRat06ZN57lENmQJAYgUHlZevcoZIePZ35Qfcqpbo4Gc8 +Fpm6vwKr/tZf2Vlt0qo2VkhWFS6L0C92m4AX6EQmDHT+Pj7BWNdS+aCuGQJBAOkq +NKPZvWdr8jNOV3mKvxqB/U0uMigIOYGGtvLKt5vkh42J7ILFbHW8w95UbWMKjDUG +X/hF3WQEUo//Imsa2yECQHSZIpJxiTRueoDiyRt0LH+jdbYFUu/6D0UIYXhFvP/p +EZX+hfCfUnNYX59UVpRjSZ66g0CbCjuBPOhmOD+hDeQ= +-----END RSA PRIVATE KEY-----`) + +func GetApiVersion(c *gin.Context) int { + var apiVersion = c.GetHeader("apiVersion") + if StrToInt(apiVersion) == 0 { //没有版本号先不校验 + apiVersion = c.GetHeader("Apiversion") + } + if StrToInt(apiVersion) == 0 { //没有版本号先不校验 + apiVersion = c.GetHeader("api_version") + } + return StrToInt(apiVersion) +} + +//签名校验 +func SignCheck(c *gin.Context) bool { + var apiVersion = GetApiVersion(c) + if apiVersion == 0 { //没有版本号先不校验 + return true + } + //1.通过rsa 解析出 aes + var key = c.GetHeader("key") + + //拼接对应参数 + var uri = c.Request.RequestURI + var query = GetQueryParam(uri) + fmt.Println(query) + query["timestamp"] = c.GetHeader("timestamp") + query["nonce"] = c.GetHeader("nonce") + query["key"] = key + token := c.GetHeader("Authorization") + if token != "" { + // 按空格分割 + parts := strings.SplitN(token, " ", 2) + if len(parts) == 2 && parts[0] == "Bearer" { + token = parts[1] + } + } + query["token"] = token + //2.query参数按照 ASCII 码从小到大排序 + str := JoinStringsInASCII(query, "&", false, false, "") + //3.拼上密钥 + secret := "" + if InArr(c.GetHeader("platform"), []string{"android", "ios"}) { + secret = c.GetString("app_api_secret_key") + } else if c.GetHeader("platform") == "wap" { + secret = c.GetString("h5_api_secret_key") + } else { + secret = c.GetString("applet_api_secret_key") + } + str = fmt.Sprintf("%s&secret=%s", str, secret) + fmt.Println(str) + //4.md5加密 转小写 + sign := strings.ToLower(Md5(str)) + //5.判断跟前端传来的sign是否一致 + if sign != c.GetHeader("sign") { + return false + } + return true +} + +func ResultAes(c *gin.Context, raw []byte) string { + var key = c.GetHeader("key") + base, _ := php2go.Base64Decode(key) + aes, err := RsaDecrypt([]byte(base), privateKey) + if err != nil { + logx.Info(err) + return "" + } + + str, _ := openssl.AesECBEncrypt(raw, aes, openssl.PKCS7_PADDING) + value := php2go.Base64Encode(string(str)) + fmt.Println(value) + + return value +} + +func ResultAesDecrypt(c *gin.Context, raw string) string { + var key = c.GetHeader("key") + base, _ := php2go.Base64Decode(key) + aes, err := RsaDecrypt([]byte(base), privateKey) + if err != nil { + logx.Info(err) + return "" + } + fmt.Println(raw) + value1, _ := php2go.Base64Decode(raw) + if value1 == "" { + return "" + } + str1, _ := openssl.AesECBDecrypt([]byte(value1), aes, openssl.PKCS7_PADDING) + + return string(str1) +} diff --git a/app/utils/slice.go b/app/utils/slice.go new file mode 100644 index 0000000..30bd4ee --- /dev/null +++ b/app/utils/slice.go @@ -0,0 +1,26 @@ +package utils + +// ContainsString is 字符串是否包含在字符串切片里 +func ContainsString(array []string, val string) (index int) { + index = -1 + for i := 0; i < len(array); i++ { + if array[i] == val { + index = i + return + } + } + return +} + +func PaginateSliceInt64(x []int64, skip int, size int) []int64 { + if skip > len(x) { + skip = len(x) + } + + end := skip + size + if end > len(x) { + end = len(x) + } + + return x[skip:end] +} diff --git a/app/utils/slice_and_string.go b/app/utils/slice_and_string.go new file mode 100644 index 0000000..3ae6946 --- /dev/null +++ b/app/utils/slice_and_string.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "reflect" + "strings" + "unsafe" +) + +// string与slice互转,零copy省内存 + +// zero copy to change slice to string +func Slice2String(b []byte) (s string) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pString.Data = pBytes.Data + pString.Len = pBytes.Len + return +} + +// no copy to change string to slice +func StringToSlice(s string) (b []byte) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pBytes.Data = pString.Data + pBytes.Len = pString.Len + pBytes.Cap = pString.Len + return +} + +// 任意slice合并 +func SliceJoin(sep string, elems ...interface{}) string { + l := len(elems) + if l == 0 { + return "" + } + if l == 1 { + s := fmt.Sprint(elems[0]) + sLen := len(s) - 1 + if s[0] == '[' && s[sLen] == ']' { + return strings.Replace(s[1:sLen], " ", sep, -1) + } + return s + } + sep = strings.Replace(fmt.Sprint(elems), " ", sep, -1) + return sep[1 : len(sep)-1] +} diff --git a/app/utils/string.go b/app/utils/string.go new file mode 100644 index 0000000..e7142ef --- /dev/null +++ b/app/utils/string.go @@ -0,0 +1,155 @@ +package utils + +import ( + "fmt" + "github.com/syyongx/php2go" + "reflect" + "sort" + "strings" +) + +func Implode(glue string, args ...interface{}) string { + data := make([]string, len(args)) + for i, s := range args { + data[i] = fmt.Sprint(s) + } + return strings.Join(data, glue) +} + +//字符串是否在数组里 +func InArr(target string, str_array []string) bool { + for _, element := range str_array { + if target == element { + return true + } + } + return false +} + +//把数组的值放到key里 +func ArrayColumn(array interface{}, key string) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + t := reflect.TypeOf(array) + v := reflect.ValueOf(array) + if t.Kind() != reflect.Slice { + return nil, nil + } + if v.Len() == 0 { + return nil, nil + } + for i := 0; i < v.Len(); i++ { + indexv := v.Index(i) + if indexv.Type().Kind() != reflect.Struct { + return nil, nil + } + mapKeyInterface := indexv.FieldByName(key) + if mapKeyInterface.Kind() == reflect.Invalid { + return nil, nil + } + mapKeyString, err := InterfaceToString(mapKeyInterface.Interface()) + if err != nil { + return nil, err + } + result[mapKeyString] = indexv.Interface() + } + return result, err +} + +//转string +func InterfaceToString(v interface{}) (result string, err error) { + switch reflect.TypeOf(v).Kind() { + case reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + result = fmt.Sprintf("%v", v) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + result = fmt.Sprintf("%v", v) + case reflect.String: + result = v.(string) + default: + err = nil + } + return result, err +} + +func HideTrueName(name string) string { + res := "**" + if name != "" { + runs := []rune(name) + leng := len(runs) + if leng <= 3 { + res = string(runs[0:1]) + res + } else if leng < 5 { + res = string(runs[0:2]) + res + } else if leng < 10 { + res = string(runs[0:2]) + "***" + string(runs[leng-2:leng]) + } else if leng < 16 { + res = string(runs[0:3]) + "****" + string(runs[leng-3:leng]) + } else { + res = string(runs[0:4]) + "*****" + string(runs[leng-4:leng]) + } + } + return res +} +func GetQueryParam(uri string) map[string]string { + //根据问号分割路由还是query参数 + uriList := strings.Split(uri, "?") + var query = make(map[string]string, 0) + //有参数才处理 + if len(uriList) == 2 { + //分割query参数 + var queryList = strings.Split(uriList[1], "&") + if len(queryList) > 0 { + //key value 分别赋值 + for _, v := range queryList { + var valueList = strings.Split(v, "=") + if len(valueList) == 2 { + value, _ := php2go.URLDecode(valueList[1]) + if value == "" { + value = valueList[1] + } + query[valueList[0]] = value + } + } + } + } + return query +} + +//JoinStringsInASCII 按照规则,参数名ASCII码从小到大排序后拼接 +//data 待拼接的数据 +//sep 连接符 +//onlyValues 是否只包含参数值,true则不包含参数名,否则参数名和参数值均有 +//includeEmpty 是否包含空值,true则包含空值,否则不包含,注意此参数不影响参数名的存在 +//exceptKeys 被排除的参数名,不参与排序及拼接 +func JoinStringsInASCII(data map[string]string, sep string, onlyValues, includeEmpty bool, exceptKeys ...string) string { + var list []string + var keyList []string + m := make(map[string]int) + if len(exceptKeys) > 0 { + for _, except := range exceptKeys { + m[except] = 1 + } + } + for k := range data { + if _, ok := m[k]; ok { + continue + } + value := data[k] + if !includeEmpty && value == "" { + continue + } + if onlyValues { + keyList = append(keyList, k) + } else { + list = append(list, fmt.Sprintf("%s=%s", k, value)) + } + } + if onlyValues { + sort.Strings(keyList) + for _, v := range keyList { + list = append(list, AnyToString(data[v])) + } + } else { + sort.Strings(list) + } + return strings.Join(list, sep) +} diff --git a/app/utils/time.go b/app/utils/time.go new file mode 100644 index 0000000..6860a57 --- /dev/null +++ b/app/utils/time.go @@ -0,0 +1,226 @@ +package utils + +import ( + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +func StrToTime(s string) (int64, error) { + // delete all not int characters + if s == "" { + return time.Now().Unix(), nil + } + r := make([]rune, 14) + l := 0 + // 过滤除数字以外的字符 + for _, v := range s { + if '0' <= v && v <= '9' { + r[l] = v + l++ + if l == 14 { + break + } + } + } + for l < 14 { + r[l] = '0' // 补0 + l++ + } + t, err := time.Parse("20060102150405", string(r)) + if err != nil { + return 0, err + } + return t.Unix(), nil +} + +func TimeToStr(unixSecTime interface{}, layout ...string) string { + i := AnyToInt64(unixSecTime) + if i == 0 { + return "" + } + f := "2006-01-02 15:04:05" + if len(layout) > 0 { + f = layout[0] + } + return time.Unix(i, 0).Format(f) +} +func Time2String(date time.Time, format string) string { + if format == "" { + format = "2006-01-02 15:04:05" + } + timeS := date.Format(format) + if timeS == "0001-01-01 00:00:00" { + return "" + } + return timeS +} + +func FormatNanoUnix() string { + return strings.Replace(time.Now().Format("20060102150405.0000000"), ".", "", 1) +} + +func TimeParse(format, src string) (time.Time, error) { + return time.ParseInLocation(format, src, time.Local) +} + +func TimeParseStd(src string) time.Time { + t, _ := TimeParse("2006-01-02 15:04:05", src) + return t +} + +func TimeStdParseUnix(src string) int64 { + t, err := TimeParse("2006-01-02 15:04:05", src) + if err != nil { + return 0 + } + return t.Unix() +} + +// 获取一个当前时间 时间间隔 时间戳 +func GetTimeInterval(unit string, amount int) (startTime, endTime int64) { + t := time.Now() + nowTime := t.Unix() + tmpTime := int64(0) + switch unit { + case "years": + tmpTime = time.Date(t.Year()+amount, t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() + case "months": + tmpTime = time.Date(t.Year(), t.Month()+time.Month(amount), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Unix() + case "days": + tmpTime = time.Date(t.Year(), t.Month(), t.Day()+amount, t.Hour(), 0, 0, 0, t.Location()).Unix() + case "hours": + tmpTime = time.Date(t.Year(), t.Month(), t.Day(), t.Hour()+amount, 0, 0, 0, t.Location()).Unix() + } + if amount > 0 { + startTime = nowTime + endTime = tmpTime + } else { + startTime = tmpTime + endTime = nowTime + } + return +} + +// 几天前 +func TimeInterval(newTime int) string { + now := time.Now().Unix() + newTime64 := AnyToInt64(newTime) + if newTime64 >= now { + return "刚刚" + } + interval := now - newTime64 + switch { + case interval < 60: + return AnyToString(interval) + "秒前" + case interval < 60*60: + return AnyToString(interval/60) + "分前" + case interval < 60*60*24: + return AnyToString(interval/60/60) + "小时前" + case interval < 60*60*24*30: + return AnyToString(interval/60/60/24) + "天前" + case interval < 60*60*24*30*12: + return AnyToString(interval/60/60/24/30) + "月前" + default: + return AnyToString(interval/60/60/24/30/12) + "年前" + } +} + +// 时分秒字符串转时间戳,传入示例:8:40 or 8:40:10 +func HmsToUnix(str string) (int64, error) { + t := time.Now() + arr := strings.Split(str, ":") + if len(arr) < 2 { + return 0, errors.New("Time format error") + } + h, _ := strconv.Atoi(arr[0]) + m, _ := strconv.Atoi(arr[1]) + s := 0 + if len(arr) == 3 { + s, _ = strconv.Atoi(arr[3]) + } + formatted1 := fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), h, m, s) + res, err := time.ParseInLocation("20060102150405", formatted1, time.Local) + if err != nil { + return 0, err + } else { + return res.Unix(), nil + } +} + +// 获取特定时间范围 +func GetTimeRange(s string) map[string]int64 { + t := time.Now() + var stime, etime time.Time + + switch s { + case "today": + stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "yesterday": + stime = time.Date(t.Year(), t.Month(), t.Day()-1, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + case "within_seven_days": + // 前6天0点 + stime = time.Date(t.Year(), t.Month(), t.Day()-6, 0, 0, 0, 0, t.Location()) + // 明天 0点 + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "current_month": + stime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, t.Location()) + case "last_month": + stime = time.Date(t.Year(), t.Month()-1, 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + } + + return map[string]int64{ + "start": stime.Unix(), + "end": etime.Unix(), + } +} + +// 获取特定时间范围 +func GetDateTimeRangeStr(s string) (string, string) { + t := time.Now() + var stime, etime time.Time + + switch s { + case "today": + stime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "yesterday": + stime = time.Date(t.Year(), t.Month(), t.Day()-1, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + case "within_seven_days": + // 前6天0点 + stime = time.Date(t.Year(), t.Month(), t.Day()-6, 0, 0, 0, 0, t.Location()) + // 明天 0点 + etime = time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, t.Location()) + case "current_month": + stime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, t.Location()) + case "last_month": + stime = time.Date(t.Year(), t.Month()-1, 0, 0, 0, 0, 0, t.Location()) + etime = time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, t.Location()) + } + + return stime.Format("2006-01-02 15:04:05"), etime.Format("2006-01-02 15:04:05") +} + +//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。 +func GetFirstDateOfMonth(d time.Time) time.Time { + d = d.AddDate(0, 0, -d.Day()+1) + return GetZeroTime(d) +} + +//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 +func GetLastDateOfMonth(d time.Time) time.Time { + return GetFirstDateOfMonth(d).AddDate(0, 1, -1) +} + +//获取某一天的0点时间 +func GetZeroTime(d time.Time) time.Time { + return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) +} diff --git a/app/utils/uuid.go b/app/utils/uuid.go new file mode 100644 index 0000000..da7018b --- /dev/null +++ b/app/utils/uuid.go @@ -0,0 +1,76 @@ +package utils + +import ( + "github.com/sony/sonyflake" + + "applet/app/utils/logx" + "fmt" + "math/rand" + "time" +) + +const ( + KC_RAND_KIND_NUM = 0 // 纯数字 + KC_RAND_KIND_LOWER = 1 // 小写字母 + KC_RAND_KIND_UPPER = 2 // 大写字母 + KC_RAND_KIND_ALL = 3 // 数字、大小写字母 +) + +func newUUID() *[16]byte { + u := &[16]byte{} + rand.Read(u[:16]) + u[8] = (u[8] | 0x80) & 0xBf + u[6] = (u[6] | 0x40) & 0x4f + return u +} + +func UUIDString() string { + u := newUUID() + return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} + +func UUIDHexString() string { + u := newUUID() + return fmt.Sprintf("%x%x%x%x%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:]) +} +func UUIDBinString() string { + u := newUUID() + return fmt.Sprintf("%s", [16]byte(*u)) +} + +func Krand(size int, kind int) []byte { + ikind, kinds, result := kind, [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}, make([]byte, size) + isAll := kind > 2 || kind < 0 + rand.Seed(time.Now().UnixNano()) + for i := 0; i < size; i++ { + if isAll { // random ikind + ikind = rand.Intn(3) + } + scope, base := kinds[ikind][0], kinds[ikind][1] + result[i] = uint8(base + rand.Intn(scope)) + } + return result +} + +// OrderUUID is only num for uuid +func OrderUUID(uid int) string { + ustr := IntToStr(uid) + tstr := Int64ToStr(time.Now().Unix()) + ulen := len(ustr) + tlen := len(tstr) + rlen := 18 - ulen - tlen + krb := Krand(rlen, KC_RAND_KIND_NUM) + return ustr + tstr + string(krb) +} + +var flake *sonyflake.Sonyflake + +func GenId() int64 { + + id, err := flake.NextID() + if err != nil { + _ = logx.Errorf("flake.NextID() failed with %s\n", err) + panic(err) + } + return int64(id) +} diff --git a/app/utils/validator_err_trans.go b/app/utils/validator_err_trans.go new file mode 100644 index 0000000..29d97bf --- /dev/null +++ b/app/utils/validator_err_trans.go @@ -0,0 +1,55 @@ +package utils + +import ( + "fmt" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/locales/en" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + enTranslations "github.com/go-playground/validator/v10/translations/en" + chTranslations "github.com/go-playground/validator/v10/translations/zh" + "reflect" +) + +var ValidatorTrans ut.Translator + +// ValidatorTransInit 验证器错误信息翻译初始化 +// local 通常取决于 http 请求头的 'Accept-Language' +func ValidatorTransInit(local string) (err error) { + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + zhT := zh.New() //chinese + enT := en.New() //english + uni := ut.New(enT, zhT, enT) + + var o bool + ValidatorTrans, o = uni.GetTranslator(local) + if !o { + return fmt.Errorf("uni.GetTranslator(%s) failed", local) + } + // 注册一个方法,从自定义标签label中获取值(用在把字段名映射为中文) + v.RegisterTagNameFunc(func(field reflect.StructField) string { + label := field.Tag.Get("label") + if label == "" { + return field.Name + } + return label + }) + // 注册翻译器 + switch local { + case "en": + err = enTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + case "zh": + err = chTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + default: + err = enTranslations.RegisterDefaultTranslations(v, ValidatorTrans) + } + return + } + return +} + +// ValidatorTransInitZh 验证器错误信息翻译为中文初始化 +func ValidatorTransInitZh() (err error) { + return ValidatorTransInit("zh") +} diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..adb5edb --- /dev/null +++ b/build.sh @@ -0,0 +1,18 @@ +echo "update -> repo" +git fetch +git reset --hard origin/master +echo "update repo -> Success" + +id="git rev-parse --short HEAD" +export ZYOS_APP_COMMIT_ID=`eval $id` +echo "GET the Commit ID for git -> $ZYOS_APP_COMMIT_ID" + +echo "Start build image " + +image_name=registry-vpc.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-mall:${ZYOS_APP_COMMIT_ID} +#final_image_name=registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos:${ZYOS_APP_COMMIT_ID} +docker build -t ${image_name} . + +docker push ${image_name} +echo "Push image -> $image_name Success" +export ZYOS_APP_LATEST_VERSION=${image_name} \ No newline at end of file diff --git a/cmd/task/main.go b/cmd/task/main.go new file mode 100644 index 0000000..ec3c112 --- /dev/null +++ b/cmd/task/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "applet/app/cfg" + "applet/app/db" + "applet/app/task" + "applet/app/utils" + "applet/app/utils/logx" +) + +func init() { + // 加载任务配置 + cfg.InitTaskCfg() + // 日志配置 + cfg.InitLog() + // 初始化redis + cfg.InitCache() + baseDb := *cfg.DB + baseDb.Path = fmt.Sprintf(cfg.DB.Path, cfg.DB.Name) + if err := db.InitDB(&baseDb); err != nil { + panic(err) + } + utils.CurlDebug = true + //cfg.InitMemCache() +} + +func main() { + go func() { + // 初始化jobs方法列表、添加reload方法定时更新任务 + task.Init() + task.Run() + }() + // graceful shutdown + quit := make(chan os.Signal) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + _ = logx.Info("Server exiting...") +} diff --git a/cmd_db.bat b/cmd_db.bat new file mode 100644 index 0000000..7a9e8d5 --- /dev/null +++ b/cmd_db.bat @@ -0,0 +1,25 @@ +@echo off + +set Table=* +set TName="" +set one=%1 + +if "%one%" NEQ "" ( + set Table=%one% + set TName="^%one%$" +) + +set BasePath="./" +set DBUSER="root" +set DBPSW="Fnuo123com@" +set DBNAME="fnuoos_test1" +set DBHOST="119.23.182.117" +set DBPORT="3306" + +del "app\db\model\%Table%.go" + +echo start reverse table %Table% + +xorm reverse mysql "%DBUSER%:%DBPSW%@tcp(%DBHOST%:%DBPORT%)/%DBNAME%?charset=utf8" %BasePath%/etc/db_tpl %BasePath%/app/db/model/ %TName% + +echo end \ No newline at end of file diff --git a/cmd_db.sh b/cmd_db.sh new file mode 100644 index 0000000..ab769e2 --- /dev/null +++ b/cmd_db.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# 使用方法, 直接执行该脚本更新所有表, cmd_db.sh 表名, 如 ./cmd_db.sh tableName + +Table=* +TName="" +if [ "$1" ] ;then + Table=$1 + TName="^$1$" +fi + +BasePath="./" +DBUSER="root" +DBPSW="Fnuo123com@" +DBNAME="fnuoos_test1" +DBHOST="119.23.182.117" +DBPORT="3306" + +rm -rf $BasePath/app/db/model/$Table.go && \ + +xorm reverse mysql "$DBUSER:$DBPSW@tcp($DBHOST:$DBPORT)/$DBNAME?charset=utf8" $BasePath/etc/db_tpl $BasePath/app/db/model/ $TName \ No newline at end of file diff --git a/cmd_run.bat b/cmd_run.bat new file mode 100644 index 0000000..51d7b81 --- /dev/null +++ b/cmd_run.bat @@ -0,0 +1,12 @@ +@echo off + +set BasePath=%~dp0 +set APP=applet.exe +set CfgPath=%BasePath%\etc\cfg.yml + +del %BasePath%\bin\%APP% + +go build -o %BasePath%\bin\%APP% %BasePath%\cmd\main.go && %BasePath%\bin\%APP% -c=%CfgPath% + + +pause diff --git a/cmd_run.sh b/cmd_run.sh new file mode 100644 index 0000000..6758f1b --- /dev/null +++ b/cmd_run.sh @@ -0,0 +1,8 @@ +#!/bin/bash +APP=applet +BasePath=$(dirname $(readlink -f $0)) +CfgPath=$BasePath/etc/cfg.yml +cd $BasePath +rm -rf $BasePath/bin/$APP +go build -o $BasePath/bin/$APP $BasePath/main.go \ +&& $BasePath/bin/$APP -c=$CfgPath \ No newline at end of file diff --git a/cmd_task.bat b/cmd_task.bat new file mode 100644 index 0000000..f70eabc --- /dev/null +++ b/cmd_task.bat @@ -0,0 +1,13 @@ +@echo off + +set Name=task +set BasePath=%~dp0 +set APP=%Name%.exe +set CfgPath=%BasePath%etc\%Name%.yml + +del %BasePath%\bin\%APP% + +go build -o %BasePath%\bin\%APP% %BasePath%\cmd\%Name%\main.go && %BasePath%\bin\%APP% -c=%CfgPath% + + +pause diff --git a/cmd_task.sh b/cmd_task.sh new file mode 100644 index 0000000..8f7da10 --- /dev/null +++ b/cmd_task.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +APP=task +BasePath=$(dirname $(readlink -f $0)) +CfgPath=$BasePath/etc/task.yml +cd $BasePath +rm -rf $BasePath/bin/$APP +go build -o $BasePath/bin/$APP $BasePath/cmd/$APP/main.go \ +&& $BasePath/bin/$APP -c=$CfgPath \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..3e7d945 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,1708 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "termsOfService": "智莺生活后端组", + "contact": { + "name": "sherlockwhite" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/acq/fix": { + "get": { + "description": "拉新活动--fix", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新活动--fix", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/home": { + "post": { + "description": "本期榜单/上期榜单/我的邀请人数和奖励/任务列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新--首页数据", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/qrcode": { + "get": { + "description": "二维码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新--邀请二维码", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/reward/detail": { + "post": { + "description": "拉新活动--我的奖励明细", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新活动--我的奖励明细", + "parameters": [ + { + "description": "1为以发放,2为待发放,3为失效", + "name": "state", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/reward_receive": { + "post": { + "description": "拉新活动--领取奖励", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新活动--领取奖励", + "parameters": [ + { + "description": "任务ID", + "name": "job_id", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/credit/card/config": { + "get": { + "description": "获取信用卡配置", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "信用卡" + ], + "summary": "获取信用卡配置", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/detail": { + "post": { + "description": "多麦商城--商城详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--商城详情", + "parameters": [ + { + "description": "商城id", + "name": "brand_id", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/home": { + "get": { + "description": "多麦商城--首页数据", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--首页数据", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/search": { + "post": { + "description": "多麦商城--搜索", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--搜索", + "parameters": [ + { + "description": "搜索关键词", + "name": "key", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/update": { + "get": { + "description": "多麦商城--更新数据", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--更新数据", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/logistic/query": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "物流" + ], + "summary": "快递100物流查询", + "parameters": [ + { + "description": "logisticQueryReq", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.logisticQueryReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"该快递公司不支持查询\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/mod/pub.flutter.duomai.mall.detail.page": { + "get": { + "description": "多麦商城详情页样式", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城详情页样式", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/mod/pub.flutter.duomai.mall.home.page": { + "get": { + "description": "多麦商城首页样式", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城首页样式", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/mod/pub.flutter.duomai.mall.search.page": { + "get": { + "description": "多麦商城搜索页样式", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城搜索页样式", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/card/activation": { + "post": { + "description": "权益卡激活", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡激活", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.privilegeOpenCardCheckReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/open_card/check": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡卡号卡密检测", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.privilegeOpenCardCheckReq" + } + } + ], + "responses": { + "200": { + "description": "0:不存在 1:已经被使用 2:可用", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/open_card/order_query": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡开卡订单查询页面", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/hdl.PrivilegeOpenCardOrdQueryPageResp" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"验证码错误\"}", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡开卡订单查询", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.PrivilegeOpenCardOrdQueryReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/model.PrivilegeOpenCardOrd" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"验证码错误\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/open_card/order_suc": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "订单支付成功页面", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/model.PrivilegeOpenCardOrd" + } + } + } + } + }, + "/api/v1/privilege/open_card/pay_page": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡开卡支付页面", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/hdl.privilegeOpenCardPayPageResp" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"权益卡配置缺失\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/shake_ticket/:goods_id/:type": { + "get": { + "description": "收藏/领券买/分享赚", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "抖券" + ], + "summary": "抖券商品收藏/领券买/分享赚", + "parameters": [ + { + "type": "string", + "description": "商品id", + "name": "goods_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "类型(0取消收藏,1收藏,2点击 领券买,3点击分享赚)", + "name": "type", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/shake_ticket_list": { + "get": { + "description": "定向计划/高佣专场/精选低价包邮/偏远地区包邮/抖货商品/各大榜单商品/今日值得买", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "抖券" + ], + "summary": "抖券商品推荐列表", + "parameters": [ + { + "type": "string", + "description": "页码", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "页数", + "name": "page_size", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "分类id(3定向计划/4高佣专场/5精选低价包邮/6偏远地区包邮/7抖货商品/8各大榜单商品/9今日值得买)", + "name": "category_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "渠道", + "name": "pvd", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/md.IndexRecommendList" + } + } + } + } + }, + "/api/v1/sign/fast/in": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "登录、注册" + ], + "summary": "用户手机快速登录", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/md.FastLoginRequestBody" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/md.LoginResponse" + } + }, + "400": { + "description": "{\"code\":400001,\"data\":[],\"msg\":\"请求参数错误\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/sub_region_list": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "获取区域id下的区域", + "parameters": [ + { + "type": "string", + "description": "上级地区类型:root(查询省级列表)、province(省级ID下的城市)、city(市级id下的区域)", + "name": "parent", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "上级地区id", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/hdl.resultItem" + } + } + }, + "400": { + "description": "{\"code\":400001,\"data\":[],\"msg\":\"请求参数错误\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/address/:id": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址详情", + "parameters": [ + { + "type": "string", + "description": "地址id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/model.UserAddress" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"地址不存在\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/address/delete/:id": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址删除", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"地址不存在\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/address/update": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址新增、编辑", + "parameters": [ + { + "description": "json参数,Id不传为新增", + "name": "\"\"", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.updateAddressReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"地址不存在\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/addresses": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址列表", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/model.UserAddress" + } + } + }, + "500": { + "description": "{\"code\":500000,\"data\":[],\"msg\":\"数据库操作失败\"}", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "hdl.PrivilegeOpenCardOrdQueryPageResp": { + "type": "object", + "properties": { + "btn_bg_color_1": { + "type": "string" + }, + "btn_bg_color_2": { + "type": "string" + }, + "btn_text": { + "type": "string" + }, + "btn_text_color": { + "type": "string" + }, + "header_img": { + "type": "string" + }, + "logistic_company": { + "type": "array", + "items": { + "$ref": "#/definitions/model.LogisticCompany" + } + } + } + }, + "hdl.PrivilegeOpenCardOrdQueryReq": { + "type": "object", + "required": [ + "com", + "num" + ], + "properties": { + "com": { + "description": "快递公司名称", + "type": "string" + }, + "num": { + "description": "快递单号", + "type": "string" + } + } + }, + "hdl.logisticQueryReq": { + "type": "object", + "required": [ + "com", + "num" + ], + "properties": { + "com": { + "description": "快递公司名称", + "type": "string" + }, + "num": { + "description": "快递单号", + "type": "string" + } + } + }, + "hdl.privilegeOpenCardCheckReq": { + "type": "object", + "required": [ + "key", + "num" + ], + "properties": { + "key": { + "description": "卡密", + "type": "string" + }, + "num": { + "description": "卡号", + "type": "string" + } + } + }, + "hdl.privilegeOpenCardPayPageResp": { + "type": "object", + "properties": { + "amount": { + "description": "付费金额", + "type": "string" + }, + "card_type": { + "description": "卡的类型:\"1\"实体卡 \"2\"虚拟卡", + "type": "string" + }, + "date_type": { + "description": "日期类型:month:月 season:季 year:年 forever:永久", + "type": "string" + }, + "page_style": { + "description": "页面样式", + "$ref": "#/definitions/hdl.privilegeOpenCardPayStyle" + } + } + }, + "hdl.privilegeOpenCardPayStyle": { + "type": "object", + "properties": { + "exclusive_privilege": { + "description": "专属特权", + "type": "array", + "items": { + "type": "object", + "properties": { + "brand_id": { + "type": "string" + }, + "brand_img": { + "type": "string" + }, + "brand_img_url": { + "type": "string" + }, + "brand_name": { + "type": "string" + }, + "is_show": { + "type": "string" + }, + "sub_title": { + "type": "string" + } + } + } + }, + "payment_btn": { + "description": "底部支付按钮", + "type": "array", + "items": { + "type": "object", + "properties": { + "bg_img": { + "type": "string" + }, + "bg_img_url": { + "type": "string" + }, + "func": { + "type": "string" + }, + "name": { + "type": "string" + }, + "text": { + "type": "string" + }, + "text_color": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "payment_choose_icon": { + "description": "支付方式选中、未选中图标", + "type": "array", + "items": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "payment_style": { + "description": "支付方式", + "type": "array", + "items": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "style": { + "description": "头部样式", + "type": "object", + "properties": { + "header_bg_img": { + "type": "string" + }, + "header_bg_img_url": { + "type": "string" + }, + "special_deals_img": { + "type": "string" + }, + "special_deals_img_url": { + "type": "string" + }, + "special_deals_text": { + "type": "string" + } + } + } + } + }, + "hdl.resultItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "440100000000" + }, + "name": { + "type": "string", + "example": "city" + } + } + }, + "hdl.updateAddressReq": { + "type": "object", + "required": [ + "city_id", + "county_id", + "detail", + "phone", + "province_id", + "receiver" + ], + "properties": { + "city_id": { + "type": "string" + }, + "county_id": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "phone": { + "type": "string" + }, + "post_code": { + "type": "string" + }, + "province_id": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "md.Apple": { + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + }, + "md.FastLoginRequestBody": { + "type": "object", + "properties": { + "apple": { + "$ref": "#/definitions/md.Apple" + }, + "captcha": { + "type": "string" + }, + "is_not_create": { + "type": "string" + }, + "mobile": { + "type": "string" + }, + "parent_uid": { + "type": "string" + }, + "qq": { + "$ref": "#/definitions/md.QQLogin" + }, + "return_user_msg": { + "type": "string" + }, + "taobao": { + "$ref": "#/definitions/md.TaobaoLogin" + }, + "wechat": { + "$ref": "#/definitions/md.WeChat" + }, + "wechat_mini": { + "$ref": "#/definitions/md.WeChatMiniApp" + }, + "zone": { + "type": "string" + } + } + }, + "md.IndexRecommendList": { + "type": "object", + "properties": { + "good": { + "type": "array", + "items": { + "$ref": "#/definitions/md.RecommendGood" + } + }, + "provider": { + "description": "BarTitleList []BarTitle ` + "`" + `json:\"bar_title_list\"` + "`" + `", + "type": "string" + } + } + }, + "md.LoginResponse": { + "type": "object", + "properties": { + "bind_phone_enable": { + "type": "string" + }, + "is_pid": { + "type": "string" + }, + "perms": { + "type": "array", + "items": { + "type": "string" + } + }, + "phone": { + "type": "string" + }, + "register_invite_code_enable": { + "type": "string" + }, + "register_popup_condition": { + "description": "弹出类型设置", + "$ref": "#/definitions/md.RegisterPopupCondition" + }, + "token": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "wechat_applet_open_id": { + "type": "string" + }, + "wechat_union_id": { + "type": "string" + } + } + }, + "md.Marquee": { + "type": "object", + "properties": { + "avatar_url": { + "type": "string" + }, + "content": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "md.ProductDetailResponse": { + "type": "object", + "properties": { + "commission": { + "type": "string" + }, + "coupon_price": { + "type": "string" + }, + "good_id": { + "type": "string" + }, + "mod_list": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "provider": { + "type": "string" + }, + "provider_name": { + "type": "string" + }, + "shop_avatar": { + "type": "string" + } + } + }, + "md.QQLogin": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "city": { + "type": "string" + }, + "expires_in": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "pay_token": { + "type": "string" + }, + "pf": { + "type": "string" + }, + "pf_key": { + "type": "string" + }, + "province": { + "type": "string" + }, + "ret": { + "type": "string" + }, + "unionid": { + "type": "string" + } + } + }, + "md.RecommendGood": { + "type": "object", + "properties": { + "commission": { + "type": "string" + }, + "coupon": { + "type": "string" + }, + "current_price": { + "type": "string" + }, + "detail_data": { + "$ref": "#/definitions/md.ProductDetailResponse" + }, + "good_id": { + "type": "string" + }, + "good_image": { + "type": "string" + }, + "good_title": { + "type": "string" + }, + "inorder_count": { + "type": "string" + }, + "is_collect": { + "type": "integer" + }, + "is_coupons": { + "type": "integer" + }, + "is_share": { + "type": "integer" + }, + "market_price": { + "type": "string" + }, + "marquee_list": { + "type": "array", + "items": { + "$ref": "#/definitions/md.Marquee" + } + }, + "provider": { + "type": "string" + }, + "provider_name": { + "type": "string" + }, + "pvd": { + "type": "string" + }, + "shop_avatar": { + "type": "string" + }, + "shop_name": { + "type": "string" + }, + "video": { + "type": "string" + } + } + }, + "md.RegisterPopupCondition": { + "type": "object", + "properties": { + "invite_code": { + "description": "邀请码设置:弹出类型是激活码的时候起作用", + "type": "object", + "properties": { + "popup": { + "description": "是否弹出 “0”否 “1”是", + "type": "string" + }, + "should_input": { + "description": "是否必填 “0”否 “1”是", + "type": "string" + } + } + }, + "popup_type": { + "description": "弹出类型:“0”关闭 ”1”激活码 “2”邀请码", + "type": "string" + }, + "should_input": { + "description": "是否必填 “0”否 “1”是", + "type": "string" + } + } + }, + "md.TaobaoLogin": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "auth_code": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "nick_name": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "sid": { + "type": "string" + } + } + }, + "md.WeChat": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "city": { + "type": "string" + }, + "expires_in": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "pay_token": { + "type": "string" + }, + "pf": { + "type": "string" + }, + "pf_key": { + "type": "string" + }, + "province": { + "type": "string" + }, + "ret": { + "type": "string" + }, + "unionid": { + "type": "string" + } + } + }, + "md.WeChatMiniApp": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "code": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "unionid": { + "type": "string" + } + } + }, + "model.LogisticCompany": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "model.PrivilegeOpenCardOrd": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "after_sale_id": { + "type": "integer" + }, + "card_key": { + "type": "string" + }, + "card_num": { + "type": "string" + }, + "card_type": { + "type": "integer" + }, + "cost_price": { + "type": "string" + }, + "create_time": { + "type": "string" + }, + "date_type": { + "type": "integer" + }, + "given_data": { + "type": "string" + }, + "logistic_company": { + "type": "string" + }, + "logistic_num": { + "type": "string" + }, + "ord_id": { + "type": "integer" + }, + "pay_channel": { + "type": "integer" + }, + "pay_time": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "settle_at": { + "type": "integer" + }, + "state": { + "type": "integer" + }, + "uid": { + "type": "integer" + }, + "update_time": { + "type": "string" + } + } + }, + "model.UserAddress": { + "type": "object", + "properties": { + "city_id": { + "type": "string" + }, + "city_name": { + "type": "string" + }, + "county_id": { + "type": "string" + }, + "county_name": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "phone": { + "type": "string" + }, + "post_code": { + "type": "string" + }, + "province_id": { + "type": "string" + }, + "province_name": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "uid": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "MasterID": { + "type": "apiKey", + "name": "MasterID", + "in": "header" + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "localhost:5000", + BasePath: "/", + Schemes: []string{}, + Title: "智莺生活移动端接口", + Description: "移动端接口", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..527c14e --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,1646 @@ +{ + "swagger": "2.0", + "info": { + "description": "移动端接口", + "title": "智莺生活移动端接口", + "termsOfService": "智莺生活后端组", + "contact": { + "name": "sherlockwhite" + }, + "version": "1.0" + }, + "host": "localhost:5000", + "basePath": "/", + "paths": { + "/api/v1/acq/fix": { + "get": { + "description": "拉新活动--fix", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新活动--fix", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/home": { + "post": { + "description": "本期榜单/上期榜单/我的邀请人数和奖励/任务列表", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新--首页数据", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/qrcode": { + "get": { + "description": "二维码", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新--邀请二维码", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/reward/detail": { + "post": { + "description": "拉新活动--我的奖励明细", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新活动--我的奖励明细", + "parameters": [ + { + "description": "1为以发放,2为待发放,3为失效", + "name": "state", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/acquisition/reward_receive": { + "post": { + "description": "拉新活动--领取奖励", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "拉新活动" + ], + "summary": "拉新活动--领取奖励", + "parameters": [ + { + "description": "任务ID", + "name": "job_id", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/credit/card/config": { + "get": { + "description": "获取信用卡配置", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "信用卡" + ], + "summary": "获取信用卡配置", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/detail": { + "post": { + "description": "多麦商城--商城详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--商城详情", + "parameters": [ + { + "description": "商城id", + "name": "brand_id", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/home": { + "get": { + "description": "多麦商城--首页数据", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--首页数据", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/search": { + "post": { + "description": "多麦商城--搜索", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--搜索", + "parameters": [ + { + "description": "搜索关键词", + "name": "key", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/duomai/mall/update": { + "get": { + "description": "多麦商城--更新数据", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城--更新数据", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/logistic/query": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "物流" + ], + "summary": "快递100物流查询", + "parameters": [ + { + "description": "logisticQueryReq", + "name": "req", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.logisticQueryReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"该快递公司不支持查询\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/mod/pub.flutter.duomai.mall.detail.page": { + "get": { + "description": "多麦商城详情页样式", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城详情页样式", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/mod/pub.flutter.duomai.mall.home.page": { + "get": { + "description": "多麦商城首页样式", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城首页样式", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/mod/pub.flutter.duomai.mall.search.page": { + "get": { + "description": "多麦商城搜索页样式", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "多麦商城" + ], + "summary": "多麦商城搜索页样式", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/card/activation": { + "post": { + "description": "权益卡激活", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡激活", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.privilegeOpenCardCheckReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/open_card/check": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡卡号卡密检测", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.privilegeOpenCardCheckReq" + } + } + ], + "responses": { + "200": { + "description": "0:不存在 1:已经被使用 2:可用", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/open_card/order_query": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡开卡订单查询页面", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/hdl.PrivilegeOpenCardOrdQueryPageResp" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"验证码错误\"}", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡开卡订单查询", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.PrivilegeOpenCardOrdQueryReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/model.PrivilegeOpenCardOrd" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"验证码错误\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/privilege/open_card/order_suc": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "订单支付成功页面", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/model.PrivilegeOpenCardOrd" + } + } + } + } + }, + "/api/v1/privilege/open_card/pay_page": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "权益卡" + ], + "summary": "权益卡开卡支付页面", + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/hdl.privilegeOpenCardPayPageResp" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"权益卡配置缺失\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/shake_ticket/:goods_id/:type": { + "get": { + "description": "收藏/领券买/分享赚", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "抖券" + ], + "summary": "抖券商品收藏/领券买/分享赚", + "parameters": [ + { + "type": "string", + "description": "商品id", + "name": "goods_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "类型(0取消收藏,1收藏,2点击 领券买,3点击分享赚)", + "name": "type", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/shake_ticket_list": { + "get": { + "description": "定向计划/高佣专场/精选低价包邮/偏远地区包邮/抖货商品/各大榜单商品/今日值得买", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "抖券" + ], + "summary": "抖券商品推荐列表", + "parameters": [ + { + "type": "string", + "description": "页码", + "name": "page", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "页数", + "name": "page_size", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "分类id(3定向计划/4高佣专场/5精选低价包邮/6偏远地区包邮/7抖货商品/8各大榜单商品/9今日值得买)", + "name": "category_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "渠道", + "name": "pvd", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/md.IndexRecommendList" + } + } + } + } + }, + "/api/v1/sign/fast/in": { + "post": { + "produces": [ + "application/json" + ], + "tags": [ + "登录、注册" + ], + "summary": "用户手机快速登录", + "parameters": [ + { + "description": "json", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/md.FastLoginRequestBody" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/md.LoginResponse" + } + }, + "400": { + "description": "{\"code\":400001,\"data\":[],\"msg\":\"请求参数错误\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/sub_region_list": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "获取区域id下的区域", + "parameters": [ + { + "type": "string", + "description": "上级地区类型:root(查询省级列表)、province(省级ID下的城市)、city(市级id下的区域)", + "name": "parent", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "上级地区id", + "name": "id", + "in": "query" + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/hdl.resultItem" + } + } + }, + "400": { + "description": "{\"code\":400001,\"data\":[],\"msg\":\"请求参数错误\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/address/:id": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址详情", + "parameters": [ + { + "type": "string", + "description": "地址id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "$ref": "#/definitions/model.UserAddress" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"地址不存在\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/address/delete/:id": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址删除", + "parameters": [ + { + "type": "string", + "description": "ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"地址不存在\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/address/update": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址新增、编辑", + "parameters": [ + { + "description": "json参数,Id不传为新增", + "name": "\"\"", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/hdl.updateAddressReq" + } + } + ], + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "string" + } + }, + "400": { + "description": "{\"code\":400000,\"data\":[],\"msg\":\"地址不存在\"}", + "schema": { + "type": "string" + } + } + } + } + }, + "/api/v1/user/addresses": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "地址" + ], + "summary": "用户地址列表", + "responses": { + "200": { + "description": "ok", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/model.UserAddress" + } + } + }, + "500": { + "description": "{\"code\":500000,\"data\":[],\"msg\":\"数据库操作失败\"}", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "hdl.PrivilegeOpenCardOrdQueryPageResp": { + "type": "object", + "properties": { + "btn_bg_color_1": { + "type": "string" + }, + "btn_bg_color_2": { + "type": "string" + }, + "btn_text": { + "type": "string" + }, + "btn_text_color": { + "type": "string" + }, + "header_img": { + "type": "string" + }, + "logistic_company": { + "type": "array", + "items": { + "$ref": "#/definitions/model.LogisticCompany" + } + } + } + }, + "hdl.PrivilegeOpenCardOrdQueryReq": { + "type": "object", + "required": [ + "com", + "num" + ], + "properties": { + "com": { + "description": "快递公司名称", + "type": "string" + }, + "num": { + "description": "快递单号", + "type": "string" + } + } + }, + "hdl.logisticQueryReq": { + "type": "object", + "required": [ + "com", + "num" + ], + "properties": { + "com": { + "description": "快递公司名称", + "type": "string" + }, + "num": { + "description": "快递单号", + "type": "string" + } + } + }, + "hdl.privilegeOpenCardCheckReq": { + "type": "object", + "required": [ + "key", + "num" + ], + "properties": { + "key": { + "description": "卡密", + "type": "string" + }, + "num": { + "description": "卡号", + "type": "string" + } + } + }, + "hdl.privilegeOpenCardPayPageResp": { + "type": "object", + "properties": { + "amount": { + "description": "付费金额", + "type": "string" + }, + "card_type": { + "description": "卡的类型:\"1\"实体卡 \"2\"虚拟卡", + "type": "string" + }, + "date_type": { + "description": "日期类型:month:月 season:季 year:年 forever:永久", + "type": "string" + }, + "page_style": { + "description": "页面样式", + "$ref": "#/definitions/hdl.privilegeOpenCardPayStyle" + } + } + }, + "hdl.privilegeOpenCardPayStyle": { + "type": "object", + "properties": { + "exclusive_privilege": { + "description": "专属特权", + "type": "array", + "items": { + "type": "object", + "properties": { + "brand_id": { + "type": "string" + }, + "brand_img": { + "type": "string" + }, + "brand_img_url": { + "type": "string" + }, + "brand_name": { + "type": "string" + }, + "is_show": { + "type": "string" + }, + "sub_title": { + "type": "string" + } + } + } + }, + "payment_btn": { + "description": "底部支付按钮", + "type": "array", + "items": { + "type": "object", + "properties": { + "bg_img": { + "type": "string" + }, + "bg_img_url": { + "type": "string" + }, + "func": { + "type": "string" + }, + "name": { + "type": "string" + }, + "text": { + "type": "string" + }, + "text_color": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "payment_choose_icon": { + "description": "支付方式选中、未选中图标", + "type": "array", + "items": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "payment_style": { + "description": "支付方式", + "type": "array", + "items": { + "type": "object", + "properties": { + "icon": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "style": { + "description": "头部样式", + "type": "object", + "properties": { + "header_bg_img": { + "type": "string" + }, + "header_bg_img_url": { + "type": "string" + }, + "special_deals_img": { + "type": "string" + }, + "special_deals_img_url": { + "type": "string" + }, + "special_deals_text": { + "type": "string" + } + } + } + } + }, + "hdl.resultItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "440100000000" + }, + "name": { + "type": "string", + "example": "city" + } + } + }, + "hdl.updateAddressReq": { + "type": "object", + "required": [ + "city_id", + "county_id", + "detail", + "phone", + "province_id", + "receiver" + ], + "properties": { + "city_id": { + "type": "string" + }, + "county_id": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "phone": { + "type": "string" + }, + "post_code": { + "type": "string" + }, + "province_id": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "md.Apple": { + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + }, + "md.FastLoginRequestBody": { + "type": "object", + "properties": { + "apple": { + "$ref": "#/definitions/md.Apple" + }, + "captcha": { + "type": "string" + }, + "is_not_create": { + "type": "string" + }, + "mobile": { + "type": "string" + }, + "parent_uid": { + "type": "string" + }, + "qq": { + "$ref": "#/definitions/md.QQLogin" + }, + "return_user_msg": { + "type": "string" + }, + "taobao": { + "$ref": "#/definitions/md.TaobaoLogin" + }, + "wechat": { + "$ref": "#/definitions/md.WeChat" + }, + "wechat_mini": { + "$ref": "#/definitions/md.WeChatMiniApp" + }, + "zone": { + "type": "string" + } + } + }, + "md.IndexRecommendList": { + "type": "object", + "properties": { + "good": { + "type": "array", + "items": { + "$ref": "#/definitions/md.RecommendGood" + } + }, + "provider": { + "description": "BarTitleList []BarTitle `json:\"bar_title_list\"`", + "type": "string" + } + } + }, + "md.LoginResponse": { + "type": "object", + "properties": { + "bind_phone_enable": { + "type": "string" + }, + "is_pid": { + "type": "string" + }, + "perms": { + "type": "array", + "items": { + "type": "string" + } + }, + "phone": { + "type": "string" + }, + "register_invite_code_enable": { + "type": "string" + }, + "register_popup_condition": { + "description": "弹出类型设置", + "$ref": "#/definitions/md.RegisterPopupCondition" + }, + "token": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "wechat_applet_open_id": { + "type": "string" + }, + "wechat_union_id": { + "type": "string" + } + } + }, + "md.Marquee": { + "type": "object", + "properties": { + "avatar_url": { + "type": "string" + }, + "content": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "md.ProductDetailResponse": { + "type": "object", + "properties": { + "commission": { + "type": "string" + }, + "coupon_price": { + "type": "string" + }, + "good_id": { + "type": "string" + }, + "mod_list": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "provider": { + "type": "string" + }, + "provider_name": { + "type": "string" + }, + "shop_avatar": { + "type": "string" + } + } + }, + "md.QQLogin": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "city": { + "type": "string" + }, + "expires_in": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "pay_token": { + "type": "string" + }, + "pf": { + "type": "string" + }, + "pf_key": { + "type": "string" + }, + "province": { + "type": "string" + }, + "ret": { + "type": "string" + }, + "unionid": { + "type": "string" + } + } + }, + "md.RecommendGood": { + "type": "object", + "properties": { + "commission": { + "type": "string" + }, + "coupon": { + "type": "string" + }, + "current_price": { + "type": "string" + }, + "detail_data": { + "$ref": "#/definitions/md.ProductDetailResponse" + }, + "good_id": { + "type": "string" + }, + "good_image": { + "type": "string" + }, + "good_title": { + "type": "string" + }, + "inorder_count": { + "type": "string" + }, + "is_collect": { + "type": "integer" + }, + "is_coupons": { + "type": "integer" + }, + "is_share": { + "type": "integer" + }, + "market_price": { + "type": "string" + }, + "marquee_list": { + "type": "array", + "items": { + "$ref": "#/definitions/md.Marquee" + } + }, + "provider": { + "type": "string" + }, + "provider_name": { + "type": "string" + }, + "pvd": { + "type": "string" + }, + "shop_avatar": { + "type": "string" + }, + "shop_name": { + "type": "string" + }, + "video": { + "type": "string" + } + } + }, + "md.RegisterPopupCondition": { + "type": "object", + "properties": { + "invite_code": { + "description": "邀请码设置:弹出类型是激活码的时候起作用", + "type": "object", + "properties": { + "popup": { + "description": "是否弹出 “0”否 “1”是", + "type": "string" + }, + "should_input": { + "description": "是否必填 “0”否 “1”是", + "type": "string" + } + } + }, + "popup_type": { + "description": "弹出类型:“0”关闭 ”1”激活码 “2”邀请码", + "type": "string" + }, + "should_input": { + "description": "是否必填 “0”否 “1”是", + "type": "string" + } + } + }, + "md.TaobaoLogin": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "auth_code": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "nick_name": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "sid": { + "type": "string" + } + } + }, + "md.WeChat": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "city": { + "type": "string" + }, + "expires_in": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "pay_token": { + "type": "string" + }, + "pf": { + "type": "string" + }, + "pf_key": { + "type": "string" + }, + "province": { + "type": "string" + }, + "ret": { + "type": "string" + }, + "unionid": { + "type": "string" + } + } + }, + "md.WeChatMiniApp": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "code": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "open_id": { + "type": "string" + }, + "unionid": { + "type": "string" + } + } + }, + "model.LogisticCompany": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "model.PrivilegeOpenCardOrd": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "after_sale_id": { + "type": "integer" + }, + "card_key": { + "type": "string" + }, + "card_num": { + "type": "string" + }, + "card_type": { + "type": "integer" + }, + "cost_price": { + "type": "string" + }, + "create_time": { + "type": "string" + }, + "date_type": { + "type": "integer" + }, + "given_data": { + "type": "string" + }, + "logistic_company": { + "type": "string" + }, + "logistic_num": { + "type": "string" + }, + "ord_id": { + "type": "integer" + }, + "pay_channel": { + "type": "integer" + }, + "pay_time": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "settle_at": { + "type": "integer" + }, + "state": { + "type": "integer" + }, + "uid": { + "type": "integer" + }, + "update_time": { + "type": "string" + } + } + }, + "model.UserAddress": { + "type": "object", + "properties": { + "city_id": { + "type": "string" + }, + "city_name": { + "type": "string" + }, + "county_id": { + "type": "string" + }, + "county_name": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "phone": { + "type": "string" + }, + "post_code": { + "type": "string" + }, + "province_id": { + "type": "string" + }, + "province_name": { + "type": "string" + }, + "receiver": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "uid": { + "type": "integer" + } + } + } + }, + "securityDefinitions": { + "MasterID": { + "type": "apiKey", + "name": "MasterID", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..ca2a7a9 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,1084 @@ +basePath: / +definitions: + hdl.PrivilegeOpenCardOrdQueryPageResp: + properties: + btn_bg_color_1: + type: string + btn_bg_color_2: + type: string + btn_text: + type: string + btn_text_color: + type: string + header_img: + type: string + logistic_company: + items: + $ref: '#/definitions/model.LogisticCompany' + type: array + type: object + hdl.PrivilegeOpenCardOrdQueryReq: + properties: + com: + description: 快递公司名称 + type: string + num: + description: 快递单号 + type: string + required: + - com + - num + type: object + hdl.logisticQueryReq: + properties: + com: + description: 快递公司名称 + type: string + num: + description: 快递单号 + type: string + required: + - com + - num + type: object + hdl.privilegeOpenCardCheckReq: + properties: + key: + description: 卡密 + type: string + num: + description: 卡号 + type: string + required: + - key + - num + type: object + hdl.privilegeOpenCardPayPageResp: + properties: + amount: + description: 付费金额 + type: string + card_type: + description: 卡的类型:"1"实体卡 "2"虚拟卡 + type: string + date_type: + description: 日期类型:month:月 season:季 year:年 forever:永久 + type: string + page_style: + $ref: '#/definitions/hdl.privilegeOpenCardPayStyle' + description: 页面样式 + type: object + hdl.privilegeOpenCardPayStyle: + properties: + exclusive_privilege: + description: 专属特权 + items: + properties: + brand_id: + type: string + brand_img: + type: string + brand_img_url: + type: string + brand_name: + type: string + is_show: + type: string + sub_title: + type: string + type: object + type: array + payment_btn: + description: 底部支付按钮 + items: + properties: + bg_img: + type: string + bg_img_url: + type: string + func: + type: string + name: + type: string + text: + type: string + text_color: + type: string + type: + type: string + type: object + type: array + payment_choose_icon: + description: 支付方式选中、未选中图标 + items: + properties: + icon: + type: string + icon_url: + type: string + type: + type: string + type: object + type: array + payment_style: + description: 支付方式 + items: + properties: + icon: + type: string + icon_url: + type: string + type: + type: string + type: object + type: array + style: + description: 头部样式 + properties: + header_bg_img: + type: string + header_bg_img_url: + type: string + special_deals_img: + type: string + special_deals_img_url: + type: string + special_deals_text: + type: string + type: object + type: object + hdl.resultItem: + properties: + id: + example: "440100000000" + type: string + name: + example: city + type: string + type: object + hdl.updateAddressReq: + properties: + city_id: + type: string + county_id: + type: string + detail: + type: string + id: + type: integer + phone: + type: string + post_code: + type: string + province_id: + type: string + receiver: + type: string + tag: + type: string + required: + - city_id + - county_id + - detail + - phone + - province_id + - receiver + type: object + md.Apple: + properties: + token: + type: string + type: object + md.FastLoginRequestBody: + properties: + apple: + $ref: '#/definitions/md.Apple' + captcha: + type: string + is_not_create: + type: string + mobile: + type: string + parent_uid: + type: string + qq: + $ref: '#/definitions/md.QQLogin' + return_user_msg: + type: string + taobao: + $ref: '#/definitions/md.TaobaoLogin' + wechat: + $ref: '#/definitions/md.WeChat' + wechat_mini: + $ref: '#/definitions/md.WeChatMiniApp' + zone: + type: string + type: object + md.IndexRecommendList: + properties: + good: + items: + $ref: '#/definitions/md.RecommendGood' + type: array + provider: + description: BarTitleList []BarTitle `json:"bar_title_list"` + type: string + type: object + md.LoginResponse: + properties: + bind_phone_enable: + type: string + is_pid: + type: string + perms: + items: + type: string + type: array + phone: + type: string + register_invite_code_enable: + type: string + register_popup_condition: + $ref: '#/definitions/md.RegisterPopupCondition' + description: 弹出类型设置 + token: + type: string + user_id: + type: string + username: + type: string + wechat_applet_open_id: + type: string + wechat_union_id: + type: string + type: object + md.Marquee: + properties: + avatar_url: + type: string + content: + type: string + name: + type: string + type: object + md.ProductDetailResponse: + properties: + commission: + type: string + coupon_price: + type: string + good_id: + type: string + mod_list: + items: + additionalProperties: true + type: object + type: array + provider: + type: string + provider_name: + type: string + shop_avatar: + type: string + type: object + md.QQLogin: + properties: + access_token: + type: string + avatar_url: + type: string + city: + type: string + expires_in: + type: string + gender: + type: string + nickname: + type: string + open_id: + type: string + pay_token: + type: string + pf: + type: string + pf_key: + type: string + province: + type: string + ret: + type: string + unionid: + type: string + type: object + md.RecommendGood: + properties: + commission: + type: string + coupon: + type: string + current_price: + type: string + detail_data: + $ref: '#/definitions/md.ProductDetailResponse' + good_id: + type: string + good_image: + type: string + good_title: + type: string + inorder_count: + type: string + is_collect: + type: integer + is_coupons: + type: integer + is_share: + type: integer + market_price: + type: string + marquee_list: + items: + $ref: '#/definitions/md.Marquee' + type: array + provider: + type: string + provider_name: + type: string + pvd: + type: string + shop_avatar: + type: string + shop_name: + type: string + video: + type: string + type: object + md.RegisterPopupCondition: + properties: + invite_code: + description: 邀请码设置:弹出类型是激活码的时候起作用 + properties: + popup: + description: 是否弹出 “0”否 “1”是 + type: string + should_input: + description: 是否必填 “0”否 “1”是 + type: string + type: object + popup_type: + description: 弹出类型:“0”关闭 ”1”激活码 “2”邀请码 + type: string + should_input: + description: 是否必填 “0”否 “1”是 + type: string + type: object + md.TaobaoLogin: + properties: + access_token: + type: string + auth_code: + type: string + avatar_url: + type: string + nick_name: + type: string + open_id: + type: string + sid: + type: string + type: object + md.WeChat: + properties: + access_token: + type: string + avatar_url: + type: string + city: + type: string + expires_in: + type: string + gender: + type: string + nickname: + type: string + open_id: + type: string + pay_token: + type: string + pf: + type: string + pf_key: + type: string + province: + type: string + ret: + type: string + unionid: + type: string + type: object + md.WeChatMiniApp: + properties: + avatar: + type: string + code: + type: string + nickname: + type: string + open_id: + type: string + unionid: + type: string + type: object + model.LogisticCompany: + properties: + code: + type: string + name: + type: string + type: object + model.PrivilegeOpenCardOrd: + properties: + address: + type: string + after_sale_id: + type: integer + card_key: + type: string + card_num: + type: string + card_type: + type: integer + cost_price: + type: string + create_time: + type: string + date_type: + type: integer + given_data: + type: string + logistic_company: + type: string + logistic_num: + type: string + ord_id: + type: integer + pay_channel: + type: integer + pay_time: + type: string + phone: + type: string + receiver: + type: string + settle_at: + type: integer + state: + type: integer + uid: + type: integer + update_time: + type: string + type: object + model.UserAddress: + properties: + city_id: + type: string + city_name: + type: string + county_id: + type: string + county_name: + type: string + detail: + type: string + id: + type: integer + phone: + type: string + post_code: + type: string + province_id: + type: string + province_name: + type: string + receiver: + type: string + tag: + type: string + uid: + type: integer + type: object +host: localhost:5000 +info: + contact: + name: sherlockwhite + description: 移动端接口 + termsOfService: 智莺生活后端组 + title: 智莺生活移动端接口 + version: "1.0" +paths: + /api/v1/acq/fix: + get: + consumes: + - application/json + description: 拉新活动--fix + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 拉新活动--fix + tags: + - 拉新活动 + /api/v1/acquisition/home: + post: + consumes: + - application/json + description: 本期榜单/上期榜单/我的邀请人数和奖励/任务列表 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 拉新--首页数据 + tags: + - 拉新活动 + /api/v1/acquisition/qrcode: + get: + consumes: + - application/json + description: 二维码 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 拉新--邀请二维码 + tags: + - 拉新活动 + /api/v1/acquisition/reward/detail: + post: + consumes: + - application/json + description: 拉新活动--我的奖励明细 + parameters: + - description: 1为以发放,2为待发放,3为失效 + in: body + name: state + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 拉新活动--我的奖励明细 + tags: + - 拉新活动 + /api/v1/acquisition/reward_receive: + post: + consumes: + - application/json + description: 拉新活动--领取奖励 + parameters: + - description: 任务ID + in: body + name: job_id + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 拉新活动--领取奖励 + tags: + - 拉新活动 + /api/v1/credit/card/config: + get: + consumes: + - application/json + description: 获取信用卡配置 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 获取信用卡配置 + tags: + - 信用卡 + /api/v1/duomai/mall/detail: + post: + consumes: + - application/json + description: 多麦商城--商城详情 + parameters: + - description: 商城id + in: body + name: brand_id + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城--商城详情 + tags: + - 多麦商城 + /api/v1/duomai/mall/home: + get: + consumes: + - application/json + description: 多麦商城--首页数据 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城--首页数据 + tags: + - 多麦商城 + /api/v1/duomai/mall/search: + post: + consumes: + - application/json + description: 多麦商城--搜索 + parameters: + - description: 搜索关键词 + in: body + name: key + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城--搜索 + tags: + - 多麦商城 + /api/v1/duomai/mall/update: + get: + consumes: + - application/json + description: 多麦商城--更新数据 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城--更新数据 + tags: + - 多麦商城 + /api/v1/logistic/query: + post: + consumes: + - application/json + parameters: + - description: logisticQueryReq + in: body + name: req + required: true + schema: + $ref: '#/definitions/hdl.logisticQueryReq' + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + "400": + description: '{"code":400000,"data":[],"msg":"该快递公司不支持查询"}' + schema: + type: string + summary: 快递100物流查询 + tags: + - 物流 + /api/v1/mod/pub.flutter.duomai.mall.detail.page: + get: + consumes: + - application/json + description: 多麦商城详情页样式 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城详情页样式 + tags: + - 多麦商城 + /api/v1/mod/pub.flutter.duomai.mall.home.page: + get: + consumes: + - application/json + description: 多麦商城首页样式 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城首页样式 + tags: + - 多麦商城 + /api/v1/mod/pub.flutter.duomai.mall.search.page: + get: + consumes: + - application/json + description: 多麦商城搜索页样式 + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 多麦商城搜索页样式 + tags: + - 多麦商城 + /api/v1/privilege/card/activation: + post: + consumes: + - application/json + description: 权益卡激活 + parameters: + - description: json + in: body + name: body + required: true + schema: + $ref: '#/definitions/hdl.privilegeOpenCardCheckReq' + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 权益卡激活 + tags: + - 权益卡 + /api/v1/privilege/open_card/check: + get: + consumes: + - application/json + parameters: + - description: json + in: body + name: body + required: true + schema: + $ref: '#/definitions/hdl.privilegeOpenCardCheckReq' + produces: + - application/json + responses: + "200": + description: 0:不存在 1:已经被使用 2:可用 + schema: + type: string + summary: 权益卡卡号卡密检测 + tags: + - 权益卡 + /api/v1/privilege/open_card/order_query: + get: + produces: + - application/json + responses: + "200": + description: ok + schema: + $ref: '#/definitions/hdl.PrivilegeOpenCardOrdQueryPageResp' + "400": + description: '{"code":400000,"data":[],"msg":"验证码错误"}' + schema: + type: string + summary: 权益卡开卡订单查询页面 + tags: + - 权益卡 + post: + parameters: + - description: json + in: body + name: body + required: true + schema: + $ref: '#/definitions/hdl.PrivilegeOpenCardOrdQueryReq' + produces: + - application/json + responses: + "200": + description: ok + schema: + $ref: '#/definitions/model.PrivilegeOpenCardOrd' + "400": + description: '{"code":400000,"data":[],"msg":"验证码错误"}' + schema: + type: string + summary: 权益卡开卡订单查询 + tags: + - 权益卡 + /api/v1/privilege/open_card/order_suc: + get: + produces: + - application/json + responses: + "200": + description: ok + schema: + $ref: '#/definitions/model.PrivilegeOpenCardOrd' + summary: 订单支付成功页面 + tags: + - 权益卡 + /api/v1/privilege/open_card/pay_page: + get: + produces: + - application/json + responses: + "200": + description: ok + schema: + $ref: '#/definitions/hdl.privilegeOpenCardPayPageResp' + "400": + description: '{"code":400000,"data":[],"msg":"权益卡配置缺失"}' + schema: + type: string + summary: 权益卡开卡支付页面 + tags: + - 权益卡 + /api/v1/shake_ticket/:goods_id/:type: + get: + consumes: + - application/json + description: 收藏/领券买/分享赚 + parameters: + - description: 商品id + in: path + name: goods_id + required: true + type: string + - description: 类型(0取消收藏,1收藏,2点击 领券买,3点击分享赚) + in: path + name: type + required: true + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + summary: 抖券商品收藏/领券买/分享赚 + tags: + - 抖券 + /api/v1/shake_ticket_list: + get: + consumes: + - application/json + description: 定向计划/高佣专场/精选低价包邮/偏远地区包邮/抖货商品/各大榜单商品/今日值得买 + parameters: + - description: 页码 + in: query + name: page + required: true + type: string + - description: 页数 + in: query + name: page_size + required: true + type: string + - description: 分类id(3定向计划/4高佣专场/5精选低价包邮/6偏远地区包邮/7抖货商品/8各大榜单商品/9今日值得买) + in: query + name: category_id + required: true + type: string + - description: 渠道 + in: query + name: pvd + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/md.IndexRecommendList' + summary: 抖券商品推荐列表 + tags: + - 抖券 + /api/v1/sign/fast/in: + post: + parameters: + - description: json + in: body + name: body + required: true + schema: + $ref: '#/definitions/md.FastLoginRequestBody' + produces: + - application/json + responses: + "200": + description: ok + schema: + $ref: '#/definitions/md.LoginResponse' + "400": + description: '{"code":400001,"data":[],"msg":"请求参数错误"}' + schema: + type: string + summary: 用户手机快速登录 + tags: + - 登录、注册 + /api/v1/sub_region_list: + get: + parameters: + - description: 上级地区类型:root(查询省级列表)、province(省级ID下的城市)、city(市级id下的区域) + in: query + name: parent + required: true + type: string + - description: 上级地区id + in: query + name: id + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + items: + $ref: '#/definitions/hdl.resultItem' + type: array + "400": + description: '{"code":400001,"data":[],"msg":"请求参数错误"}' + schema: + type: string + summary: 获取区域id下的区域 + tags: + - 地址 + /api/v1/user/address/:id: + get: + parameters: + - description: 地址id + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + $ref: '#/definitions/model.UserAddress' + "400": + description: '{"code":400000,"data":[],"msg":"地址不存在"}' + schema: + type: string + summary: 用户地址详情 + tags: + - 地址 + /api/v1/user/address/delete/:id: + post: + consumes: + - application/json + parameters: + - description: ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + "400": + description: '{"code":400000,"data":[],"msg":"地址不存在"}' + schema: + type: string + summary: 用户地址删除 + tags: + - 地址 + /api/v1/user/address/update: + post: + consumes: + - application/json + parameters: + - description: json参数,Id不传为新增 + in: body + name: '""' + required: true + schema: + $ref: '#/definitions/hdl.updateAddressReq' + produces: + - application/json + responses: + "200": + description: ok + schema: + type: string + "400": + description: '{"code":400000,"data":[],"msg":"地址不存在"}' + schema: + type: string + summary: 用户地址新增、编辑 + tags: + - 地址 + /api/v1/user/addresses: + get: + produces: + - application/json + responses: + "200": + description: ok + schema: + items: + $ref: '#/definitions/model.UserAddress' + type: array + "500": + description: '{"code":500000,"data":[],"msg":"数据库操作失败"}' + schema: + type: string + summary: 用户地址列表 + tags: + - 地址 +securityDefinitions: + MasterID: + in: header + name: MasterID + type: apiKey +swagger: "2.0" diff --git a/etc/db_tpl/config b/etc/db_tpl/config new file mode 100644 index 0000000..34c75ee --- /dev/null +++ b/etc/db_tpl/config @@ -0,0 +1,7 @@ +lang=go +genJson=1 +prefix=cos_ +ignoreColumnsJSON= +created= +updated= +deleted= \ No newline at end of file diff --git a/etc/db_tpl/struct.go.tpl b/etc/db_tpl/struct.go.tpl new file mode 100644 index 0000000..74b2896 --- /dev/null +++ b/etc/db_tpl/struct.go.tpl @@ -0,0 +1,17 @@ +package {{.Models}} + +{{$ilen := len .Imports}} +{{if gt $ilen 0}} +import ( + {{range .Imports}}"{{.}}"{{end}} +) +{{end}} + +{{range .Tables}} +type {{Mapper .Name}} struct { +{{$table := .}} +{{range .ColumnsSeq}}{{$col := $table.GetColumn .}} {{Mapper $col.Name}} {{Type $col}} {{Tag $table $col}} +{{end}} +} +{{end}} + diff --git a/etc/task.yml b/etc/task.yml new file mode 100644 index 0000000..6642724 --- /dev/null +++ b/etc/task.yml @@ -0,0 +1,37 @@ +# debug release test +debug: true +prd: false +local: true +# 缓存 +redis_addr: '120.24.28.6:32572' + +app_comm: + url: http://127.0.0.1:5003 + + +admin: + api_aes_key: e{&[^Ft(.~g]1eR-]VO + api_aes_iv: ZV`7<5X]/2brS@sz +# 数据库 +db: + host: '119.23.182.117:3306' + name: 'zyos_website' + user: 'root' + psw: 'Fnuo123com@' + show_log: true + max_lifetime: 30 + max_open_conns: 100 + max_idle_conns: 100 + path: 'tmp/task_sql_%v.log' + +# 日志 +log: + level: 'debug' # 普通日志级别 #debug, info, warn, fatal, panic + is_stdout: true + time_format: 'standard' # sec, second, milli, nano, standard, iso + encoding: 'console' + is_file_out: true + file_dir: './tmp/' + file_max_size: 256 + file_max_age: 1 + file_name: 'task.log' diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b294c07 --- /dev/null +++ b/go.mod @@ -0,0 +1,54 @@ +module applet + +go 1.15 + +require ( + github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 + github.com/boombuler/barcode v1.0.1 + github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/forgoer/openssl v0.0.0-20201023062029-c3112b0c8700 + github.com/gin-contrib/sessions v0.0.3 + github.com/gin-gonic/gin v1.6.3 + github.com/go-openapi/spec v0.20.3 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.13.0 + github.com/go-playground/universal-translator v0.17.0 + github.com/go-playground/validator/v10 v10.4.2 + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-sql-driver/mysql v1.6.0 + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/gomodule/redigo v2.0.0+incompatible + github.com/gookit/color v1.3.8 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/iGoogle-ink/gopay v1.5.36 + github.com/iGoogle-ink/gotil v1.0.20 + github.com/json-iterator/go v1.1.10 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/makiuchi-d/gozxing v0.0.0-20210324052758-57132e828831 + github.com/pkg/errors v0.9.1 + github.com/qiniu/api.v7/v7 v7.8.2 + github.com/robfig/cron/v3 v3.0.1 + github.com/sony/sonyflake v1.0.0 + github.com/swaggo/swag v1.7.0 + github.com/syyongx/php2go v0.9.4 + github.com/tidwall/gjson v1.7.4 + github.com/ugorji/go v1.2.5 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.16.0 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect + golang.org/x/text v0.3.6 // indirect + golang.org/x/tools v0.1.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 + gopkg.in/yaml.v2 v2.4.0 + honnef.co/go/tools v0.0.1-2020.1.4 // indirect + xorm.io/builder v0.3.9 // indirect + xorm.io/xorm v1.0.7 +) diff --git a/k8s/mall-task-prd.yaml b/k8s/mall-task-prd.yaml new file mode 100644 index 0000000..1e592eb --- /dev/null +++ b/k8s/mall-task-prd.yaml @@ -0,0 +1,56 @@ +kind: Deployment +apiVersion: apps/v1 +metadata: + name: mall-task + namespace: zhios + labels: + app: mall-task + annotations: + kubesphere.io/creator: wuhanqin + kubesphere.io/description: 自营商城go定时任务 +spec: + replicas: 1 + selector: + matchLabels: + app: mall-task + template: + metadata: + labels: + app: mall-task + spec: + volumes: + - name: host-time + hostPath: + path: /etc/localtime + type: '' + - name: mall-task-cfg1 + configMap: + name: zhios-mall-task + items: + - key: task.yml + path: task.yml + defaultMode: 420 + containers: + - name: container-mall-task + image: 'registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-mall-task:0.3' + resources: + limits: + cpu: '1' + memory: 1000Mi + requests: + cpu: 200m + memory: 1000Mi + volumeMounts: + - name: host-time + readOnly: true + mountPath: /etc/localtime + - name: mall-task-cfg1 + readOnly: true + mountPath: /var/zyos/task.yml + subPath: task.yml + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullPolicy: Always + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst diff --git a/k8s/mall-task.yaml b/k8s/mall-task.yaml new file mode 100644 index 0000000..534392a --- /dev/null +++ b/k8s/mall-task.yaml @@ -0,0 +1,56 @@ +kind: Deployment +apiVersion: apps/v1 +metadata: + name: mall-task + namespace: dev + labels: + app: mall-task + annotations: + kubesphere.io/creator: wuhanqin + kubesphere.io/description: 自营商城go定时任务 +spec: + replicas: 1 + selector: + matchLabels: + app: mall-task + template: + metadata: + labels: + app: mall-task + spec: + volumes: + - name: host-time + hostPath: + path: /etc/localtime + type: '' + - name: mall-task-cfg1 + configMap: + name: mall-task-cfg + items: + - key: task.yml + path: task.yml + defaultMode: 420 + containers: + - name: container-mall-task + image: 'registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-mall-task:0.1' + resources: + limits: + cpu: '1' + memory: 1000Mi + requests: + cpu: 200m + memory: 1000Mi + volumeMounts: + - name: host-time + readOnly: true + mountPath: /etc/localtime + - name: mall-task-cfg1 + readOnly: true + mountPath: /var/zyos/task.yml + subPath: task.yml + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullPolicy: Always + restartPolicy: Always + terminationGracePeriodSeconds: 30 + dnsPolicy: ClusterFirst diff --git a/k8s/zyos-mall-deployment_prd.yaml b/k8s/zyos-mall-deployment_prd.yaml new file mode 100644 index 0000000..e2dc9da --- /dev/null +++ b/k8s/zyos-mall-deployment_prd.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: zhios + name: zhios-mall + labels: + app: zhios-mall +spec: + replicas: 1 + template: + metadata: + name: zhios-mall + labels: + app: zhios-mall + spec: + containers: + - name: zhios-mall-container + image: registry-vpc.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-mall:0.1 + ports: + - containerPort: 5002 + name: 5002tcp + protocol: TCP + resources: + limits: + cpu: "1" + memory: 256Mi + requests: + cpu: 200m + memory: 128Mi + imagePullPolicy: IfNotPresent + restartPolicy: Always + volumes: + - name: host-time + hostPath: + path: /etc/localtime + type: '' + - name: mall-cfg + configMap: + name: zhios-mall-cfg + defaultMode: 420 + selector: + matchLabels: + app: zhios-mall + + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 25% + maxSurge: 25% diff --git a/main.go b/main.go new file mode 100644 index 0000000..0974be1 --- /dev/null +++ b/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "applet/app/cfg" + "applet/app/db" + "applet/app/router" + "applet/app/utils" +) + +//系统初始化 +func init() { + cfg.InitCfg() //配置初始化 + cfg.InitLog() //日志初始化 + cfg.InitCache() //缓存初始化 + if cfg.Debug { //判断是否是debug + if err := db.InitDB(cfg.DB); err != nil { //主数据库初始化 + panic(err) + } + channel := make(chan int, 0) //开辟管道,缓冲为 + go db.InitDBs(channel) + <-channel + } + fmt.Println("init success") + +} + +// @title 智莺生活移动端接口 +// @version 1.0 +// @description 移动端接口 +// @termsOfService 智莺生活后端组 +// @contact.name sherlockwhite +// @host localhost:5000 +// @securityDefinitions.apikey MasterID +// @in header +// @name MasterID +// @BasePath / +func main() { + // 启动获取所有品牌 + //go taoke.GetAllBrand() + r := router.Init() //创建路由 + // arkid.Init() + srv := &http.Server{ //设置http服务参数 + Addr: cfg.SrvAddr, //指定ip和端口 + Handler: r, //指定路由 + } + // 读取默认站长的推广位 并写进redis + // master, err := db.UserProfileFindByID(,"1") + + // if err != nil { + // panic(err) + // } + if cfg.CurlDebug { + utils.CurlDebug = true + } + // + // if has := cache.SetJson(svc.SysCfgGet(nil, "app_name")+"_default_pid_user", master, 0); has != true { + // panic(errors.New("设置默认pid缓存失败")) + // } + // Initializing the server in a goroutine so that it won't block the graceful shutdown handling below + go func() { //协程启动监听http服务 + fmt.Println("Listening and serving HTTP on " + cfg.SrvAddr) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }() + // graceful shutdown + //退出go守护进程 + quit := make(chan os.Signal) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + log.Println("Shutting down server...") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil { + log.Fatal("Server forced to shutdown:", err) + } + log.Println("Server exiting") + +} diff --git a/static/html/LandingPage.html b/static/html/LandingPage.html new file mode 100644 index 0000000..313f321 --- /dev/null +++ b/static/html/LandingPage.html @@ -0,0 +1,179 @@ + + + + + + Landing Page + + + + + + + +
+
+ PullNew +
+ 恭喜您获得 +
+
+ 现金红包 +
+ ImgBtn + +
+
+ + + + \ No newline at end of file diff --git a/static/html/article.html b/static/html/article.html new file mode 100644 index 0000000..ad76ce5 --- /dev/null +++ b/static/html/article.html @@ -0,0 +1,95 @@ + + + + + + + + {{.title}} + + + + + + +
+
+

{{.title}}

+
+
+

{{.author}}

+ {{.time}} +
+ +
+ {{.studyCount}}人已学习 +
+
+
+
+
+ + + + diff --git a/static/html/kz_goods_share.html b/static/html/kz_goods_share.html new file mode 100644 index 0000000..348bddf --- /dev/null +++ b/static/html/kz_goods_share.html @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + + + 好货 + + + + 下载APP + + + + + +
+ +
+ + + +
+ + + + + + + +
+ +

{{.goodTitle}}

+ +
+ +

+ + + + 券后价 + 价格 + + + + + ¥{{.goodPrice}}¥{{.goodCostPrice}} + +

+ + + +
+ + + +

{{.goodCouponPrice}}元券

+ +
+ +
+ +
+ +
+ + + +
{{.tkl}}
+ + + +
+ + 一键复制 + +
+ + + +
+ + 长按复制上方口令,打开手机【】购买 + +
+ +
\ No newline at end of file diff --git a/test/aces_test.go b/test/aces_test.go new file mode 100644 index 0000000..bce9cd1 --- /dev/null +++ b/test/aces_test.go @@ -0,0 +1,175 @@ +package test + +import ( + "applet/app/db" + "applet/app/md" + "applet/app/svc" + "applet/app/utils" + "encoding/base64" + "encoding/json" + "fmt" + "testing" +) + +func TestAesCrypt_Encrypt(t *testing.T) { + var aesCrypt = utils.AesCrypt{ + Key: []byte("e{&[^Ft(.~g]1eR-]VO"), + Iv: []byte("ZV`7<5X]/2brS@sz"), + } + + var text = `{"uid":"82","applyOrder":"821607392542143106","db":{"db_host":"119.23.182.117","db_port":"3306","db_name":"fnuoos_template","db_username":"root","db_password":"Fnuo123com@"}}` + result, err := aesCrypt.Encrypt([]byte(text)) + if err != nil { + fmt.Println(err) + return + } + + pass64 := base64.StdEncoding.EncodeToString(result) + fmt.Println(pass64) +} + +func TestAesCrypt_Decrypt(t *testing.T) { + var aesCrypt = utils.AesCrypt{ + Key: []byte("e{&[^Ft(.~g]1eR-]VO"), + Iv: []byte("ZV`7<5X]/2brS@sz"), + } + + pass64 := "JD0RXX1YbZPWKeNiVKsq0jQ1Bfnbln3fIMcmJkovU5gUCf329y9ZdqECWe4OKpoOk25/hPNaBH9VwellhIQhpw==" + bytesPass, err := base64.StdEncoding.DecodeString(pass64) + if err != nil { + fmt.Println(err) + return + } + + plainText, err := aesCrypt.Decrypt(bytesPass) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(string(plainText)) +} + +func Test_Vi(t *testing.T) { + fmt.Println("123") + fmt.Println([]byte("ZV`7<5X]/2brS@sz")) +} + +func Test_CombineData(t *testing.T) { + data := + `{ + "Uid": 21699, + "Lv": 0, + "NewLv": 0, + "LevelWeight": -1, + "Profit": 7.13, + "SubsidyFee": 0, + "ProfitList": [ + { + "cid": "0", + "val": 7.13 + }, + { + "cid": "1", + "val": 10 + }, + { + "cid": "19", + "val": 120 + }, + { + "cid": "20", + "val": 0 + }, + { + "cid": "21", + "val": 0 + } + ], + "SubsidyFeeList": null, + "OwnbuyReturnType": 0, + "Diff": 0, + "ParentUser": { + "Uid": 553, + "Lv": 8, + "NewLv": 0, + "LevelWeight": 2, + "Profit": 0, + "SubsidyFee": 0, + "ProfitList": [ + { + "cid": "0", + "val": 0 + } + ], + "SubsidyFeeList": null, + "OwnbuyReturnType": 0, + "Diff": 1, + "ParentUser": { + "Uid": 21699, + "Lv": 0, + "NewLv": 0, + "LevelWeight": -1, + "Profit": 7.13, + "SubsidyFee": 0, + "ProfitList": [ + { + "cid": "0", + "val": 7.13 + }, + { + "cid": "1", + "val": 10 + }, + { + "cid": "19", + "val": 120 + }, + { + "cid": "20", + "val": 0 + }, + { + "cid": "21", + "val": 0 + } + ], + "SubsidyFeeList": null, + "OwnbuyReturnType": 0, + "Diff": 0, + "ParentUser": { + "Uid": 553, + "Lv": 8, + "NewLv": 0, + "LevelWeight": 2, + "Profit": 0, + "SubsidyFee": 0, + "ProfitList": [ + { + "cid": "0", + "val": 0 + } + ], + "SubsidyFeeList": null, + "OwnbuyReturnType": 0, + "Diff": 1, + "ParentUser": null + } + } + } + }` + bytes := []byte(data) + var lvUser md.LvUser + if err := json.Unmarshal(bytes, &lvUser); err != nil { + fmt.Println(err) + return + } + dataSlice := svc.CombineVirtualCoinRelateData(&lvUser, 249534162504132595, "mall_goods", 0) + for _, item := range dataSlice { + fmt.Printf("%#v\n", item) + } + err := db.DbInsertBatch(db.DBs["123456"], dataSlice) + if err != nil { + fmt.Println(err) + } +} diff --git a/test/toke_test.go b/test/toke_test.go new file mode 100644 index 0000000..f37d853 --- /dev/null +++ b/test/toke_test.go @@ -0,0 +1,35 @@ +package test + +import ( + "applet/app/db" + "applet/app/lib/taoke" + "applet/app/svc" + "fmt" + "testing" +) + +func TestBrandList(t *testing.T) { + tkBrandStruct, err := taoke.BrandList("1", "20") + if err != nil { + t.Error(err) + } + for _, brandStruct := range *tkBrandStruct { + fmt.Println(brandStruct) + } + +} + +func TestSuperCategory(t *testing.T) { + category, err := taoke.SuperCategory() + if err != nil { + t.Error(err) + } + for _, brandStruct := range *category { + fmt.Println(brandStruct) + } +} + +func TestAddBrandDB(t *testing.T) { + engine := db.DBs["123456"] + svc.AddBrandDB(engine) +} diff --git a/test/zhimeng_api.go b/test/zhimeng_api.go new file mode 100644 index 0000000..6ef1670 --- /dev/null +++ b/test/zhimeng_api.go @@ -0,0 +1,156 @@ +package test + +import ( + "fmt" + "github.com/gocolly/colly" + "github.com/gocolly/colly/extensions" + "github.com/tidwall/gjson" + "net/http" + "regexp" + "strings" +) + +/* +目前可用接口 +[商品查询]https://www.showdoc.com.cn/59349170678610?page_id=339616554551473 +[商品详情]https://www.showdoc.com.cn/59349170678610?page_id=339687047645094 + +*/ + +// Response is SDK Response +type Response struct { + Msg string `json:"msg"` + Success int `json:"success"` + Data interface{} `json:"data"` +} + +func main() { + // // JD + // postData := map[string]string{"keyword": "联想", "p": "1", "size": "10"} + // fmt.Println(postData["time"]) + // res, _ := zhimeng.Send("jd", "getgoods", postData) + // fmt.Println(string(res)) + // p := Response{} + // json.Unmarshal(res, &p) + // fmt.Println(p) + // // VIP + // postData = map[string]string{"keyword": "联想", "p": "1", "size": "10", "order": "0"} + // fmt.Println(postData["time"]) + // res, _ = zhimeng.Send("wph", "seach_goods", postData) + // fmt.Println(string(res)) + // p = Response{} + // json.Unmarshal(res, &p) + // fmt.Println(p) + // // PDD + // postData = map[string]string{"keyword": "联想", "p": "1", "size": "10", "sort": "goods_price asc"} + // res, _ = zhimeng.Send("pdd", "getgoods", postData) + // fmt.Println(string(res)) + // p = Response{} + // json.Unmarshal(res, &p) + // fmt.Println(p) + for i := 0; i < 1000; i++ { + fmt.Println(i) + scrapPDD() + } +} + +func scrapJD() { + c := colly.NewCollector(func(collector *colly.Collector) { + extensions.RandomUserAgent(collector) + }) + c.OnResponse(func(r *colly.Response) { + re, _ := regexp.Compile(`[(]//[^\s]*[)]`) + body := r.Body + fmt.Println(string(body)) + urls := re.FindAllString(string(body), -1) + fmt.Println(urls) + for _, url := range urls { + url = strip(url, "()") + url = "https:" + url + fmt.Println(url) + } + }) + c.Visit("https://wqsitem.jd.com/detail/100008309360_d100008309360_normal.html") +} + +func scrapPDD() { + var cookies = []*http.Cookie{} + var mapcookies = make(map[string]string) + url := fmt.Sprintf("https://mobile.yangkeduo.com/goods.html?goods_id=%s", "156632692649") + cs := "api_uid=CiHUKl9DZKpL6QBVK4qWAg==; _nano_fp=Xpdbl0PyX5Pxn0TynT_DTGXbst0kz5cjzGAQDnBR; ua=Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F84.0.4147.135%20Safari%2F537.36; webp=1; quick_entrance_click_record=20200824%2C1; PDDAccessToken=XRC6FNX7FRBL6AJRMRBRN4CDG2PZXO3YJZYHFUA4O2PLDAWVYXHA1125821; pdd_user_id=9622705741400; pdd_user_uin=F27EAZ4V5S7EGEVMCJI2P7RFLE_GEXDA; chat_config={'host_whitelist':['.yangkeduo.com','.pinduoduo.com','.10010.com/queen/tencent/pinduoduo-fill.html','.ha.10086.cn/pay/card-sale!toforward.action','wap.ha.10086.cn','m.10010.com']}; pdd_vds=gaLMNqmfGfyYEpyYiZGWopaCicNHbXGWtDNcOZnWLqiDNfLHOXnZaqtCLDiX" + csList := strings.Split(cs, ";") + for _, c := range csList { + s := strings.Trim(c, " ") + sList := strings.SplitN(s, "=", 2) + + mapcookies[sList[len(sList)-len(sList)]] = sList[(len(sList) - len(sList) + 1)] + + } + fmt.Println(mapcookies) + for key, value := range mapcookies { + if key == "ua" { + continue + } + cookies = append(cookies, &http.Cookie{Name: key, Value: value}) + } + c := colly.NewCollector( + colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36"), + ) + + c.OnResponse(func(r *colly.Response) { + re, _ := regexp.Compile(`window.rawData=.*}`) + body := r.Body + fmt.Println(string(body)) + result := re.FindString(string(body)) + // fmt.Println(result) + result = strings.SplitN(result, "=", 2)[1] + // fmt.Println(result) + value := gjson.Get(result, "store.initDataObj.goods.detailGallery") + // fmt.Println(value) + list := value.Array() + imageList := []string{} + for _, v := range list { + nv := gjson.Get(v.String(), "url") + imageList = append(imageList, nv.String()) + } + fmt.Println(imageList) + ck := c.Cookies("https://mobile.yangkeduo.com") + fmt.Println(ck) + cookies = ck + }) + + c.SetCookies("https://mobile.yangkeduo.com", cookies) + + c.Visit(url) +} + +func strip(ss string, charss string) string { + s, chars := []rune(ss), []rune(charss) + length := len(s) + max := len(s) - 1 + l, r := true, true //标记当左端或者右端找到正常字符后就停止继续寻找 + start, end := 0, max + tmpEnd := 0 + charset := make(map[rune]bool) //创建字符集,也就是唯一的字符,方便后面判断是否存在 + for i := 0; i < len(chars); i++ { + charset[chars[i]] = true + } + for i := 0; i < length; i++ { + if _, exist := charset[s[i]]; l && !exist { + start = i + l = false + } + tmpEnd = max - i + if _, exist := charset[s[tmpEnd]]; r && !exist { + end = tmpEnd + r = false + } + if !l && !r { + break + } + } + if l && r { // 如果左端和右端都没找到正常字符,那么表示该字符串没有正常字符 + return "" + } + return string(s[start : end+1]) +} diff --git a/wap.conf b/wap.conf new file mode 100644 index 0000000..8f0d455 --- /dev/null +++ b/wap.conf @@ -0,0 +1,35 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + #将DNS指向kubernetes集群内的DNS + resolver kube-dns.kube-system.svc.cluster.local valid=30s; + + set $oss_endpoint_service zyos-oss.dev.svc.cluster.local:5000; + set $endpoint_service zyos-app.dev.svc.cluster.local:5000; + + location /static { + root /usr/share/nginx/html; + index index.html index.htm; + } + location /api/qiniu { + proxy_pass http://$oss_endpoint_service; + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + break; + } + #proxy server + location /api { + proxy_pass http://$endpoint_service; + + proxy_http_version 1.1; + + proxy_set_header Connection ""; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + break; + } +} \ No newline at end of file diff --git a/zyos-mall-deployment.yaml b/zyos-mall-deployment.yaml new file mode 100644 index 0000000..03e4c77 --- /dev/null +++ b/zyos-mall-deployment.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: dev + name: zyos-mall + labels: + app: zyos-mall +spec: + replicas: 1 + template: + metadata: + name: zyos-mall + labels: + app: zyos-mall + spec: + containers: + - name: zyos-mall-container + image: registry-vpc.cn-shenzhen.aliyuncs.com/fnuoos-prd/zyos-mall:0.1 + ports: + - containerPort: 5002 + name: 5002tcp + protocol: TCP + resources: + limits: + cpu: "1" + memory: 256Mi + requests: + cpu: 200m + memory: 128Mi + imagePullPolicy: IfNotPresent + restartPolicy: Always + volumes: + - name: host-time + hostPath: + path: /etc/localtime + type: '' + - name: mall-cfg + configMap: + name: zyos-mall-cfg + defaultMode: 420 + selector: + matchLabels: + app: zyos-mall + + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 25% + maxSurge: 25%