Browse Source

初版

master
zmy 1 day ago
parent
commit
bf6361944b
31 changed files with 2439 additions and 0 deletions
  1. 6
    0
      .gitignore
  2. 15
    0
      .vscode/launch.json
  3. 13
    0
      .vscode/settings.json
  4. 51
    0
      api/def.go
  5. 28
    0
      api/pub/def.go
  6. 39
    0
      api/pub/module.go
  7. 42
    0
      api/pub/upfile.go
  8. 33
    0
      api/test.go
  9. 15
    0
      build.bat
  10. 49
    0
      common/db.go
  11. 21
    0
      conf/default.conf
  12. 0
    0
      conf/default.conf.lock
  13. 367
    0
      db/db.go
  14. 76
    0
      go.mod
  15. 173
    0
      go.sum
  16. 179
    0
      keys/key_manager.go
  17. 54
    0
      keys/token_saver.go
  18. 42
    0
      main.go
  19. 88
    0
      models/db.go
  20. 65
    0
      models/shop_category.go
  21. 63
    0
      models/shop_product.go
  22. 100
    0
      pkg/tpl/tpl.go
  23. 83
    0
      pkg/utils/csv.go
  24. 16
    0
      pkg/utils/page.go
  25. 337
    0
      pkg/utils/util.go
  26. 107
    0
      routers/jwt.go
  27. 28
    0
      routers/pub_hub.go
  28. 89
    0
      routers/router.go
  29. 72
    0
      routers/static_fs.go
  30. 129
    0
      service.go
  31. 59
    0
      test/main.go

+ 6
- 0
.gitignore View File

