From 07e86deae319ef521bc9356d013347d0f551dcd7 Mon Sep 17 00:00:00 2001 From: huangjiajun <582604932@qq.com> Date: Mon, 1 Jul 2024 17:18:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/cfg/cfg_app.go | 5 +- app/cfg/init_cfg.go | 6 +- app/db/db_sys_mod_format_img.go | 63 +++++ app/db/dbs.go | 104 ++++++++ app/db/dbs_map.go | 194 +++++++++++++++ app/db/model/db_mapping.go | 18 ++ app/db/model/user_app_list.go | 8 + app/db/station/sys_cfg.go | 7 + app/hdl/zhimeng_platform/hdl_withdrawal.go | 41 ++++ app/lib/mob/api.go | 270 +++++++++++++++++++++ app/lib/sms/sms.go | 84 +++++++ app/lib/zhimeng/api.go | 66 +++++ app/lib/zhimeng/sdk.go | 98 ++++++++ app/lib/zhimeng/sms_send.go | 70 ++++++ app/md/md_login.go | 8 + app/mw/mw_auth_jwt.go | 11 +- app/mw/mw_request_cache.go | 4 +- app/router/router.go | 1 + app/svc/platform/svc_withdrawal.go | 51 +++- go.mod | 2 +- main.go | 5 +- 21 files changed, 1096 insertions(+), 20 deletions(-) create mode 100644 app/db/db_sys_mod_format_img.go create mode 100644 app/db/dbs.go create mode 100644 app/db/dbs_map.go create mode 100644 app/db/model/db_mapping.go create mode 100644 app/db/model/user_app_list.go create mode 100644 app/db/station/sys_cfg.go create mode 100644 app/lib/mob/api.go create mode 100644 app/lib/sms/sms.go create mode 100644 app/lib/zhimeng/api.go create mode 100644 app/lib/zhimeng/sdk.go create mode 100644 app/lib/zhimeng/sms_send.go diff --git a/app/cfg/cfg_app.go b/app/cfg/cfg_app.go index 41ea372..11a3207 100644 --- a/app/cfg/cfg_app.go +++ b/app/cfg/cfg_app.go @@ -13,9 +13,10 @@ type Config struct { ZhimengDB DBCfg `yaml:"zhimeng_db"` Log LogCfg `yaml:"log"` ES ESCfg `yaml:"es"` + Local bool } -//数据库配置结构体 +// 数据库配置结构体 type DBCfg struct { Host string `yaml:"host"` //ip及端口 Name string `yaml:"name"` //库名 @@ -28,7 +29,7 @@ type DBCfg struct { Path string `yaml:"path"` //日志文件存放路径 } -//日志配置结构体 +// 日志配置结构体 type LogCfg struct { AppName string `yaml:"app_name" ` Level string `yaml:"level"` diff --git a/app/cfg/init_cfg.go b/app/cfg/init_cfg.go index 8c2b8a1..e680a25 100644 --- a/app/cfg/init_cfg.go +++ b/app/cfg/init_cfg.go @@ -7,7 +7,7 @@ import ( "gopkg.in/yaml.v2" ) -//配置文件数据,全局变量 +// 配置文件数据,全局变量 var ( Debug bool Prd bool @@ -17,9 +17,10 @@ var ( ZhimengDB *DBCfg Log *LogCfg ES *ESCfg + Local bool ) -//初始化配置文件,将cfg.yml读入到内存 +// 初始化配置文件,将cfg.yml读入到内存 func InitCfg() { //用指定的名称、默认值、使用信息注册一个string类型flag。 path := flag.String("c", "etc/cfg.yml", "config file") @@ -47,4 +48,5 @@ func InitCfg() { Log = &conf.Log RedisAddr = conf.RedisAddr SrvAddr = conf.SrvAddr + Local = conf.Local } 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..e7cb6b0 --- /dev/null +++ b/app/db/db_sys_mod_format_img.go @@ -0,0 +1,63 @@ +package db + +import ( + "applet/app/db/station" + "applet/app/utils/logx" + "github.com/gin-gonic/gin" + "xorm.io/xorm" +) + +// 单条记录获取DB +func SysCfgGet(c *gin.Context, key string) string { + res := SysCfgFind(c, key) + if _, ok := res[key]; !ok { + return "" + } + return res[key] +} + +// 多条记录获取 +func SysCfgFind(c *gin.Context, keys ...string) map[string]string { + eg := DBs[c.GetString("master_id")] + masterId := c.GetString("master_id") + res := map[string]string{} + //TODO::判断keys长度(大于10个直接查数据库) + if len(keys) > 10 { + cfgList, _ := SysCfgGetAll(eg) + if cfgList == nil { + return nil + } + for _, v := range *cfgList { + res[v.Key] = v.Val + } + } else { + for _, key := range keys { + res[key] = SysCfgGetWithDb(eg, masterId, key) + } + } + return res +} +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 +} + +// 单条记录获取DB +func SysCfgGetWithDb(eg *xorm.Engine, masterId string, HKey string) string { + cfg, err := SysCfgGetOne(eg, HKey) + if err != nil || cfg == nil { + _ = logx.Error(err) + return "" + } + return cfg.Val +} 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_map.go b/app/db/dbs_map.go new file mode 100644 index 0000000..4325d61 --- /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 + 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, 22255132).Or("db_master_id=?", 56593133).Find(&m) + //err = Db.Where("deleted_at != ? AND is_dev = '1' AND db_master_id=?", 1, 22255132).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 '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_wap_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 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 masterId + "." + domainBase.V, nil +} 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/user_app_list.go b/app/db/model/user_app_list.go new file mode 100644 index 0000000..58b6cb5 --- /dev/null +++ b/app/db/model/user_app_list.go @@ -0,0 +1,8 @@ +package model + +type UserAppList struct { + Id int `json:"id" xorm:"int(11) NOT NULL "` + Uuid int64 `json:"uuid" xorm:"int(10) NOT NULL "` + AppId int64 `json:"app_id" xorm:"int(10) NOT NULL "` + SmsPlatform string `json:"sms_platform" xorm:"varchar(255) DEFAULT 'mob' "` +} diff --git a/app/db/station/sys_cfg.go b/app/db/station/sys_cfg.go new file mode 100644 index 0000000..22d906b --- /dev/null +++ b/app/db/station/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/hdl/zhimeng_platform/hdl_withdrawal.go b/app/hdl/zhimeng_platform/hdl_withdrawal.go index fdca9b1..d0e1f1a 100644 --- a/app/hdl/zhimeng_platform/hdl_withdrawal.go +++ b/app/hdl/zhimeng_platform/hdl_withdrawal.go @@ -1,8 +1,17 @@ package zhimeng_platform import ( + "applet/app/db" + "applet/app/e" + sms2 "applet/app/lib/sms" "applet/app/svc/platform" + "applet/app/utils" + "encoding/json" + "fmt" "github.com/gin-gonic/gin" + "github.com/syyongx/php2go" + "math/rand" + "time" ) func WithdrawalIncome(c *gin.Context) { @@ -23,3 +32,35 @@ func WithdrawalInvoiceImg(c *gin.Context) { func WithdrawalBindAlipay(c *gin.Context) { platform.WithdrawalBindAlipay(c) } +func Sms(c *gin.Context) { + if c.GetString("is_system") == "" { + e.OutErr(c, 400, e.NewErr(400, "请重新在后台进入聚合联盟")) + return + } + if c.GetString("is_system") != "1" { + e.OutErr(c, 400, e.NewErr(400, "请使用超管账号设置")) + return + } + appName := db.SysCfgGet(c, "sms_push_sign") + captcha := createCaptcha() + content := fmt.Sprintf("【%s】验证码:%s", appName, captcha) + templateCode := "" + marshal, _ := json.Marshal(c.Request.Header) + waykeys := "zhimeng_" + c.ClientIP() + "_" + utils.IntToStr(utils.GetApiVersion(c)) + "_" + c.Request.RequestURI + "_" + string(marshal) + postData := map[string]interface{}{ + "content": content, + "mobile": c.GetString("phone"), + "templateCode": templateCode, + "way": php2go.Base64Encode(waykeys), + } + err := sms2.GetSmsConfig(c, "86", postData) + if err != nil { + e.OutErr(c, 400, err.Error()) + return + } + e.OutSuc(c, "success", nil) + return +} +func createCaptcha() string { + return fmt.Sprintf("%05v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000000)) +} diff --git a/app/lib/mob/api.go b/app/lib/mob/api.go new file mode 100644 index 0000000..efeb16f --- /dev/null +++ b/app/lib/mob/api.go @@ -0,0 +1,270 @@ +package mob + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/lib/sms" + "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 := db.SysCfgGetWithDb(e, k, "third_app_push_set") + if m == "" { + fmt.Printf("masterid:%s 找不到推送配置", k) + continue + } + key := gjson.Get(m, "mobAppKey").String() + secret := gjson.Get(m, "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 := db.SysCfgGetWithDb(e, k, "third_app_push_set") + if m == "" { + fmt.Printf("masterid:%s 找不到推送配置", k) + continue + } + key := gjson.Get(m, "mobAppKey").String() + secret := gjson.Get(m, "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) { + + return &SDK{AppKey: "30dc33054b635", AppSecret: "396e98c293130c9976fb7428b6b434d6"}, 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) + //fmt.Println(args) + // 发送请求 + respBody, err := utils.CurlPost(url, args, nil) + if err != nil { + fmt.Println(err) + return false, logx.Warn(err) + } + fmt.Println("=======================mob") + fmt.Println("mob", string(respBody)) + code := gjson.GetBytes(respBody, "status").Int() + if code == 468 { + return false, errors.New("验证码错误") + } + if code != 200 { + utils.FilePutContents("sms", string(respBody)) + return false, errors.New("验证码错误~") + } + 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) +} + +func Check(c *gin.Context, phone, zone, validCode string, ok1 bool) (bool, error) { + smsPlatform := sms.GetSmsPlatform(c) + + if smsPlatform == "mob" { + mob1, errr := GetMobSDK(c.GetString("master_id")) + if errr != nil { + return false, e.NewErr(400, "mob配置错误") + } + send := map[string]interface{}{ + "phone": phone, + "zone": zone, + "code": validCode, + } + if zone == "" { + send["zone"] = "86" + } + c.Set("not_deduction_doing", "1") + ok, err := mob1.MobSMS(c, send) + fmt.Println(ok) + if err != nil { + fmt.Println(err) + return false, e.NewErr(400, "验证码校验错误") + } + + return ok, nil + } + return ok1, nil +} diff --git a/app/lib/sms/sms.go b/app/lib/sms/sms.go new file mode 100644 index 0000000..9b6e819 --- /dev/null +++ b/app/lib/sms/sms.go @@ -0,0 +1,84 @@ +package sms + +import ( + "applet/app/db" + "applet/app/db/model" + "applet/app/lib/zhimeng" + "applet/app/utils" + "applet/app/utils/cache" + "applet/app/utils/logx" + "code.fnuoos.com/go_rely_warehouse/zyos_go_third_party_api.git/sms" + "fmt" + "github.com/tidwall/gjson" + + "github.com/gin-gonic/gin" +) + +// NewZhimengSMS is 智盟的短信服务 +func NewZhimengSMS(c *gin.Context) *zhimeng.SDK { + sms := new(zhimeng.SDK) + key := db.SysCfgGet(c, "third_zm_sms_key") + secret := db.SysCfgGet(c, "third_zm_sms_secret") + if key == "" || secret == "" { + _ = logx.Warn("短信服务配置错误") + } + sms.Init("send_msg", key, secret) + return sms +} +func GetSmsPlatform(c *gin.Context) string { + var smsPlatform = "ljioe" + key := fmt.Sprintf("%s:sms_platform", c.GetString("master_id")) + smsPlatformTmp, _ := cache.GetString(key) + if smsPlatformTmp == "" { + smsPlatformTmp = GetWebSiteAppSmsPlatform(c.GetString("master_id")) + if smsPlatformTmp != "" { + cache.SetEx(key, smsPlatformTmp, 300) + } + } + if smsPlatformTmp != "" { + smsPlatform = smsPlatformTmp + } + return smsPlatform +} +func GetWebSiteAppSmsPlatform(mid string) string { + obj := new(model.UserAppList) + has, err := db.Db.Where("uuid=? ", mid).Asc("id").Get(obj) + if err != nil || !has { + return "" + } + return utils.AnyToString(obj.SmsPlatform) +} + +func GetTplId(c *gin.Context, zone, types string) string { + // 校验短信验证码 + tplId := "" + if zone != "86" { + tplId = db.SysCfgGet(c, "mob_sms_sdk_international_template_id") + } else { + tplId = db.SysCfgGet(c, "mob_sms_sdk_template_id") + } + if c.GetString("app_type") == "o2o" { + tplId = db.SysCfgGet(c, "biz_mob_sms_sdk_template_id") + } + normal := gjson.Get(tplId, types).String() + if normal == "" { + normal = gjson.Get(tplId, "normal").String() + } + return normal +} +func GetSmsConfig(c *gin.Context, zone string, postData map[string]interface{}) error { + postData["is_mob"] = "1" + postData["type"] = "mob" + postData["sms_type"] = "putong" + zone = "86" + postData["templateCode"] = "14373673" + postData["zone"] = zone + postData["smsmsg_key"] = "30dc33054b635" + postData["uid"] = c.GetString("master_id") + err := sms.SmsSendZy(db.Db, postData) + if err != nil { + return err + } + return nil + +} diff --git a/app/lib/zhimeng/api.go b/app/lib/zhimeng/api.go new file mode 100644 index 0000000..c1aef2d --- /dev/null +++ b/app/lib/zhimeng/api.go @@ -0,0 +1,66 @@ +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) + fmt.Println("====短信==", router, args) + 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/lib/zhimeng/sdk.go b/app/lib/zhimeng/sdk.go new file mode 100644 index 0000000..5a9506b --- /dev/null +++ b/app/lib/zhimeng/sdk.go @@ -0,0 +1,98 @@ +package zhimeng + +import ( + "applet/app/utils/logx" + "encoding/json" + "errors" + "fmt" + + "github.com/tidwall/gjson" +) + +// SDK is zm sdk +type SDK struct { + Action string + operation string + response []byte + SmsKey string + SmsSecret string + data interface{} + err error +} + +// ToInterface is data to Interface +func (sdk *SDK) ToInterface() interface{} { + return sdk.data +} + +// Init is init action +// In some condition, such as send Sms, need pass sms key and secret after 'action' +func (sdk *SDK) Init(action string, keys ...string) { + sdk.Action = action + //if keys[0] == "" || keys[1] == "" { + // sdk.err = errors.New("智盟短信未配置") + //} + if len(keys) > 1 { + sdk.SmsKey = keys[0] + sdk.SmsSecret = keys[1] + } + +} + +// SelectFunction is select api with operation +func (sdk *SDK) SelectFunction(operation string) *SDK { + sdk.operation = operation + return sdk +} + +// WithSMSArgs is SMS +func (sdk *SDK) WithSMSArgs(args map[string]interface{}) *SDK { + res, err := SMSend(sdk.Action, sdk.operation, sdk.SmsKey, sdk.SmsSecret, args) + if err != nil { + logx.Error(err) + } + sdk.response = res + return sdk +} + +// WithArgs is post data to api +func (sdk *SDK) WithArgs(args map[string]interface{}) *SDK { + // args["appkey"] = svc.SysCfgGet(c, md.KEY_CFG_ZM_AK) + // args["secret_key"] = svc.SysCfgGet(c, md.KEY_CFG_ZM_SK) + + res, err := Send(sdk.Action, sdk.operation, args) + if err != nil { + logx.Error(err) + } + // for k, v := range args { + // fmt.Printf("%s:%v \n", k, v) + // } + fmt.Println("唯品会请求", args, string(res)) + sdk.response = res + return sdk +} + +// Result is response data from api , return interface{} +func (sdk *SDK) Result() (*SDK, error) { + if sdk.err != nil { + return nil, sdk.err + } + tmp := struct { + Msg string `json:"msg"` + Success int `json:"success"` + Data interface{} `json:"data"` + }{} + if err := json.Unmarshal(sdk.response, &tmp); err != nil { + return nil, logx.Error("【Resp】" + string(sdk.response) + ", 【Error】" + err.Error()) + } + if tmp.Success != StatusSuc { + return nil, logx.Error(string(sdk.response)) + } + + if gjson.GetBytes(sdk.response, "data").String() == "[]" { + return nil, errors.New("no result") + } + + sdk.data = tmp.Data + return sdk, nil +} diff --git a/app/lib/zhimeng/sms_send.go b/app/lib/zhimeng/sms_send.go new file mode 100644 index 0000000..21030a4 --- /dev/null +++ b/app/lib/zhimeng/sms_send.go @@ -0,0 +1,70 @@ +package zhimeng + +import ( + "applet/app/utils" + "applet/app/utils/logx" + "code.fnuoos.com/go_rely_warehouse/zyos_go_third_party_api.git/sms" + "encoding/json" + "xorm.io/xorm" +) + +// 查询数量 +func SmsNum(engine *xorm.Engine, dbName, smsType, appKey, appSecret string) (int, error) { + if smsType == "1" { //新的 + num := sms.SmsNumGetSmsNum(engine, "putong", dbName) + return num, nil + } + params := map[string]interface{}{ + "appkey": appKey, + "secret_key": appSecret, + } + resp, err := Send("send_msg", "msg_num", params) + if err != nil { + return 0, logx.Warn(err) + } + var tmp struct { + Msg string `json:"msg"` + Success int `json:"success"` + Data struct { + Count string `json:"count"` + } `json:"data"` + } + if err = json.Unmarshal(resp, &tmp); err != nil { + return 0, logx.Warn("[resp]: " + string(resp) + ", [err]:" + err.Error()) + } + return utils.StrToInt(tmp.Data.Count), nil +} + +func SmsToSend(engine *xorm.Engine, dbName, smsType, appKey, appSecret, content, phone string) (int, error) { + if smsType == "1" { //新的 + args := map[string]interface{}{ + "mobile": phone, + "content": content, + "sms_type": "putong", + "uid": dbName, + } + err := sms.SmsSend(engine, args) + if err != nil { + return 0, err + } + return 1, nil + } + params := map[string]interface{}{ + "appkey": appKey, + "secret_key": appSecret, + "mobile": phone, + "content": content, + } + resp, err := Send("send_msg", "msg_doing", params) + if err != nil { + return 0, logx.Warn(err) + } + var tmp struct { + Msg string `json:"msg"` + Success int `json:"success"` + } + if err = json.Unmarshal(resp, &tmp); err != nil { + return 0, logx.Warn("[resp]: " + string(resp) + ", [err]:" + err.Error()) + } + return tmp.Success, nil +} diff --git a/app/md/md_login.go b/app/md/md_login.go index 16b0897..8f7da1d 100644 --- a/app/md/md_login.go +++ b/app/md/md_login.go @@ -8,3 +8,11 @@ type LoginReq struct { type LoginResponse struct { Token string `json:"token"` } +type Register struct { + Mobile string `json:"mobile"` + Captcha string `json:"captcha"` + Type string `json:"type"` + Zone string `json:"zone"` + PicCode string `json:"pic_code"` + PicCodeId string `json:"pic_code_id"` +} diff --git a/app/mw/mw_auth_jwt.go b/app/mw/mw_auth_jwt.go index a6b2f3c..586136d 100644 --- a/app/mw/mw_auth_jwt.go +++ b/app/mw/mw_auth_jwt.go @@ -45,7 +45,16 @@ func AuthJWT(c *gin.Context) { e.OutErr(c, e.ERR_TOKEN_EXPIRE, errors.New("token 过期已失效")) return } + MasterId := claim.MasterId + if strings.Contains(MasterId, "_") { + ex := strings.Split(MasterId, "_") + if len(ex) >= 3 { + MasterId = ex[0] + c.Set("phone", ex[1]) + c.Set("is_system", ex[2]) + } + } //设置上下文信息 - c.Set("master_id", claim.MasterId) + c.Set("master_id", MasterId) c.Next() } diff --git a/app/mw/mw_request_cache.go b/app/mw/mw_request_cache.go index bb7261c..c8f7a29 100644 --- a/app/mw/mw_request_cache.go +++ b/app/mw/mw_request_cache.go @@ -11,13 +11,13 @@ import ( "io/ioutil" ) -//自己实现一个type gin.ResponseWriter interface +// 自己实现一个type gin.ResponseWriter interface type responseWriter struct { gin.ResponseWriter b *bytes.Buffer } -//重写Write([]byte) (int, error) +// 重写Write([]byte) (int, error) func (w responseWriter) Write(b []byte) (int, error) { //向一个bytes.buffer中再写一份数据 w.b.Write(b) diff --git a/app/router/router.go b/app/router/router.go index a9ca5f8..01938a3 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -99,6 +99,7 @@ func routeZhimeng(r *gin.RouterGroup) { r.POST("/withdrawal_income", platformHdl.WithdrawalIncome) r.POST("/withdrawal_bind_alipay", platformHdl.WithdrawalBindAlipay) + r.GET("/sms", platformHdl.Sms) r.POST("/withdrawal_list", platformHdl.WithdrawalList) r.POST("/withdrawal_doing", platformHdl.WithdrawalDoing) r.POST("/withdrawal_output", platformHdl.WithdrawalOutput) diff --git a/app/svc/platform/svc_withdrawal.go b/app/svc/platform/svc_withdrawal.go index 900c39f..429a467 100644 --- a/app/svc/platform/svc_withdrawal.go +++ b/app/svc/platform/svc_withdrawal.go @@ -5,6 +5,7 @@ import ( "applet/app/db/model" offical "applet/app/db/official" "applet/app/e" + "applet/app/lib/mob" "applet/app/utils" "applet/app/utils/cache" "encoding/json" @@ -321,19 +322,47 @@ func WithdrawalBindAlipay(c *gin.Context) { e.OutErr(c, 400, e.NewErr(400, "支付宝信息不能为空")) return } - masterId, _ := c.Get("master_id") - mid := utils.AnyToString(masterId) - masterDb := db.MasterDb{} - masterDb.Set() - master := masterDb.GetMaster(mid) - master.AlipayName = args["alipay_name"] - master.Alipay = args["alipay"] - update := masterDb.MasterUpdate(master) - if update == false { - e.OutErr(c, 400, e.NewErr(400, "修改失败")) + mob1, errr := mob.GetMobSDK(c.GetString("master_id")) + if errr != nil { + e.OutErr(c, e.ERR_MOB_CONFIG, errr) return } - e.OutSuc(c, "success", nil) + if c.GetString("is_system") == "" { + e.OutErr(c, 400, e.NewErr(400, "请重新在后台进入聚合联盟")) + return + } + if c.GetString("is_system") != "1" { + e.OutErr(c, 400, e.NewErr(400, "请使用超管账号设置")) + return + } + send := map[string]interface{}{ + "phone": c.GetString("phone"), + "zone": "86", + "code": args["captcha"], + } + ok, err := mob1.MobSMS(c, send) + if err != nil { + e.OutErr(c, 400, err.Error()) + return + } + ok = true + if ok { + masterId, _ := c.Get("master_id") + mid := utils.AnyToString(masterId) + masterDb := db.MasterDb{} + masterDb.Set() + master := masterDb.GetMaster(mid) + master.AlipayName = args["alipay_name"] + master.Alipay = args["alipay"] + update := masterDb.MasterUpdate(master) + if update == false { + e.OutErr(c, 400, e.NewErr(400, "修改失败")) + return + } + e.OutSuc(c, "success", nil) + return + } + e.OutErr(c, 400, e.NewErr(400, "验证码错误")) return } diff --git a/go.mod b/go.mod index d57b04a..fced9e3 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.15 require ( code.fnuoos.com/go_rely_warehouse/zyos_go_es.git v1.0.1-0.20230707081910-52e70aa52998 - code.fnuoos.com/go_rely_warehouse/zyos_go_third_party_api.git v1.1.21-0.20240628083932-3a853b9ed178 + code.fnuoos.com/go_rely_warehouse/zyos_go_third_party_api.git v1.1.21-0.20240701084733-e197a1ca8795 github.com/360EntSecGroup-Skylar/excelize v1.4.1 github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/boombuler/barcode v1.0.1 diff --git a/main.go b/main.go index 8beb23c..4cc4873 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,7 @@ import ( "applet/app/router" ) -//系统初始化 +// 系统初始化 func init() { cfg.InitCfg() //配置初始化 cfg.InitLog() //日志初始化 @@ -28,6 +28,9 @@ func init() { if err := db.InitZhimengDB(cfg.ZhimengDB); err != nil { panic(err) } + channel := make(chan int, 0) //开辟管道,缓冲为 + go db.InitDBs(channel) + <-channel } fmt.Println("init success")