@@ -0,0 +1,6 @@
/_build
/**/node_modules*
/web/adm/dist
/*.exe
/conf/curs
/_data

+ 15
- 0
.vscode/launch.json View File

@@ -0,0 +1,15 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceRoot}/"
}
]
}

+ 13
- 0
.vscode/settings.json View File

@@ -0,0 +1,13 @@
{
"prettier.trailingComma": "all",
"editor.wordWrapColumn": 160,
"typescript.preferences.quoteStyle": "single",
"javascript.preferences.quoteStyle": "single",
"workbench.tree.indent": 30,
"workbench.tree.renderIndentGuides": "always",
"prettier.singleQuote": true,
"prettier.jsxBracketSameLine": true,
"prettier.jsxSingleQuote": true,
"prettier.printWidth": 160,
"compile-hero.disable-compile-files-on-did-save-code": true
}

+ 51
- 0
api/def.go View File

@@ -0,0 +1,51 @@
package api

import (
"fmt"
"strconv"
"strings"
"time"

baapi "3g3e.com/rechy/ck.ba.api"
jwt "3g3e.com/rechy/ck.ba.api/jwt-v2"
"3g3e.com/ymdx/lib"
"github.com/gin-gonic/gin"
)

//
func Unauthorized(c *gin.Context, code int, message string) {
baapi.JSONReply(c, "nologin", fmt.Sprintf(message))
}

//
func GetUserId(c *gin.Context) (baapi.Type, int64) {
claims := jwt.ExtractClaims(c)
fmt.Println(lib.JsonEncodeIndent(claims))

r := strings.Split(claims["id"].(string), "-")
if len(r) < 2 {
return baapi.TypeUnknown, 0
}

id, _ := strconv.ParseInt(r[1], 10, 64)
return baapi.Type(r[0]), id
}

// 登录
func LoginResponse(c *gin.Context, code int, token string, tm time.Time) {
user, ok := c.Get("user")
if !ok {
baapi.JSONReply(c, "error", "用户信息不存在")
return
}
dd := gin.H{
"token": token,
"user": user,
}
baapi.JSONReply(c, "success", "登录成功", dd)
}

// 登出
func LogoutResponse(c *gin.Context, code int) {
baapi.JSONReply(c, "success", "退出成功")
}

+ 28
- 0
api/pub/def.go View File

@@ -0,0 +1,28 @@
package pub

import (
"sync"

baapi "3g3e.com/rechy/ck.ba.api"
)

// 命令对应方法的键值对集
// cmd => func(*baapi.Command) (string, string, interface{})
var fns sync.Map

func FindHandler(cmd string) (func(*baapi.Command) (string, interface{}, interface{}), bool) {
r, ok := fns.Load(cmd)
if !ok {
return nil, false
}
f, ok := r.(func(*baapi.Command) (string, interface{}, interface{}))
if !ok {
return nil, false
}

return f, true
}

func RegisterCommand(cmd string, fn func(*baapi.Command) (string, interface{}, interface{})) {
fns.Store(cmd, fn)
}

+ 39
- 0
api/pub/module.go View File

@@ -0,0 +1,39 @@
package pub

import (
"encoding/json"

baapi "3g3e.com/rechy/ck.ba.api"
"3g3e.com/ymdx/glog"
"3g3e.com/zmy/ck.platform.shop/models"
)

func init() {
RegisterCommand("close_module", CommandCloseModule)
}

// 关闭模块
func CommandCloseModule(req *baapi.Command) (string, interface{}, interface{}) {

glog.Infolns("run pub command:", req)
if req.Data == "" {
return "error", "req.Data is empty", nil
}
//将商户的开启状态修改成禁止状态
var userinfo *baapi.ModUser
err := json.Unmarshal([]byte(req.Data), &userinfo)
if err != nil {
return "error", err, nil
}
uuid := userinfo.UUID
info := make(map[string]interface{})
info["status"] = 11

re := models.Db.Table("euser").Where("uuid = ?", uuid).Updates(info)
if re.Error != nil {
return "fail", "商户更新失败", nil
}

return "success", "成功", nil

}

+ 42
- 0
api/pub/upfile.go View File

@@ -0,0 +1,42 @@
package pub

import (
"net/http"
"strconv"
"time"

"github.com/gin-gonic/gin"
)

const Imgfile = "/var/www/soft/ck.platform.tag/img/"
const Imgpath = "https://dx.3g3e.com.cn/img/"

func UpFile(c *gin.Context) {
//获取文件头
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"message": "error",
"msg": err.Error(),
})
return
}
//获取文件名
t := time.Now().Unix()
fileName := strconv.FormatInt(t, 10) + ".png"
path := Imgfile + fileName
//保存文件到服务器本地
//SaveUploadedFile(文件头,保存路径)
if err := c.SaveUploadedFile(file, path); err != nil {
c.JSON(http.StatusOK, gin.H{
"message": "error",
"msg": "上传失败",
})
return
}
//保存成功返回正确的Json数据
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"picture": Imgpath + fileName,
})
}

+ 33
- 0
api/test.go View File

@@ -0,0 +1,33 @@
package api

import (
baapi "3g3e.com/rechy/ck.ba.api"
"3g3e.com/ymdx/glog"
"github.com/gin-gonic/gin"
)

// @Summary 测试
// @Accept json
// @Produce json
// @Param param1 query string false "参数1"
// @Param param2 query string false "参数2"
// @Success 200 {object} baapi.ResponseMessage "{code:success, message:...}"
// @Failure 203 {object} baapi.ResponseMessage "{code:error, message:...}"
// @Router /api/test [get]
func Test(c *gin.Context) {
var req struct {
Param1 string `form:"param1" json:"param1" binding:"-"`
Param2 string `form:"param2" json:"param2" binding:"-"`
}
if err := c.ShouldBind(&req); err != nil {
baapi.JSONReply(c, "error", err, nil)
return
}

glog.Infolns(req)

baapi.JSONReply("success", "测试成功", gin.H{
"test": "测试内容",
})

}

+ 15
- 0
build.bat View File

@@ -0,0 +1,15 @@
:: 删除目录
rd /s /q _build
md _build

:: 编译主程序
set GOOS=linux
set GOARCH=amd64
::go build -ldflags "-s -w" -o "_build/ck.platform.tag"
go build -o "_build/ck.platform.tag"

:: 构建目录
::echo F | xcopy /y /c /q .\conf\app_svr.conf .\_build\conf\app.conf
::echo F | xcopy /y /c /q .\conf\default_svr.conf .\_build\conf\default.conf
::xcopy /y /e /c /i /q .\static .\_build\static
::xcopy /y /e /c /i /q .\views .\_build\views

+ 49
- 0
common/db.go View File

@@ -0,0 +1,49 @@
package common

import (
"github.com/jmoiron/sqlx"
)

func Rows(rows *sqlx.Rows) []map[string]interface{} {
res := make([]map[string]interface{}, 0)
if rows == nil {
return res
}
for rows.Next() {
dest := make(map[string]interface{})
rows.MapScan(dest)
for key, item := range dest {
switch item.(type) {
case []byte:
dest[key] = string(item.([]byte))
default:
}
}
res = append(res, dest)
}
rows.Close()
return res
}

func SqlInStrFromArrayString(items []string) string {
ll := len(items)
str := ""
for i := 0; i < ll; i++ {
idStr := items[i]
if idStr == "" {
continue
}
if i > 0 {
str = str + "','" + idStr
} else {
str = "('" + idStr
}
}
if len(str) > 0 {
str = str + "')"
} else {
str = "(-1)"
}

return str
}

+ 21
- 0
conf/default.conf View File

@@ -0,0 +1,21 @@
{
"cors_domain_perttens": [
"*.3g3e.com.cn",
"3g3e.com.cn",
"*.3g3e.com",
"3g3e.com"
],
"db": {
"driver": "mysql",
"source": "ckshop01:WLrojMppwE9@tcp(221.180.226.54:3389)/ck_shop?charset=utf8mb4\u0026parseTime=True\u0026loc=Local"
},
"id": "Olg37J0dQy12vxlx",
"module": {
"ba_login_url": "https://ba.3g3e.com.cn/api/login",
"ba_url": "https://ba.3g3e.com.cn",
"jwt": {
},
"key": "ck.shop.api"
},
"port": 8351
}

+ 0
- 0
conf/default.conf.lock View File


+ 367
- 0
db/db.go View File

@@ -0,0 +1,367 @@
package db

import (
"errors"
"math"
"strings"

"3g3e.com/ymdx/glog"
"3g3e.com/ymdx/lib"
"3g3e.com/zmy/ck.platform.shop/common"
"github.com/jmoiron/sqlx"
)

var sdb *sqlx.DB

func InitDB(dirverName, dataSource string) (*sqlx.DB, error) {
if sdb != nil {
sdb.Close()
sdb = nil
}

if dirverName == "sqlite3" && dataSource != ":memory:" {
dataSource = lib.EnsureFilePath(dataSource)
}

var err error
sdb, err = sqlx.Open(dirverName, dataSource)
if err != nil {
glog.Infoln(err)
return nil, err
}
sdb.SetMaxOpenConns(300)
sdb.SetMaxIdleConns(30)
sdb.Ping()

//create table

return sdb, nil
}
func PayOrderFld(sess map[string]interface{}, utype string, muid int, uid int, cpnum float64, orderId string, about string) error {

if utype == "user" {

sql := "SELECT user_money FROM user WHERE uid=? LIMIT 0, 1"
rows, err := sdb.Queryx(sql, muid)
if err != nil {
glog.Errorln(err)
return err
}

rs := common.Rows(rows)
rows.Close()
if len(rs) == 0 {
return errors.New("1#len(rs) == 0")
}
mone := rs[0]

if cpnum == 0 {
about = "您已成功获得此次充值!"
}
if cpnum < 0 {
needMoney := lib.ValueToFloat(mone["user_money"], 0.0) - math.Abs(cpnum/10000)
wtime := lib.Now().Format("2006-01-02 15:04:05")
sqli := "INSERT INTO paycouponlog (uid,order_sn,muid,user_money,cpcr,type_desc,cpabout,status,now_count,wtime) VALUES (?,?,?,?,?,?,?,?,?,?)"
_, err := sdb.Exec(sqli,
uid,
orderId,
muid,
cpnum/10000,
"",
"获得充值",
about,
1,
needMoney,
wtime)
if err != nil {
glog.Errorln(err)
return err
}

DecUserMoney(muid, math.Abs(cpnum), about, needMoney, sess)
} else {
needMoney := lib.ValueToFloat(mone["user_money"], 0.0) + math.Abs(cpnum/10000)
wtime := lib.Now().Format("2006-01-02 15:04:05")
sqli := "INSERT INTO paycouponlog (uid,order_sn,muid,user_money,cpcr,type_desc,cpabout,status,now_count,wtime) VALUES (?,?,?,?,?,?,?,?,?,?)"
_, err := sdb.Exec(sqli,
uid,
orderId,
muid,
cpnum/10000,
"",
"获得充值",
about,
1,
needMoney,
wtime)
if err != nil {
glog.Errorln(err)
return err
}

AddUserMoney(muid, cpnum, about, needMoney, sess)
}

} else if utype == "euser" {
sql := "SELECT user_money FROM euser WHERE uid=? LIMIT 0, 1"
rows, err := sdb.Queryx(sql, muid)
if err != nil {
glog.Errorln(err)
return err
}
rs := common.Rows(rows)
rows.Close()

if len(rs) == 0 {
return errors.New("2#len(rs) == 0")
}
mone := rs[0]

if cpnum == 0 {
about = "您已成功获得此次充值!"
}
if cpnum < 0 {
needMoney := lib.ValueToFloat(mone["user_money"], 0.0) - math.Abs(cpnum/10000)
wtime := lib.Now().Format("2006-01-02 15:04:05")
sqli := "INSERT INTO epaycouponlog (uid,order_sn,muid,user_money,cpcr,type_desc,cpabout,status,now_count,wtime) VALUES (?,?,?,?,?,?,?,?,?,?)"
_, err := sdb.Exec(sqli,
uid,
orderId,
muid,
cpnum/10000,
"",
"获得充值",
about,
1,
needMoney,
wtime,
)
if err != nil {
glog.Errorln(err)
return err
}
DecEuserMoney(muid, math.Abs(cpnum), about, needMoney, sess)
} else {
needMoney := lib.ValueToFloat(mone["user_money"], 0.0) + math.Abs(cpnum/10000)
wtime := lib.Now().Format("2006-01-02 15:04:05")
sqli := "INSERT INTO epaycouponlog (uid,order_sn,muid,user_money,cpcr,type_desc,cpabout,status,now_count,wtime) VALUES (?,?,?,?,?,?,?,?,?,?)"
_, err := sdb.Exec(sqli,
uid,
orderId,
muid,
cpnum/10000,
"",
"获得充值",
about,
1,
needMoney,
wtime,
)
if err != nil {
glog.Errorln(err)
return err
}
AddEuserMoney(muid, cpnum, about, needMoney, sess)
}
}

return nil
}
func AddUserMoney(uid int, num float64, title string, now_count float64, sess map[string]interface{}) bool {
re := false
if !lib.ValueIsEmpty(uid) && num >= 0 {
re = AddPayLog(
uid,
num,
title,
now_count,
sess,
)
}
return re
}

func DecUserMoney(uid int, num float64, title string, now_count float64, sess map[string]interface{}) bool {
re := false
if !lib.ValueIsEmpty(uid) && num >= 0 {
re = AddPayLog(
uid,
(-1)*num,
title,
now_count,
sess,
)
}
return re
}
func AddPayLog(userId int, userMoney float64, changeDesc string, now_count float64, sess map[string]interface{}) bool {

if userMoney < 0 {
gcount := GetCount("SELECT COUNT(*) FROM user Where uid = ? and user_money >= ?", userId, math.Abs(userMoney/10000))
if gcount == 0 {
return false
}
}

/* 插入帐户变动记录 */
changeTime := lib.Now().Format("2006-01-02 15:04:05")
sql := "INSERT INTO payaccountlog (user_id,user_money,change_desc,change_time,change_type,now_count) VALUES (?,?,?,?,?,?)"
_, err := sdb.Exec(sql,
userId,
userMoney/10000,
changeDesc,
changeTime,
99,
now_count,
)
if err != nil {
glog.Errorln(err)
return false
}
/* 更新用户信息 */

sqli := "UPDATE user SET user_money=user_money+? WHERE uid=?"
_, err = sdb.Exec(sqli,
userMoney/10000,
userId,
)
if err != nil {
glog.Errorln(err)
return false
}
if sess != nil {
sqls := "SELECT user_money FROM user WHERE uid=? LIMIT 0, 1"
rows, err := sdb.Queryx(sqls, userId)
if err != nil {
glog.Errorln(err)
return false
}
rs := common.Rows(rows)
rows.Close()

if len(rs) == 0 {
glog.Errorln("#重大错误")
return false
}
mone := rs[0]
sess["user_money"] = mone["user_money"]
}
return true
}

func AddEPayLog(userId int, userMoney float64, changeDesc string, now_count float64, sess map[string]interface{}) bool {

if userMoney < 0 && userId != 1 {
gcount := GetCount("SELECT COUNT(*) FROM euser Where uid = ? and user_money >= ?", userId, math.Abs(userMoney/10000))
if gcount == 0 {
return false
}
}

/* 插入帐户变动记录 */
changeTime := lib.Now().Format("2006-01-02 15:04:05")
sql := "INSERT INTO epayaccountlog (user_id,user_money,change_desc,change_time,change_type,now_count) VALUES (?,?,?,?,?,?)"
_, err := sdb.Exec(sql,
userId,
userMoney/10000,
changeDesc,
changeTime,
99,
now_count,
)
if err != nil {
glog.Errorln(err)
return false
}
/* 更新用户信息 */

sqli := "UPDATE euser SET user_money=user_money+?,sys_money=sys_money+? WHERE uid=?"
_, err = sdb.Exec(sqli,
userMoney/10000,
userMoney/10000,
userId,
)
if err != nil {
glog.Errorln(err)
return false
}
if sess != nil {
sqls := "SELECT user_money FROM euser WHERE uid=? LIMIT 0, 1"
rows, err := sdb.Queryx(sqls, userId)
if err != nil {
glog.Errorln(err)
return false
}
rs := common.Rows(rows)
rows.Close()

if len(rs) == 0 {
glog.Errorln("#重大错误")
return false
}
mone := rs[0]
sess["user_money"] = mone["user_money"]
sess["sys_money"] = mone["sys_money"]
}
return true
}
func GetCount(sql string, args ...interface{}) int {
strings.Replace(sql, "count(*)", "COUNT(*)", -1)
rows, err := sdb.Queryx(sql, args...)
if err != nil {
return 0
}
rs := common.Rows(rows)
rows.Close()

if len(rs) == 0 {
return 0
}

return lib.ValueToInt(rs[0]["COUNT(*)"], 0)
}
func DecEuserMoney(uid int, num float64, title string, now_count float64, sess map[string]interface{}) bool {

re := false
if !lib.ValueIsEmpty(uid) && num >= 0 {
re = AddEPayLog(
uid,
(-1)*num,
title,
now_count,
sess,
)
}
return re
}
func AddEuserMoney(uid int, num float64, title string, now_count float64, sess map[string]interface{}) bool {

re := false
if !lib.ValueIsEmpty(uid) && num >= 0 {
re = AddEPayLog(
uid,
num,
title,
now_count,
sess,
)
}
return re
}

func GETEuser(eid int64) (map[string]interface{}, error) {
sql := "SELECT user_money FROM euser WHERE uid=? LIMIT 0, 1"
rows, err := sdb.Queryx(sql, eid)
if err != nil {
glog.Errorln(err)
return nil, err
}
rs := common.Rows(rows)
rows.Close()

if len(rs) == 0 {
return nil, errors.New("2#len(rs) == 0")
}
sess := rs[0]
return sess, nil
}

+ 76
- 0
go.mod View File

@@ -0,0 +1,76 @@
module 3g3e.com/zmy/ck.platform.shop

go 1.22

toolchain go1.22.4

replace (
3g3e.com/rechy/ck.ba.api => ./../../../3g3e.com/rechy/ck.ba.api
3g3e.com/rechy/visdb => ./../../../3g3e.com/rechy/visdb
3g3e.com/ymdx/config => ./../../../3g3e.com/ymdx/config
3g3e.com/ymdx/glog => ./../../../3g3e.com/ymdx/glog
3g3e.com/ymdx/lib => ./../../../3g3e.com/ymdx/lib
)

require (
3g3e.com/rechy/ck.ba.api v0.0.0-00010101000000-000000000000
3g3e.com/rechy/visdb v0.0.0-00010101000000-000000000000
3g3e.com/ymdx/config v0.0.0-00010101000000-000000000000
3g3e.com/ymdx/glog v0.0.0-00010101000000-000000000000
3g3e.com/ymdx/lib v0.0.0-00010101000000-000000000000
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/jmoiron/sqlx v1.4.0
github.com/satori/go.uuid v1.2.0
github.com/takama/daemon v1.0.0
github.com/unknwon/com v1.0.1
golang.org/x/crypto v0.26.0
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.11
)

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/pprof v1.5.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gookit/color v1.5.2 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/hunzsig/graphics v0.0.0-20190929035859-45587367f25f // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/smartystreets/assertions v1.1.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

+ 173
- 0
go.sum View File

@@ -0,0 +1,173 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3 h1:l/lhv2aJCUignzls81+wvga0TFlyoZx8QxRMQgXpZik=
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.3/go.mod h1:AKpV6+wZ2MfPRJnTbQ6NPgWrKzbe9RCIlCF/FKzMtM8=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/pprof v1.5.0 h1:E/Oy7g+kNw94KfdCy3bZxQFtyDnAX2V7axRS7sNYVrU=
github.com/gin-contrib/pprof v1.5.0/go.mod h1:GqFL6LerKoCQ/RSWnkYczkTJ+tOAUVN/8sbnEtaqOKs=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gookit/color v1.5.2 h1:uLnfXcaFjlrDnQDT+NCBcfhrXqYTx/rcCa6xn01Y8yI=
github.com/gookit/color v1.5.2/go.mod h1:w8h4bGiHeeBpvQVePTutdbERIUf3oJE5lZ8HM0UgXyg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hunzsig/graphics v0.0.0-20190929035859-45587367f25f h1:qXExBWlmgjia7C3PTdsg52SA4LmJ+0Y0tyIWZ/SToA0=
github.com/hunzsig/graphics v0.0.0-20190929035859-45587367f25f/go.mod h1:gv/HT+LsUPuRTgdXpYU3BYHzw0rzxMKpp70IZMUnrhE=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0=
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ=
github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 179
- 0
keys/key_manager.go View File

@@ -0,0 +1,179 @@
package keys

import (
"crypto/rsa"
"errors"
"sync"

baapi "3g3e.com/rechy/ck.ba.api"
jwt "3g3e.com/rechy/ck.ba.api/jwt-v2"
cfg "3g3e.com/ymdx/config"
"3g3e.com/ymdx/glog"
"3g3e.com/ymdx/lib"
)

var km *KeyManager
var keyManagerLock sync.Mutex

func GetKeyManager() *KeyManager {
keyManagerLock.Lock()
defer keyManagerLock.Unlock()

if km == nil {
km = &KeyManager{}
}

if !km.isInited {
km.initKeys()
}
return km
}

func ResetKeyManager() error {
keyManagerLock.Lock()
defer keyManagerLock.Unlock()

k := &KeyManager{}
err := k.initKeys()
if err != nil {
return err
}
km = k
return nil
}

type BaKeys struct {
MyPrivateKey *rsa.PrivateKey
AimPublicKey *rsa.PublicKey
}

type KeyManager struct {
isInited bool
selfPrivateKey *rsa.PrivateKey
selfPublicKey *rsa.PublicKey
baPrivKeys sync.Map // string(module_name) => *BaKeys
baOtherPublicKeys sync.Map // string(module_name) => *rsa.PublicKey
baPublicKey *rsa.PublicKey
}

func (k *KeyManager) initKeys() error {
defer func() {
// 不管是否成功初始化, 都设置为已经初始化
k.isInited = true
}()

var err error
privKey, pubKey, err := lib.GenKeyPair(2048)
if err != nil {
return err
}

selfPrivateKey := cfg.String("module.jwt.self.private_key", privKey)
selfPublicKey := cfg.String("module.jwt.self.public_key", pubKey)
baPublicKey := cfg.String("module.jwt.ba.public_key", "")
baOtherPublicKeys := cfg.Map("module.jwt.ba.others", nil)

k.selfPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(selfPrivateKey))
if err != nil {
return err
}
k.selfPublicKey, err = jwt.ParseRSAPublicKeyFromPEM([]byte(selfPublicKey))
if err != nil {
return err
}

for ki, vi := range baOtherPublicKeys {
pkey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(lib.ValueToString(vi, "")))
if err != nil {
glog.Errorlns("ba.others.public_key:", ki, err)
continue
}

k.baOtherPublicKeys.Store(ki, pkey)
}

k.baPublicKey, err = jwt.ParseRSAPublicKeyFromPEM([]byte(baPublicKey))
if err != nil {
return err
}
return nil
}

func (k *KeyManager) ModifyTokenHeader(header map[string]interface{}) map[string]interface{} {
header["iss"] = baapi.ModuleName()
return header
}

func (k *KeyManager) KeyFuncVerify(header map[string]interface{}) (interface{}, error) {
iss, ok1 := header["iss"] // 颁发模块
aud, ok2 := header["aud"] // token申请登录模块
if ok1 && ok2 && iss != "" && aud != "" {
if iss == aud {
return k.selfPublicKey, nil
}
}

if k.baPublicKey == nil {
return nil, jwt.ErrInvalidPubKey
}

s, ok := k.baOtherPublicKeys.Load(aud)
if ok {
return s.(*rsa.PublicKey), nil
}

return k.baPublicKey, nil
}

func (k *KeyManager) KeyFuncSign(header map[string]interface{}) (interface{}, error) {

iss, ok1 := header["iss"]
aud, ok2 := header["aud"]
if ok1 && ok2 && iss != "" && aud != "" {
if iss == aud {
if k.selfPrivateKey != nil {
return k.selfPrivateKey, nil
}
} else {
ks, ok := k.baPrivKeys.Load(aud)
if ok && ks != nil {
keys := ks.(*BaKeys)
if keys.MyPrivateKey != nil {
return keys.MyPrivateKey, nil
}
}
}
}

return nil, jwt.ErrInvalidPrivKey
}

func (k *KeyManager) Sign(isBA bool, moduleTo string, data string) (string, error) {
if isBA {
ks, ok := k.baPrivKeys.Load(moduleTo)
if ok && ks != nil {
keys := ks.(*BaKeys)
if keys.MyPrivateKey != nil {
return jwt.RS512Sign(data, keys.MyPrivateKey)
}
}
} else {
return jwt.RS512Sign(string(data), k.selfPrivateKey)
}
return "", errors.New("sign fail")
}

func (k *KeyManager) Verify(isBA bool, moduleFrom string, data string, sign string) error {
if isBA {
ks, ok := k.baPrivKeys.Load(moduleFrom)
if ok && ks != nil {
keys := ks.(*BaKeys)
if keys.AimPublicKey != nil {
return jwt.RS512Verify(data, sign, keys.AimPublicKey)
}
}
} else {
return jwt.RS512Verify(data, sign, k.baPublicKey)
}
return errors.New("verify fail")
}

+ 54
- 0
keys/token_saver.go View File

@@ -0,0 +1,54 @@
package keys

import (
"path/filepath"
"sync"

baapi "3g3e.com/rechy/ck.ba.api"
"3g3e.com/rechy/visdb"
"3g3e.com/ymdx/lib"
)

func TokenSaverInitBySyncMap() {
baapi.ModTokenSaver = &sync.Map{}
}

func TokenSaverInit(baseDir string) {
baapi.ModTokenSaver = newVisdbTokenSaver(baseDir)
}

type visdbTokenSaver struct {
kv *visdb.KV
baseDir string
}

func newVisdbTokenSaver(baseDir string) *visdbTokenSaver {
return &visdbTokenSaver{
kv: visdb.NewKV(filepath.Join(baseDir, "db_pub_token_saver"), 3),
baseDir: baseDir,
}
}

func (ts *visdbTokenSaver) Store(k interface{}, v interface{}) {
ts.kv.Put(lib.ValueToString(k, ""), v)
}

func (ts *visdbTokenSaver) Load(k interface{}) (interface{}, bool) {
b, err := ts.kv.Get(lib.ValueToString(k, ""))
if err != nil {
return nil, false
}
return b, true
}

func (ts *visdbTokenSaver) Delete(k interface{}) {
ts.kv.Delete(lib.ValueToString(k, ""))
}

func Store(k interface{}, v interface{}) {
baapi.ModTokenSaver.Store(k, v)
}

func Load(k interface{}) (interface{}, bool) {
return baapi.ModTokenSaver.Load(k)
}

+ 42
- 0
main.go View File

@@ -0,0 +1,42 @@
package main

import (
"fmt"

cfg "3g3e.com/ymdx/config"
"3g3e.com/ymdx/glog"
"3g3e.com/zmy/ck.platform.shop/models"
"3g3e.com/zmy/ck.platform.shop/routers"
"github.com/gin-gonic/gin"
)

const (
srvName = "Platform Shop Mnaager"
srvDescription = "Platform Shop Mnaager by Change Ke"
)

var (
ServicePort int = 8351
)

func (app *exitApp) main() {
defer app.exit()
defer glog.Flush()
// defer glog.Defer("")

// 开始链接数据库
err := models.Init()
if err != nil {
glog.Errorln("数据库连接失败:", err)
return
}
r := gin.Default()
routers.SetupRouter(r)

ServicePort = cfg.Int("port", ServicePort)
err = r.Run(fmt.Sprintf(":%d", ServicePort))
if err != nil {
glog.Errorln("服务器异常退出:", err)
return
}
}

+ 88
- 0
models/db.go View File

@@ -0,0 +1,88 @@
package models

import (
"context"
"fmt"
"time"

cfg "3g3e.com/ymdx/config"
"3g3e.com/ymdx/glog"
sdb "3g3e.com/zmy/ck.platform.shop/db"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)

var Db *gorm.DB
var tables []interface{}

type dbLogger struct{}

func (w *dbLogger) LogMode(logger.LogLevel) logger.Interface {
return w
}

func (w *dbLogger) Info(ctx context.Context, format string, args ...interface{}) {
glog.InfoExtDepthln("db", -4, fmt.Sprintf(format, args...))
}

func (w *dbLogger) Warn(ctx context.Context, format string, args ...interface{}) {
glog.WarningExtDepthln("db", -4, fmt.Sprintf(format, args...))
}

func (w *dbLogger) Error(ctx context.Context, format string, args ...interface{}) {
glog.ErrorExtDepthln("db", -4, fmt.Sprintf(format, args...))
}

func (w *dbLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
sql, affw := fc()
glog.InfoExtDepthln("db", -4, sql, affw, err)
}

func Init() error {
driver := cfg.String("db.driver", "mysql")
glog.InfoExtln("db", driver)

source := cfg.String("db.source", "ckdx01:WAdijEspwEE@tcp(221.180.226.54:3389)/ck_dx?charset=utf8&parseTime=True&loc=Local")
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: source,
}), &gorm.Config{
Logger: &dbLogger{},
})
if err != nil {
return err
}
Db = db
sdb.InitDB(driver, source)

Db.AutoMigrate(tables...)
//Db.DB().SetMaxIdleConns(10)
//Db.DB().SetMaxOpenConns(100)
return nil
}

func CloseDB() {

}

func SqlInStrFromArrayString(items []string) string {
ll := len(items)
str := ""
for i := 0; i < ll; i++ {
idStr := items[i]
if idStr == "" {
continue
}
if i > 0 {
str = str + "','" + idStr
} else {
str = "('" + idStr
}
}
if len(str) > 0 {
str = str + "')"
} else {
str = "(-1)"
}
return str
}

+ 65
- 0
models/shop_category.go View File

@@ -0,0 +1,65 @@
package models

import (
"time"

"3g3e.com/ymdx/lib"
)

// 标签类别表
type ShopCategory struct {
Id int64 `gorm:"primary_key" json:"id"`
Eid int64 `gorm:"type:bigint(20)" json:"eid"` //商户EID
Name string `gorm:"type:char (64)" json:"name"` //类别名称
Pic string `gorm:"type:varchar (250)" json:"pic"` //类别图标
Pid int64 `gorm:"type:bigint(20)" json:"pid"` //父ID
CreateTime time.Time `gorm:"type:datetime" json:"create_time"`
UpdateTime time.Time `gorm:"type:datetime" json:"update_time"`
}

func init() {
tables = append(tables, &ShopCategory{})
}

func (ShopCategory) TableName() string {
return "shop_category"
}

func InsertShopCategory(data map[string]interface{}) (int64, error) {
result := Db.Create(&ShopCategory{
Name: lib.ValueToString(data["name"], ""),
Pid: lib.ValueToInt64(data["pid"], 0),
CreateTime: time.Now(),
})

return result.Statement.Dest.(*ShopCategory).Id, result.Error
}

func ShopCategoryOne(wh string, args ...interface{}) (results ShopCategory) {
Db.Table("shop_category").Where(wh, args...).First(&results)
return
}

func ShopCategoryList(wh string, args ...interface{}) []ShopCategory {
var res []ShopCategory
Db.Table("shop_category").Where(wh, args...).Order("id ASC").Find(&res)
return res
}

func ShopCategoryListPg(wh string, pagesize int, pg int, args ...interface{}) (int, []ShopCategory, error) {
var list []ShopCategory
Db.Table("shop_category").Where(wh, args...).Limit(pagesize).Offset(pagesize * (pg - 1)).Find(&list)
var count int64
Db.Table("shop_category").Where(wh, args...).Count(&count)
return int(count), list, nil
}

func UpdateShopCategory(wh string, data map[string]interface{}, args ...interface{}) error {
result := Db.Table("shop_category").Where(wh, args...).Updates(data)
return result.Error
}

func DeleteShopCategory(wh string, args ...interface{}) error {
res := Db.Table("shop_category").Where(wh, args...).Delete(&ShopCategory{})
return res.Error
}

+ 63
- 0
models/shop_product.go View File

@@ -0,0 +1,63 @@
package models

import (
"time"

"3g3e.com/ymdx/lib"
)

// 标签类别表
type ShopProduct struct {
Id int64 `gorm:"primary_key" json:"id"`
Eid int64 `gorm:"type:bigint(20)" json:"eid"` //商户EID
Cid int64 `gorm:"type:bigint(20)" json:"cid"` //类别ID
Name string `gorm:"type:varchar (250)" json:"name"` //产品名称
Price float64 `gorm:"type:double(16,2)" json:"price"` //现价
Cost float64 `gorm:"type:double(16,2)" json:"cost"` //原价
Postage float64 `gorm:"type:double(16,2)" json:"postage"` //邮费
ImageUrl string `gorm:"type:text" json:"image_url"` //展示图片
ImageDetail string `gorm:"type:text" json:"image_detail"` //详情图片
Remark string `gorm:"type:varchar (250)" json:"remark"` //备注
KeyWord string `gorm:"type:varchar (250)" json:"key_word"` //关键词
SkuCode string `gorm:"type:varchar (250)" json:"sku_code"` //商品编码
Stock int64 `gorm:"type:bigint(20)" json:"stock"` //库存
Sales int64 `gorm:"type:bigint(20)" json:"sales"` //销量
Sort int64 `gorm:"type:int(20)" json:"sort"` //排序
Status int64 `gorm:"type:int(1); default:'0'" json:"status"` //状态 1上架 0下架
IsDelete int64 `gorm:"type:int(1); default:'0'" json:"is_delete"` //状态 1删除 0正常
GoodsService string `gorm:"type:text" json:"goods_service"` //服务承诺
CreateTime time.Time `gorm:"type:datetime" json:"create_time"`
UpdateTime time.Time `gorm:"type:datetime" json:"update_time"`
}

func init() {
tables = append(tables, &ShopProduct{})
}

func (ShopProduct) TableName() string {
return "shop_product"
}

func InsertShopProduct(data map[string]interface{}) (int64, error) {
result := Db.Create(&ShopProduct{
Eid: lib.ValueToInt64(data["eid"], 0),
Cid: lib.ValueToInt64(data["cid"], 0),
Name: lib.ValueToString(data["name"], ""),
Price: lib.ValueToFloat(data["price"], 0),
Cost: lib.ValueToFloat(data["cost"], 0),
Postage: lib.ValueToFloat(data["postage"], 0),
ImageUrl: lib.ValueToString(data["image_url"], ""),
ImageDetail: lib.ValueToString(data["image_detail"], ""),
Remark: lib.ValueToString(data["remark"], ""),
KeyWord: lib.ValueToString(data["key_word"], ""),
SkuCode: lib.ValueToString(data["sku_code"], ""),
Stock: lib.ValueToInt64(data["stock"], 0),
Sales: lib.ValueToInt64(data["sales"], 0),
Sort: lib.ValueToInt64(data["sort"], 0),
Status: lib.ValueToInt64(data["status"], 0),
GoodsService: lib.ValueToString(data["goods_service"], ""),
CreateTime: time.Now(),
})

return result.Statement.Dest.(*ShopProduct).Id, result.Error
}

+ 100
- 0
pkg/tpl/tpl.go View File

@@ -0,0 +1,100 @@
package tpl

import (
"bytes"
"errors"
"regexp"
"strings"

"3g3e.com/ymdx/lib"
)

func TemplateToContent(template, paramsJsonStr string, contents ...string) (string, error) {
template = strings.TrimSpace(template)
if template == "" {
return "", errors.New("empty template")
}

if template == "*" {
if len(contents) < 1 || contents[0] == "" {
return "", errors.New("no sms content")
}
return strings.TrimSpace(contents[0]), nil
}

params := lib.JsonDecode([]byte(paramsJsonStr))
r := regexp.MustCompile(`\$\{([a-zA-Z0-9_]+)\}`)
tpl := []byte(template)
arr := r.FindAllSubmatchIndex(tpl, -1)

pre := 0
buf := bytes.NewBuffer(nil)
for _, v := range arr {
if len(v) < 4 {
continue
}

buf.Write(tpl[pre:v[0]])
key := template[v[2]:v[3]]
val, ok := params[string(key)]
if !ok {
return "", errors.New("lost var")
}
pre = v[1]
valStr := lib.ValueToString(val, "")
if valStr == "" {
return "", errors.New("unknown sms var value")
}
buf.WriteString(valStr)
}
buf.Write(tpl[pre:])
return buf.String(), nil
}

// 模板变量返回
func TemplateVars(template string) ([]string, error) {
r := regexp.MustCompile(`\$\{([a-zA-Z0-9_]+)\}`)
arr := r.FindAllStringSubmatch(template, -1)
sz := len(arr)
if sz < 1 {
return nil, nil
}

mp := map[string]string{}
re := make([]string, 0)
for _, v := range arr {
if len(v) < 2 {
continue
}
_, ok := mp[v[1]]
if ok {
return nil, errors.New("vars count error")
}
mp[v[1]] = v[0]
re = append(re, v[1])
}

return re, nil
}

// 模板书写是否有效
func TemplateValid(template string) bool {
r := regexp.MustCompile(`\$\{([a-zA-Z0-9_]+)\}`)
arr := r.FindAllStringSubmatch(template, -1)
mp := map[string]string{}
for _, v := range arr {
if len(v) < 2 {
continue
}
mp[v[1]] = v[0]
}

sz := len(mp)
if sz != len(arr) {
return false
}

r2 := regexp.MustCompile(`\$\{`)
arr2 := r2.FindAllString(template, -1)
return sz == len(arr2)
}

+ 83
- 0
pkg/utils/csv.go View File

@@ -0,0 +1,83 @@
package utils

import (
"encoding/csv"
"net/http"
"net/url"
)

const (
CsvFlushLineMax = 500
)

//
type CsvWriter struct {
w *csv.Writer
count int
}

//
func CsvNewWriter(w http.ResponseWriter, filename string) *CsvWriter {
w.Header().Set("Content-Type", "application/csv")
w.Header().Set("Content-Disposition", "attachment; filename=\""+url.QueryEscape(filename)+"\"")
w.Write([]byte("\xEF\xBB\xBF"))

return &CsvWriter{w: csv.NewWriter(w)}
}

//
func (c *CsvWriter) Write(data ...interface{}) error {
if c.w == nil || len(data) < 1 {
return nil
}
dataStrs := make([]string, len(data))
for k, v := range data {
dataStrs[k] = ValueToString(v, "")
}

c.count++
err := c.w.Write(dataStrs)
if c.count > CsvFlushLineMax {
c.w.Flush()
c.count = 0
}
return err
}

//
func (c *CsvWriter) WriteString(data ...string) error {
if c.w == nil || len(data) < 1 {
return nil
}
c.count++
err := c.w.Write(data)
if c.count > CsvFlushLineMax {
c.w.Flush()
c.count = 0
}
return err
}

//
func (c *CsvWriter) Close() {
c.w.Flush()
}

func (c *CsvWriter) WriteByMap(data map[string]map[string]string, mapkey []string) {
// if c.w == nil || len(data) < 1 {
// return nil
// }
for _, v := range data {
dataStrs := make([]string, len(v))
// i := 0
for i := 0; i < len(mapkey); i++ {
dataStrs[i] = ValueToString(v[mapkey[i]], "")
}
c.w.Write(dataStrs)
}
c.count++
if c.count > CsvFlushLineMax {
c.w.Flush()
c.count = 0
}
}

+ 16
- 0
pkg/utils/page.go View File

@@ -0,0 +1,16 @@
package utils

import (
"github.com/gin-gonic/gin"
"github.com/unknwon/com"
)

func GetPage(c *gin.Context) int {
result := 0
page, _ := com.StrTo(c.Query("pg")).Int()
if page > 0 {
result = (page - 1) * 20
}

return result
}

+ 337
- 0
pkg/utils/util.go View File

@@ -0,0 +1,337 @@
package utils

import (
"fmt"
"math/rand"
"reflect"
"regexp"
"strconv"
"strings"
"time"

"github.com/dgrijalva/jwt-go"
guuid "github.com/satori/go.uuid"
"golang.org/x/crypto/bcrypt"
)

func ValueIsEmpty(str string) bool {

if str == "" || len(str) == 0 {
return true
}
return false
}
func ValueToInt(in interface{}, def int) int {
if in == nil {
return def
}
switch in.(type) {
case int:
return in.(int)
case int8:
return int(in.(int8))
case int16:
return int(in.(int16))
case int32:
return int(in.(int32))
case int64:
return int(in.(int64))
case uint:
return int(in.(uint))
case uint8:
return int(in.(uint8))
case uint16:
return int(in.(uint16))
case uint32:
return int(in.(uint32))
case uint64:
return int(in.(uint64))
case float32:
return int(in.(float32))
case float64:
return int(in.(float64))
case []byte:
ii, _ := strconv.ParseFloat(string(in.([]byte)), 64)
return int(ii)
case string:
ii, _ := strconv.ParseFloat(in.(string), 64)
return int(ii)
case *[]byte:
ii, _ := strconv.ParseFloat(string(*(in.(*[]byte))), 64)
return int(ii)
case *string:
ii, _ := strconv.ParseFloat(*(in.(*string)), 64)
return int(ii)
default:
}
return def
}
func ValueToString(in interface{}, def string) string {
return ValueToStringIn(in, def, 0)
}

func ValueToStringIn(in interface{}, def string, fixed int) string {
if in == nil {
return def
}
switch in.(type) {
case int:
return strconv.FormatInt(int64(in.(int)), 10)
case int8:
return strconv.FormatInt(int64(in.(int8)), 10)
case int16:
return strconv.FormatInt(int64(in.(int16)), 10)
case int32:
return strconv.FormatInt(int64(in.(int32)), 10)
case int64:
return strconv.FormatInt(in.(int64), 10)
case uint:
return strconv.FormatUint(uint64(in.(uint)), 10)
case uint8:
return strconv.FormatUint(uint64(in.(uint8)), 10)
case uint16:
return strconv.FormatUint(uint64(in.(uint16)), 10)
case uint32:
return strconv.FormatUint(uint64(in.(uint32)), 10)
case uint64:
return strconv.FormatUint(in.(uint64), 10)
case float32:
if fixed > 0 {
return strconv.FormatFloat(float64(in.(float32)), 'f', fixed, 32)
}
return strconv.FormatFloat(float64(in.(float32)), 'f', 10, 32)
case float64:
if fixed > 0 {
return strconv.FormatFloat(in.(float64), 'f', fixed, 64)
}
return strconv.FormatFloat(in.(float64), 'f', 10, 64)
case []byte:
return string(in.([]byte))
case string:
return in.(string)
case *[]byte:
return string(*(in.(*[]byte)))
case *string:
return *(in.(*string))
default:
}
return def
}

// 判断某一个值是否在切片中
func In_array(val interface{}, array interface{}) (exists bool, index int) {
exists = false
index = -1
switch reflect.TypeOf(array).Kind() {
case reflect.Slice:
s := reflect.ValueOf(array)
for i := 0; i < s.Len(); i++ {
if reflect.DeepEqual(val, s.Index(i).Interface()) == true {
index = i
exists = true
return
}
}
}
return
}

// 数组转换成string类型
func ArrayToString(wh []string) string {
whereStr := ""
arrLen := len(wh)
for i := 0; i < arrLen; i++ {
whereStr = whereStr + " " + wh[i] + " AND"
}
whereStr = strings.TrimRight(whereStr, " AND")
return whereStr
}

// 生成随机方案
func GetOrderNum() string {

// 获取纳秒数
usec := time.Now().UnixNano()
usecStr := strconv.FormatInt(usec, 10)[13:16]

// 当天凌晨时间
str := time.Now().Format("2006-01-02")
ti, _ := time.Parse("2006-01-02", str)

// 当前时间和凌晨时间差的秒数
timeNum := time.Now().Unix() - ti.Unix()
sec := fmt.Sprintf("%05d", timeNum)

// 生成随机数并自动填充
sn := fmt.Sprintf("%05d", rand.Intn(100000))

num := time.Now().Format("060102") + sec + usecStr + sn
return num
}

// 数据库中的时间转换
func StrTimeTostr(str string) string {
time := strings.Replace(str, "T", " ", -1)
time = time[0:19]
return time
}

/**
* 解析 token
*/
func ParseToken(tokenSrt string, SecretKey []byte) (id string, name string, err error) {
var token *jwt.Token
token, err = jwt.Parse(tokenSrt, func(*jwt.Token) (interface{}, error) {
return SecretKey, nil
})
if !token.Valid { //服务端验证token是否有效
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("错误的token")
return
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
fmt.Println("token过期或未启用")
return
} else {
fmt.Println("无法处理这个token", err)
return
}
}
}
// token信息
claimsa, _ := token.Claims.(jwt.MapClaims)
id = claimsa["id"].(string)
name = claimsa["name"].(string)
return
}

// 加密
// 生成随机ordersn方案
func GetOrderSn() string {

// 获取纳秒数
usec := time.Now().UnixNano()
usecStr := strconv.FormatInt(usec, 10)[13:16]

// 当天凌晨时间
str := time.Now().Format("2006-01-02")
ti, _ := time.Parse("2006-01-02", str)

// 当前时间和凌晨时间差的秒数
timeNum := time.Now().Unix() - ti.Unix()
sec := fmt.Sprintf("%05d", timeNum)

// 生成随机数并自动填充
sn := fmt.Sprintf("%05d", rand.Intn(100000))

num := time.Now().Format("060102") + sec + usecStr + sn
return num
}

// 加密密码
func GenUserHashPassword(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}

// 验证密码
func ComparePasswords(hashedPwd string, plainPwd string) error {
byteHash := []byte(hashedPwd)
bytePlain := []byte(plainPwd)
err := bcrypt.CompareHashAndPassword(byteHash, bytePlain)
return err

}

// 将rune数组用字符串常量替换
const letterBytes = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

// 生成随机数
func RandStringBytes(n int) string {
// GenRsaKey(1024)
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}

func GetOrderSnn() string {
str := guuid.NewV4().String()
return strings.ReplaceAll(str, "-", "")
}

func SplitString(s string, myStrings []rune) []string {
Split := func(r rune) bool {
for _, v := range myStrings {
if v == r {
return true
}
}
return false
}
a := strings.FieldsFunc(s, Split)
return a
}

// 提取模板内容
func ExtractContent(input string) []string {
// 定义正则表达式模式
pattern := `\${(.*?)\}` // 非贪婪模式,只匹配最小数量的字符
// pattern := `/ $ (\w) }/gi`
// 编译正则表达式
re := regexp.MustCompile(pattern)

// 查找所有匹配项并返回结果切片
matches := re.FindAllStringSubmatch(input, -1)

var result []string
for _, match := range matches {
re := regexp.MustCompile(`\$|\{|\}`) // 创建正则表达式对象,匹配大括号
result = append(result, re.ReplaceAllString(match[0], ""))
}

return result
}

// 生成随机数
func GenValidateCodeNew(width int) string {
numeric := [64]string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
r := len(numeric)
rand.Seed(time.Now().UnixNano())

sb := ""
for i := 0; i < width; i++ {
sb += numeric[rand.Intn(r)]
}
return sb
}

func CheckMobile(phone string) (bool, string) {
// todo
valid := regexp.MustCompile("[0-9]")
num := valid.FindAllString(phone, -1)
phone = strings.Join(num, "")
// 匹配规则
// ^1第一位为一
// [345789]{1} 后接一位345789 的数字
// \\d \d的转义 表示数字 {9} 接9位
// $ 结束符
regRuler := "^1[3456789]{1}\\d{9}$"

// 正则调用规则
reg := regexp.MustCompile(regRuler)

// 返回 MatchString 是否匹配
return reg.MatchString(phone), phone

}

// 计算时间差(天数)
func SubDays(t1, t2 time.Time) (day int) {
day = int(t1.Sub(t2).Hours() / 24)
return
}

+ 107
- 0
routers/jwt.go View File

@@ -0,0 +1,107 @@
package routers

import (
"errors"
"fmt"
"time"

baapi "3g3e.com/rechy/ck.ba.api"
jwt "3g3e.com/rechy/ck.ba.api/jwt-v2"
"3g3e.com/zmy/ck.platform.shop/api"
"3g3e.com/zmy/ck.platform.shop/keys"
"github.com/gin-gonic/gin"
)

const (
JwtRealm = "CK"
JwtTokenHeadName = "ChangKe"
)

var identityKey = "id"

func getJwtAuth() (*jwt.GinJWTMiddleware, error) {
authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
Realm: JwtRealm,
SigningAlgorithm: baapi.JwtSigningAlgorithm,
Timeout: 30 * time.Minute,
MaxRefresh: 20 * time.Minute,
IdentityKey: identityKey,

// 从data一个用户里找个唯一值做token存储的id
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*baapi.ModAuthor); ok {
m := jwt.MapClaims{
"author": v.String(),
identityKey: fmt.Sprintf("%v-%v", v.Type, v.ID),
}
return m
}
return jwt.MapClaims{}
},

// 从token存储的id值, 再读取出用户的相关账户信息
IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
author, _ := claims["author"].(string)
return baapi.NewModAuthor([]byte(author))
},

// 登录时验证账号密码是否正常, 返回用户信息, err为nil就是登录成功
Authenticator: func(c *gin.Context) (interface{}, time.Duration, error) {
return nil, time.Hour * 24 * 3, errors.New("this is ba client")
},

// 这个方法就是认证当前页面是否有权操作
Authorizator: func(data interface{}, c *gin.Context) bool {
if v, ok := data.(*baapi.ModAuthor); ok {
return v.HasAuth(c.Request)
}

return false
},

// 无权操作, 格式化回复内容
Unauthorized: api.Unauthorized,

// 登录返回
LoginResponse: api.LoginResponse,

// 出返回
LogoutResponse: api.LogoutResponse,

ModifyTokenHeader: modifyTokenHeader,
KeyFuncVerify: keyFuncVerify,
KeyFuncSign: keyFuncSign,

TokenLookup: "header: Authorization, query: token, cookie: jwt",
TokenHeadName: JwtTokenHeadName,
TimeFunc: time.Now,
SendCookie: false,
})

if err != nil {
return nil, err
}

errInit := authMiddleware.MiddlewareInit()
if errInit != nil {
return nil, errInit
}

return authMiddleware, nil
}

func modifyTokenHeader(header map[string]interface{}) map[string]interface{} {
kmgr := keys.GetKeyManager()
return kmgr.ModifyTokenHeader(header)
}

func keyFuncVerify(header map[string]interface{}) (interface{}, error) {
kmgr := keys.GetKeyManager()
return kmgr.KeyFuncVerify(header)
}

func keyFuncSign(header map[string]interface{}) (interface{}, error) {
kmgr := keys.GetKeyManager()
return kmgr.KeyFuncSign(header)
}

+ 28
- 0
routers/pub_hub.go View File

@@ -0,0 +1,28 @@
package routers

import (
baapi "3g3e.com/rechy/ck.ba.api"
"3g3e.com/zmy/ck.platform.shop/api/pub"
"3g3e.com/zmy/ck.platform.shop/keys"
)

type PubHub struct {
}

func (pb *PubHub) Sign(isBA bool, moduleTo string, data string) (string, error) {
kmgr := keys.GetKeyManager()
return kmgr.Sign(isBA, moduleTo, data)
}

func (pb *PubHub) Verify(isBA bool, moduleFrom string, data string, sign string) error {
kmgr := keys.GetKeyManager()
return kmgr.Verify(isBA, moduleFrom, data, sign)
}

func (pb *PubHub) Exec(req *baapi.Command) (string, interface{}, interface{}) {
fn, ok := pub.FindHandler(req.Cmd)
if !ok {
return "error", "not support command", nil
}
return fn(req)
}

+ 89
- 0
routers/router.go View File

@@ -0,0 +1,89 @@
package routers

import (
"net/http"

"3g3e.com/ymdx/glog"
"3g3e.com/zmy/ck.platform.shop/api"
"3g3e.com/zmy/ck.platform.shop/api/pub"

"strings"

// "3g3e.com/zmy/ck.platform.shop/controllers"
baapi "3g3e.com/rechy/ck.ba.api"
"3g3e.com/ymdx/lib"
"3g3e.com/zmy/ck.platform.shop/keys"
"github.com/gin-gonic/gin"
)

const (
ApiPathBase = "/"
)

// Cors is 跨域
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {

if c.Request == nil || !strings.HasPrefix(c.Request.URL.Path, ApiPathBase) {
c.Next()
return
}

origin := c.Request.Header.Get("Origin") //请求头部
wh := c.Writer.Header()
if origin != "" {
wh.Set("Access-Control-Allow-Origin", origin)
} else {
wh.Set("Access-Control-Allow-Origin", "*")
}
wh.Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
wh.Set("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")
wh.Set("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") // 跨域关键设置 让浏览器可以解析
wh.Set("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
wh.Set("Access-Control-Allow-Credentials", "true") // 设置返回格式是json

//放行所有OPTIONS方法
if c.Request.Method == "OPTIONS" {
c.JSON(http.StatusOK, "Options Request!")
}

// 处理请求
c.Next()
}
}

func SetupRouter(router *gin.Engine) {
// store := cookie.NewStore([]byte("secret"))
// router.Use(Cors(), sessions.Sessions("mysession", store))
/*******初始化操作权限路由范围 ******/
baapi.InitBind(router, &PubHub{})
// 初始化密钥管理器
keys.GetKeyManager()

/**********************/
authMiddleware, err := getJwtAuth()
if err != nil {
glog.Fatalf("get jwt middleware err [%+v]", err)
return
}
apiGroup := router.Group("/api")
{
// 无权限验证
apiGroup.GET("/test", api.Test)
apiGroup.POST("/login", authMiddleware.LoginHandler)
apiGroup.POST("/upfile", pub.UpFile)
}
v1Master := router.Group(baapi.RouteApiMaster)
v1Master.Use(authMiddleware.MiddlewareFunc())
{
}
v1Enterprise := router.Group(baapi.RouteApiEnterprise)
v1Enterprise.Use(authMiddleware.MiddlewareFunc())
{
}
// 短信回调
router.StaticFS("/img", newStaticDir(("/img"), lib.EnsureDir("./img"), ""))
router.StaticFS("/fe", newStaticDir(("/fe"), lib.EnsureDir("./web/fe"), ""))
router.StaticFS("/adm", newStaticDir(("/adm"), lib.EnsureDir("./wwwx"), "index.html"))
router.GET("/", func(c *gin.Context) { c.Redirect(http.StatusFound, "/adm/") })
}

+ 72
- 0
routers/static_fs.go View File

@@ -0,0 +1,72 @@
package routers

import (
"errors"
"net/http"
"os"
"path"
"path/filepath"
"strings"
)

type staticDir struct {
prefix string
dir string
defaultIndex string
}

func newStaticDir(prefix, dir, defaultIndex string) *staticDir {
return &staticDir{
prefix: prefix,
dir: dir,
defaultIndex: defaultIndex,
}
}

func mapDirOpenError(originalErr error, name string) error {
if os.IsNotExist(originalErr) || os.IsPermission(originalErr) {
return originalErr
}

parts := strings.Split(name, string(filepath.Separator))
for i := range parts {
if parts[i] == "" {
continue
}
fi, err := os.Stat(strings.Join(parts[:i+1], string(filepath.Separator)))
if err != nil {
return originalErr
}
if !fi.IsDir() {
return os.ErrNotExist
}
}
return originalErr
}

func (d *staticDir) Handler(w http.ResponseWriter, r *http.Request) {
fileServer := http.StripPrefix(d.prefix, http.FileServer(d))
fileServer.ServeHTTP(w, r)
}

func (d *staticDir) Open(name string) (http.File, error) {
// 无扩展名请求定位至index.html
if d.defaultIndex != "" && name != "/" && path.Ext(name) == "" {
name = "/" + d.defaultIndex
}

if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
dir := d.dir
if dir == "" {
dir = "."
}
fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
f, err := os.Open(fullName)
// glog.Infoln(fullName)
if err != nil {
return nil, mapDirOpenError(err, fullName)
}
return f, nil
}

+ 129
- 0
service.go View File

@@ -0,0 +1,129 @@
package main

import (
"context"
"flag"
"fmt"
"log"
"os"
"path/filepath"

cfg "3g3e.com/ymdx/config"
"3g3e.com/ymdx/glog"
"3g3e.com/ymdx/lib"
"github.com/takama/daemon"
)

type exitApp struct {
ctx context.Context
exit context.CancelFunc
baseDir string
}

func imain(ctx context.Context, baseDir string) *exitApp {
app := &exitApp{
ctx: ctx,
baseDir: baseDir,
}

go app.main()

return app
}

func (app *exitApp) WillExit() error {
return nil
}

func (app *exitApp) SetExitCtrl(exit context.CancelFunc) {
app.exit = exit
}

// Service is
type Service struct {
daemon.Daemon
baseDir string
}

// Manage is
func (s *Service) Manage() (string, error) {

usage := "Usage: " + srvName + " install | remove | start | stop | status"

if len(os.Args) > 1 {
command := os.Args[1]
switch command {
case "install":
return s.Install()
case "remove":
return s.Remove()
case "start":
return s.Start()
case "stop":
return s.Stop()
case "status":
return s.Status()
default:
return usage, nil
}
}
/*************** CLI ***************/
cliLog := &log.Logger{}
glog.LoggerOutput(cliLog, &glog.LogBridgeOptions{
Prefix: "CLI",
BaseDepth: 3,
ReplaceMap: nil,
ShowHeaderTrace: true,
})
cli, ctx := lib.NewCLI(s.baseDir, cliLog)
defer cli.Shutdown()

/*************** 性能分析 ***************/
//go http.ListenAndServe(":6060", nil)

/*************** 核心 ***************/
// defer cfg.SaveConfig()
defer glog.Flush()

sp := imain(ctx, s.baseDir)
cli.Add(sp)

/************** CLI LOOP **************/
cli.Loop()
return "Daemon was killed", nil
}

func main() {
baseDir := ""
flag.StringVar(&baseDir, "dir", "./_data", "running in a directory")
flag.Parse()

baseDir = lib.EnsureDir(baseDir)
pathGLogDir := filepath.Join(baseDir, "glog")
pathLogDir := filepath.Join(baseDir, "log")

// 独立于baseDir
confDir := lib.EnsureDir("./conf")
pathConfig := filepath.Join(confDir, "default.conf")

// os.RemoveAll(pathPageDir)
// os.RemoveAll(pathCaptureDir)
// os.RemoveAll(pathGLogDir)

cfg.InitWithFile("json", pathConfig)
glog.DefaultInit(glog.OtherLog, true, pathGLogDir, pathLogDir)

/*************** Service ***************/
srv, err := daemon.New(srvName, srvDescription, daemon.SystemDaemon)
if err != nil {
glog.Errorln("Error: ", err)
os.Exit(1)
}
s := &Service{srv, baseDir}
status, err := s.Manage()
if err != nil {
glog.Errorln(status, "\nError: ", err)
os.Exit(1)
}
fmt.Println(status)
}

+ 59
- 0
test/main.go View File

@@ -0,0 +1,59 @@
package main

import (
"log"
"sync"
"time"
)

type TaskRow struct {
Id int
Phone string
}

func main() {

log.SetFlags(log.Flags() | log.Ldate)

ch := make(chan *TaskRow, 10000)
stopCh := make(chan interface{})
go func() {
for i := 0; i < 100000; i++ {
ch <- &TaskRow{Id: i, Phone: "139000000"}
}
}()

go func() {
time.Sleep(time.Second * 15)
close(stopCh)
}()

wg := &sync.WaitGroup{}

for i := 0; i < 20; i++ {
wg.Add(1)
go oneThread(i, wg, ch, stopCh)
}

log.Println("开始等待============", time.Now())
time.Sleep(time.Second * 2)
// wg.Wait()
log.Println("20 线程已经退出============", time.Now())

}

func oneThread(threadId int, wg *sync.WaitGroup, ch chan *TaskRow, stopCh chan interface{}) {
defer wg.Done()
for {
select {
case <-stopCh:
return
case task := <-ch:
// dealOneTask(task)
log.Println("工人ID:", threadId, "任务: ", task.Id)
// time.Sleep(time.Second * 10)

}
}

}

Loading…
Cancel
Save