dengbiao преди 1 месец
ревизия
4f5295a053
променени са 100 файла, в които са добавени 6725 реда и са изтрити 0 реда
  1. +46
    -0
      .gitignore
  2. +35
    -0
      Dockerfile
  3. +34
    -0
      Dockerfile-task
  4. +32
    -0
      Makefile
  5. +48
    -0
      README.md
  6. +44
    -0
      app/cfg/cfg_app.go
  7. +3
    -0
      app/cfg/cfg_cache_key.go
  8. +9
    -0
      app/cfg/init_cache.go
  9. +50
    -0
      app/cfg/init_cfg.go
  10. +20
    -0
      app/cfg/init_log.go
  11. +43
    -0
      app/cfg/init_task.go
  12. +44
    -0
      app/db/dao/admin_dao.go
  13. +25
    -0
      app/db/dao/admin_role_dao.go
  14. +5
    -0
      app/db/dao/permission_dao.go
  15. +8
    -0
      app/db/dao/permission_group_dao.go
  16. +5
    -0
      app/db/dao/permission_group_permission_dao.go
  17. +25
    -0
      app/db/dao/role_dao.go
  18. +11
    -0
      app/db/dao/role_permission_group_dao.go
  19. +112
    -0
      app/db/db.go
  20. +82
    -0
      app/db/db_sys_cfg.go
  21. +104
    -0
      app/db/dbs.go
  22. +194
    -0
      app/db/dbs_map.go
  23. +115
    -0
      app/db/implement/admin_implement.go
  24. +77
    -0
      app/db/implement/admin_role_implement.go
  25. +33
    -0
      app/db/implement/permission_group_implement.go
  26. +14
    -0
      app/db/implement/permission_group_permission_implement.go
  27. +14
    -0
      app/db/implement/permission_implement.go
  28. +73
    -0
      app/db/implement/role_implement.go
  29. +32
    -0
      app/db/implement/role_permission_group_implement.go
  30. +14
    -0
      app/db/model/admin.go
  31. +10
    -0
      app/db/model/admin_role.go
  32. +14
    -0
      app/db/model/db_mapping.go
  33. +10
    -0
      app/db/model/permission.go
  34. +11
    -0
      app/db/model/permission_group.go
  35. +9
    -0
      app/db/model/permission_group_permission.go
  36. +10
    -0
      app/db/model/role.go
  37. +9
    -0
      app/db/model/role_permission_group.go
  38. +7
    -0
      app/db/model/sys_cfg.go
  39. +5
    -0
      app/db/super/dao/medium_list_dao.go
  40. +14
    -0
      app/db/super/implement/medium_list_implement.go
  41. +25
    -0
      app/db/super/model/medium_list.go
  42. +236
    -0
      app/e/code.go
  43. +72
    -0
      app/e/error.go
  44. +110
    -0
      app/e/msg.go
  45. +8
    -0
      app/e/set_cache.go
  46. +37
    -0
      app/enum/enum_admin.go
  47. +19
    -0
      app/enum/enum_permission_group.go
  48. +19
    -0
      app/enum/enum_role.go
  49. +23
    -0
      app/hdl/hdl_demo.go
  50. +60
    -0
      app/hdl/hdl_login.go
  51. +649
    -0
      app/hdl/hdl_role.go
  52. +262
    -0
      app/lib/alipay/api.go
  53. +39
    -0
      app/lib/auth/auth.go
  54. +19
    -0
      app/lib/auth/base.go
  55. +16
    -0
      app/lib/qiniu/bucket_create.go
  56. +18
    -0
      app/lib/qiniu/bucket_delete.go
  57. +18
    -0
      app/lib/qiniu/bucket_get_domain.go
  58. +22
    -0
      app/lib/qiniu/init.go
  59. +55
    -0
      app/lib/qiniu/req_img_upload.go
  60. +33
    -0
      app/lib/validate/validate_comm.go
  61. +0
    -0
     
  62. +305
    -0
      app/lib/wxpay/api.go
  63. +18
    -0
      app/md/app_redis_key.go
  64. +10
    -0
      app/md/md_login.go
  65. +91
    -0
      app/md/md_role.go
  66. +5
    -0
      app/md/md_white_uri.go
  67. +42
    -0
      app/md/split_db.go
  68. +31
    -0
      app/mw/mw_access_log.go
  69. +26
    -0
      app/mw/mw_auth.go
  70. +30
    -0
      app/mw/mw_breaker.go
  71. +17
    -0
      app/mw/mw_change_header.go
  72. +34
    -0
      app/mw/mw_check_sign.go
  73. +29
    -0
      app/mw/mw_cors.go
  74. +136
    -0
      app/mw/mw_csrf.go
  75. +30
    -0
      app/mw/mw_db.go
  76. +58
    -0
      app/mw/mw_limiter.go
  77. +57
    -0
      app/mw/mw_recovery.go
  78. +74
    -0
      app/router/router.go
  79. +31
    -0
      app/svc/svc_admin.go
  80. +55
    -0
      app/svc/svc_auth.go
  81. +11
    -0
      app/svc/svc_db.go
  82. +33
    -0
      app/svc/svc_login.go
  83. +58
    -0
      app/svc/svc_master.go
  84. +85
    -0
      app/svc/svc_redis_mutex_lock.go
  85. +184
    -0
      app/svc/svc_role.go
  86. +100
    -0
      app/svc/svc_sys_cfg_get.go
  87. +26
    -0
      app/svc/svc_validate_common.go
  88. +117
    -0
      app/task/init.go
  89. +5
    -0
      app/task/md/cron_key.go
  90. +64
    -0
      app/task/svc/svc_cancel_order.go
  91. +23
    -0
      app/task/task_cancel_order.go
  92. +123
    -0
      app/utils/aes.go
  93. +95
    -0
      app/utils/base64.go
  94. +26
    -0
      app/utils/boolean.go
  95. +421
    -0
      app/utils/cache/base.go
  96. +107
    -0
      app/utils/cache/cache/cache.go
  97. +86
    -0
      app/utils/cache/cache/conv.go
  98. +241
    -0
      app/utils/cache/cache/file.go
  99. +239
    -0
      app/utils/cache/cache/memory.go
  100. +412
    -0
      app/utils/cache/redis.go

+ 46
- 0
.gitignore Целия файл

@@ -0,0 +1,46 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.idea
.vscode
*.log
.DS_Store
Thumbs.db
*.swp
*.swn
*.swo
*.swm
*.7z
*.zip
*.rar
*.tar
*.tar.gz
go.sum
/etc/cfg.yaml
images
test/test.json
etc/cfg.yml
t.json
t1.json
t2.json
t3.json
t.go
wait-for-it.sh
test.go
xorm
test.csv
nginx.conf
.devcontainer
.devcontainer/Dockerfile
.devcontainer/sources.list
/t1.go
/tmp/*
.idea/*
/.idea/modules.xml

+ 35
- 0
Dockerfile Целия файл

@@ -0,0 +1,35 @@
# 多重构建,减少镜像大小
# 构建:使用golang:1.15版本
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build


# 容器环境变量添加,会覆盖默认的变量值
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
ENV TZ="Asia/Shanghai"
# 设置工作区
WORKDIR /go/release

# 把全部文件添加到/go/release目录
ADD . .

# 编译:把main.go编译成可执行的二进制文件,命名为zyos
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -tags netgo -ldflags="-s -w" -installsuffix cgo -o zyos main.go

FROM ubuntu:xenial as prod
LABEL maintainer="wuhanqin"
ENV TZ="Asia/Shanghai"

COPY static/html static/html
# 时区纠正
RUN rm -f /etc/localtime \
&& ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
# 在build阶段复制可执行的go二进制文件app
COPY --from=build /go/release/zyos ./zyos

COPY --from=build /go/release/etc/cfg.yml /var/zyos/cfg.yml

# 启动服务
CMD ["./zyos","-c","/var/zyos/cfg.yml"]


+ 34
- 0
Dockerfile-task Целия файл

@@ -0,0 +1,34 @@
# 多重构建,减少镜像大小
# 构建:使用golang:1.15版本
FROM registry.cn-shenzhen.aliyuncs.com/fnuoos-prd/golang:1.18.4 as build

# 容器环境变量添加,会覆盖默认的变量值
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
ENV TZ="Asia/Shanghai"
# 设置工作区
WORKDIR /go/release

# 把全部文件添加到/go/release目录
ADD . .

# 编译:把main.go编译成可执行的二进制文件,命名为zyos
RUN GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -tags netgo -ldflags="-s -w" -installsuffix cgo -o zyos_mall_task cmd/task/main.go

FROM ubuntu:xenial as prod
LABEL maintainer="wuhanqin"
ENV TZ="Asia/Shanghai"

COPY static/html static/html
# 时区纠正
RUN rm -f /etc/localtime \
&& ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone
# 在build阶段复制可执行的go二进制文件app
COPY --from=build /go/release/zyos_mall_task ./zyos_mall_task

COPY --from=build /go/release/etc/task.yml /var/zyos/task.yml

# 启动服务
CMD ["./zyos_mall_task","-c","/var/zyos/task.yml"]


+ 32
- 0
Makefile Целия файл

@@ -0,0 +1,32 @@
.PHONY: build clean tool lint help

APP=applet

all: build

build:
go build -o ./bin/$(APP) ./cmd/main.go

lite:
go build -ldflags "-s -w" -o ./bin/$(APP) ./cmd/main.go

install:
#@go build -v .
go install ./cmd/...

tool:
go vet ./...; true
gofmt -w .

lint:
golint ./...

clean:
rm -rf go-gin-example
go clean -i .

help:
@echo "make: compile packages and dependencies"
@echo "make tool: run specified go tool"
@echo "make lint: golint ./..."
@echo "make clean: remove object files and cached files"

+ 48
- 0
README.md Целия файл

@@ -0,0 +1,48 @@
# 广告站长平台

## 要看 nginx.conf 和 wap conf

## 层级介绍

- hdl 做接收数据的报错, 数据校验
- svc 做数据处理的报错, 数据转换
- lib 只抛出错误给hdl或者svc进行处理, 不做数据校验
- db 可以处理db错误,其它错误返回给svc进行处理
- mw 中间件
- md 结构体

#### 介绍
基于gin的接口小程序

#### 软件架构

软件架构说明

#### 安装教程

1. xxxx
2. xxxx
3. xxxx

#### 使用说明

1. xxxx
2. xxxx
3. xxxx

#### 参与贡献

1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request

## swagger

```
// 参考:https://segmentfault.com/a/1190000013808421
// 安装命令行
go get -u github.com/swaggo/swag/cmd/swag
// 生成
swag init
```

+ 44
- 0
app/cfg/cfg_app.go Целия файл

@@ -0,0 +1,44 @@
package cfg

import (
"time"
)

type Config struct {
Debug bool `yaml:"debug"`
Prd bool `yaml:"prd"`
CurlDebug bool `yaml:"curldebug"`
SrvAddr string `yaml:"srv_addr"`
RedisAddr string `yaml:"redis_addr"`
DB DBCfg `yaml:"db"`
Log LogCfg `yaml:"log"`
Local bool
}

// 数据库配置结构体
type DBCfg struct {
Host string `yaml:"host"` //ip及端口
Name string `yaml:"name"` //库名
User string `yaml:"user"` //用户
Psw string `yaml:"psw"` //密码
ShowLog bool `yaml:"show_log"` //是否显示SQL语句
MaxLifetime time.Duration `yaml:"max_lifetime"`
MaxOpenConns int `yaml:"max_open_conns"`
MaxIdleConns int `yaml:"max_idle_conns"`
Path string `yaml:"path"` //日志文件存放路径
}

// 日志配置结构体
type LogCfg struct {
AppName string `yaml:"app_name" `
Level string `yaml:"level"`
IsStdOut bool `yaml:"is_stdout"`
TimeFormat string `yaml:"time_format"` // second, milli, nano, standard, iso,
Encoding string `yaml:"encoding"` // console, json

IsFileOut bool `yaml:"is_file_out"`
FileDir string `yaml:"file_dir"`
FileName string `yaml:"file_name"`
FileMaxSize int `yaml:"file_max_size"`
FileMaxAge int `yaml:"file_max_age"`
}

+ 3
- 0
app/cfg/cfg_cache_key.go Целия файл

@@ -0,0 +1,3 @@
package cfg

// 统一管理缓存

+ 9
- 0
app/cfg/init_cache.go Целия файл

@@ -0,0 +1,9 @@
package cfg

import (
"applet/app/utils/cache"
)

func InitCache() {
cache.NewRedis(RedisAddr)
}

+ 50
- 0
app/cfg/init_cfg.go Целия файл

@@ -0,0 +1,50 @@
package cfg

import (
"flag"
"io/ioutil"

"gopkg.in/yaml.v2"
)

// 配置文件数据,全局变量
var (
Debug bool
Prd bool
CurlDebug bool
SrvAddr string
RedisAddr string
DB *DBCfg
Log *LogCfg
Local bool
)

// 初始化配置文件,将cfg.yml读入到内存
func InitCfg() {
//用指定的名称、默认值、使用信息注册一个string类型flag。
path := flag.String("c", "etc/cfg.yml", "config file")
//解析命令行参数写入注册的flag里。
//解析之后,flag的值可以直接使用。
flag.Parse()
var (
c []byte
err error
conf *Config
)
if c, err = ioutil.ReadFile(*path); err != nil {
panic(err)
}
//yaml.Unmarshal反序列化映射到Config
if err = yaml.Unmarshal(c, &conf); err != nil {
panic(err)
}
//数据读入内存
Prd = conf.Prd
Debug = conf.Debug
Local = conf.Local
CurlDebug = conf.CurlDebug
DB = &conf.DB
Log = &conf.Log
RedisAddr = conf.RedisAddr
SrvAddr = conf.SrvAddr
}

+ 20
- 0
app/cfg/init_log.go Целия файл

@@ -0,0 +1,20 @@
package cfg

import "applet/app/utils/logx"

func InitLog() {
logx.InitDefaultLogger(&logx.LogConfig{
AppName: Log.AppName,
Level: Log.Level,
StacktraceLevel: "error",
IsStdOut: Log.IsStdOut,
TimeFormat: Log.TimeFormat,
Encoding: Log.Encoding,
IsFileOut: Log.IsFileOut,
FileDir: Log.FileDir,
FileName: Log.FileName,
FileMaxSize: Log.FileMaxSize,
FileMaxAge: Log.FileMaxAge,
Skip: 2,
})
}

+ 43
- 0
app/cfg/init_task.go Целия файл

@@ -0,0 +1,43 @@
package cfg

import (
"flag"
"io/ioutil"

"gopkg.in/yaml.v2"

mc "applet/app/utils/cache/cache"
"applet/app/utils/logx"
)

func InitTaskCfg() {
path := flag.String("c", "etc/task.yml", "config file")
flag.Parse()
var (
c []byte
err error
conf *Config
)
if c, err = ioutil.ReadFile(*path); err != nil {
panic(err)
}
if err = yaml.Unmarshal(c, &conf); err != nil {
panic(err)
}
Prd = conf.Prd
Debug = conf.Debug
DB = &conf.DB
Log = &conf.Log
RedisAddr = conf.RedisAddr
Local = conf.Local
}

var MemCache mc.Cache

func InitMemCache() {
var err error
MemCache, err = mc.NewCache("memory", `{"interval":60}`)
if err != nil {
logx.Fatal(err.Error())
}
}

+ 44
- 0
app/db/dao/admin_dao.go Целия файл

@@ -0,0 +1,44 @@
package dao

import (
"applet/app/db/model"
"xorm.io/xorm"
)

type AdminDao interface {
CreateAdminId() (admId int, err error)
AdminDeleteBySession(session *xorm.Session, admId interface{}) (int64, error)
GetAdmin(id int) (m *model.Admin, err error)
GetAdminByUserName(userName string) (m *model.Admin, err error)
GetAdminRolePermission(admId int) (list []*AdminRolePermission, total int64, err error)
FindAdmin(username string, state, page, limit int) (list []model.Admin, total int64, err error)
UpdateAdmin(m *model.Admin, columns ...string) (int64, error)
AdminInsert(m *model.Admin) (int64, error)
FindAdminRolePermissionGroup(admId int) (list []*AdminRolePermissionGroup, total int64, err error)
}

type AdminRolePermissionGroup struct {
model.Admin `xorm:"extends"`
model.AdminRole `xorm:"extends"`
model.Role `xorm:"extends"`
model.RolePermissionGroup `xorm:"extends"`
model.PermissionGroup `xorm:"extends"`
}

func (AdminRolePermissionGroup) TableName() string {
return "admin"
}

type AdminRolePermission struct {
model.Admin `xorm:"extends"`
model.AdminRole `xorm:"extends"`
model.Role `xorm:"extends"`
model.RolePermissionGroup `xorm:"extends"`
model.PermissionGroup `xorm:"extends"`
model.PermissionGroupPermission `xorm:"extends"`
model.Permission `xorm:"extends"`
}

func (AdminRolePermission) TableName() string {
return "admin"
}

+ 25
- 0
app/db/dao/admin_role_dao.go Целия файл

@@ -0,0 +1,25 @@
package dao

import (
"applet/app/db/model"
"xorm.io/xorm"
)

type AdminRoleDao interface {
FindAdminRole(id int) (*[]model.AdminRole, error)
FindAdminRoleByRoleId(id int) (*[]model.AdminRole, error)
AdminDeleteBySessionForAdmId(session *xorm.Session, admId interface{}) (int64, error)
GetAdminRoleByRole(id int) (m *model.AdminRole, err error)
AdminRoleDeleteForRoleBySession(session *xorm.Session, roleId interface{}) (int64, error)
AdminRoleDeleteBySession(session *xorm.Session, id interface{}) (int64, error)
BatchAddAdminRoleBySession(session *xorm.Session, mm []*model.AdminRole) (int64, error)
}

type AdminRole struct {
model.Admin `xorm:"extends"`
model.AdminRole `xorm:"extends"`
}

func (AdminRole) TableName() string {
return "admin_role"
}

+ 5
- 0
app/db/dao/permission_dao.go Целия файл

@@ -0,0 +1,5 @@
package dao

type PermissionDao interface {
//TODO:: You can add specific method definitions here
}

+ 8
- 0
app/db/dao/permission_group_dao.go Целия файл

@@ -0,0 +1,8 @@
package dao

import "applet/app/db/model"

type PermissionGroupDao interface {
FindPermissionGroup() (*[]model.PermissionGroup, error)
FindPermissionGroupV2() (*[]model.PermissionGroup, error)
}

+ 5
- 0
app/db/dao/permission_group_permission_dao.go Целия файл

@@ -0,0 +1,5 @@
package dao

type PermissionGroupPermissionDao interface {
//TODO:: You can add specific method definitions here
}

+ 25
- 0
app/db/dao/role_dao.go Целия файл

@@ -0,0 +1,25 @@
package dao

import (
"applet/app/db/model"
"xorm.io/xorm"
)

type RoleDao interface {
FindRole() (*[]model.Role, error)
GetRole() (m *model.Role, err error)
UpdateRole(m *model.Role, columns ...string) (int64, error)
RoleDeleteBySession(session *xorm.Session, id interface{}) (int64, error)
RoleInsert(m *model.Role) (int, error)
FindPermissionGroupByRole(roleId int) (list []*RolePermissionGroup, total int64, err error)
}

type RolePermissionGroup struct {
model.Role `xorm:"extends"`
model.RolePermissionGroup `xorm:"extends"`
model.PermissionGroup `xorm:"extends"`
}

func (RolePermissionGroup) TableName() string {
return "role"
}

+ 11
- 0
app/db/dao/role_permission_group_dao.go Целия файл

@@ -0,0 +1,11 @@
package dao

import (
"applet/app/db/model"
"xorm.io/xorm"
)

type RolePermissionGroupDao interface {
RolePermissionGroupDeleteForRoleBySession(session *xorm.Session, roleId interface{}) (int64, error)
BatchAddRolePermissionGroupBySession(session *xorm.Session, mm []*model.RolePermissionGroup) (int64, error)
}

+ 112
- 0
app/db/db.go Целия файл

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

import (
"database/sql"
"fmt"
"os"
"time"

_ "github.com/go-sql-driver/mysql" //必须导入mysql驱动,否则会panic
"xorm.io/xorm"
"xorm.io/xorm/log"

"applet/app/cfg"
"applet/app/utils/logx"
)

var Db *xorm.Engine

//根据DB配置文件初始化数据库
func InitDB(c *cfg.DBCfg) error {
var (
err error
f *os.File
)
//创建Orm引擎
if Db, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", c.User, c.Psw, c.Host, c.Name)); err != nil {
return err
}
Db.SetConnMaxLifetime(c.MaxLifetime * time.Second) //设置最长连接时间
Db.SetMaxOpenConns(c.MaxOpenConns) //设置最大打开连接数
Db.SetMaxIdleConns(c.MaxIdleConns) //设置连接池的空闲数大小
if err = Db.Ping(); err != nil { //尝试ping数据库
return err
}
if c.ShowLog { //根据配置文件设置日志
Db.ShowSQL(true) //设置是否打印sql
Db.Logger().SetLevel(0) //设置日志等级
//修改日志文件存放路径文件名是%s.log
path := fmt.Sprintf(c.Path, c.Name)
f, err = os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
os.RemoveAll(c.Path)
if f, err = os.OpenFile(c.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777); err != nil {
return err
}
}
logger := log.NewSimpleLogger(f)
logger.ShowSQL(true)
Db.SetLogger(logger)
}
return nil
}

/********************************************* 公用方法 *********************************************/

// 数据批量插入
func DbInsertBatch(Db *xorm.Engine, m ...interface{}) error {
if len(m) == 0 {
return nil
}
id, err := Db.Insert(m...)
if id == 0 || err != nil {
return logx.Warn("cannot insert data :", err)
}
return nil
}

// QueryNativeString 查询原生sql
func QueryNativeString(Db *xorm.Engine, sql string, args ...interface{}) ([]map[string]string, error) {
results, err := Db.SQL(sql, args...).QueryString()
return results, err
}

// UpdateComm common update
func UpdateComm(Db *xorm.Engine, id interface{}, model interface{}) (int64, error) {
row, err := Db.ID(id).Update(model)
return row, err
}

// InsertComm common insert
func InsertComm(Db *xorm.Engine, model interface{}) (int64, error) {
row, err := Db.InsertOne(model)
return row, err
}

// ExecuteOriginalSql 执行原生sql
func ExecuteOriginalSql(session *xorm.Session, sql string) (sql.Result, error) {
result, err := session.Exec(sql)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
return result, nil
}

// GetComm
// payload *model
// return *model,has,err
func GetComm(Db *xorm.Engine, model interface{}) (interface{}, bool, error) {
has, err := Db.Get(model)
if err != nil {
_ = logx.Warn(err)
return nil, false, err
}
return model, has, nil
}

// InsertCommWithSession common insert
func InsertCommWithSession(session *xorm.Session, model interface{}) (int64, error) {
row, err := session.InsertOne(model)
return row, err
}

+ 82
- 0
app/db/db_sys_cfg.go Целия файл

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

import (
"applet/app/db/model"
"applet/app/md"
"applet/app/utils/cache"
"applet/app/utils/logx"
"fmt"
"xorm.io/xorm"
)

// 系统配置get
func SysCfgGetAll(Db *xorm.Engine) (*[]model.SysCfg, error) {
var cfgList []model.SysCfg
if err := Db.Cols("key,val,memo").Find(&cfgList); err != nil {
return nil, logx.Error(err)
}
return &cfgList, nil
}

// 获取一条记录
func SysCfgGetOne(Db *xorm.Engine, key string) (*model.SysCfg, error) {
var cfgList model.SysCfg
if has, err := Db.Where("`key`=?", key).Get(&cfgList); err != nil || has == false {
return nil, logx.Error(err)
}
return &cfgList, nil
}

// 返回最后插入id
func SysCfgInsert(Db *xorm.Engine, key, val, memo string) bool {
cfg := model.SysCfg{Key: key, Val: val, Memo: memo}
_, err := Db.InsertOne(&cfg)
if err != nil {
logx.Error(err)
return false
}
return true
}

func SysCfgUpdate(Db *xorm.Engine, key, val, memo string) bool {
cfg := model.SysCfg{Key: key, Val: val, Memo: memo}
_, err := Db.Where("`key`=?", key).Cols("val,memo").Update(&cfg)
if err != nil {
logx.Error(err)
return false
}
return true
}
func SysCfgGetWithDb(eg *xorm.Engine, masterId string, HKey string) string {
cacheKey := fmt.Sprintf(md.AppCfgCacheKey, masterId) + HKey
get, err := cache.GetString(cacheKey)
if err != nil || get == "" {
cfg, err := SysCfgGetOne(eg, HKey)
if err != nil || cfg == nil {
_ = logx.Error(err)
return ""
}

// key是否存在
cacheKeyExist := false
if cache.Exists(cacheKey) {
cacheKeyExist = true
}

// 设置缓存
_, err = cache.SetEx(cacheKey, cfg.Val, 30)
if err != nil {
_ = logx.Error(err)
return ""
}
if !cacheKeyExist { // 如果是首次设置 设置过期时间
_, err := cache.Expire(cacheKey, md.CfgCacheTime)
if err != nil {
_ = logx.Error(err)
return ""
}
}
return cfg.Val
}
return get
}

+ 104
- 0
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)
}
}
}

}

+ 194
- 0
app/db/dbs_map.go Целия файл

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

import (
"errors"
"fmt"

"xorm.io/xorm"

"applet/app/cfg"
"applet/app/db/model"
"applet/app/utils/logx"
)

func MapBaseExists() (bool, error) {
return Db.IsTableExist("db_mapping")
}

func InitMapDbs(c *cfg.DBCfg, prd bool) {
var tables *[]model.DbMapping
exists, err := MapBaseExists()
if !exists || err != nil {
logx.Fatalf("db_mapping not exists : %v", err)
}
// tables := MapAllDatabases(debug)
if prd {
tables = GetAllDatabasePrd() //debug 获取生产
} else {
tables = GetAllDatabaseDev() //debug 获取开发
}

if tables == nil {
logx.Fatal("no database tables data")
}
var e *xorm.Engine
DBs = map[string]*xorm.Engine{}
for _, v := range *tables {
if v.DbName != "" && v.DeletedAt == 0 && v.DbName != c.Name {
dbCfg := cfg.DBCfg{
Name: v.DbName,
ShowLog: c.ShowLog,
MaxLifetime: c.MaxLifetime,
MaxOpenConns: c.MaxOpenConns,
MaxIdleConns: c.MaxIdleConns,
Path: fmt.Sprintf(c.Path, v.DbName),
}
if v.DbHost != "" && v.DbUsername != "" && v.DbPassword != "" {
dbCfg.Host = v.DbHost
dbCfg.User = v.DbUsername
dbCfg.Psw = v.DbPassword
} else {
dbCfg.Host = c.Host
dbCfg.User = c.User
dbCfg.Psw = c.Psw
}
e, err = NewDB(&dbCfg)
if err != nil || e == nil {
logx.Warnf("db engine can't create, please check config, params[host:%s, user:%s, psw: %s, name: %s], err: %v", dbCfg.Host, dbCfg.User, dbCfg.Psw, dbCfg.Name, err)
} else {
DBs[v.DbMasterId] = e
}
}
}
}

func MapAllDatabases(debug bool) *[]model.DbMapping {
sql := "`db_name` != ?"
if debug {
sql = "`db_name` = ?"
}
var m []model.DbMapping
if err := Db.Where(sql, cfg.DB.Name).Find(&m); err != nil || len(m) == 0 {
logx.Warn(err)
return nil
}
return &m
}

// GetAllDatabasePrd is 获取生成库 所有db 除了 deleted_at = 1 的
func GetAllDatabasePrd() *[]model.DbMapping {
var m []model.DbMapping
if err := Db.Where("deleted_at != ? AND is_dev = '0' ", 1).Find(&m); err != nil || len(m) == 0 {
logx.Warn(err)
return nil
}
return &m
}

// GetAllDatabaseDev is 获取开发库 所有db 除了 deleted_at = 1 的
func GetAllDatabaseDev() *[]model.DbMapping {
var m []model.DbMapping
var err error
fmt.Println("cfg.Local is: ", cfg.Local)
if cfg.Local { // 本地调试 加快速度
fmt.Println("notice:LOCAL TEST, only masterId:** 123456 ** available!")
err = Db.Where("deleted_at != ? AND is_dev = '1' AND db_master_id=?", 1, 123456).Find(&m)
} else {
err = Db.Where("deleted_at != ? AND is_dev = '1' ", 1).Find(&m)
}

//err := Db.Where("deleted_at != ? AND is_dev = '1' and db_master_id='123456'", 1).Find(&m)
if err != nil || len(m) == 0 {
logx.Warn(err)
return nil
}
return &m
}

//GetDatabaseByMasterID is 根据站长id 获取对应的的数据库信息
func GetDatabaseByMasterID(Db *xorm.Engine, id string) (*model.DbMapping, error) {
var m model.DbMapping
has, err := Db.Where("db_master_id=?", id).Get(&m)
if !has {
return nil, errors.New("Not Found DB data by " + id)
}
if err != nil {
return nil, err
}
if m.DbHost == "" {
m.DbHost = cfg.DB.Host
m.DbUsername = cfg.DB.User
m.DbPassword = cfg.DB.Psw
}
return &m, nil
}

//SessionGetDatabaseByMasterID is 根据站长id 获取对应的的数据库信息
func SessionGetDatabaseByMasterID(Db *xorm.Session, id string) (*model.DbMapping, error) {
var m model.DbMapping
has, err := Db.Where("db_master_id=?", id).Get(&m)
if !has {
return nil, errors.New("Not Found DB data by " + id)
}
if err != nil {
return nil, err
}
if m.DbHost == "" {
m.DbHost = cfg.DB.Host
m.DbName = cfg.DB.Name
m.DbUsername = cfg.DB.User
m.DbPassword = cfg.DB.Psw
}
return &m, nil
}

// 获取自动任务队列
func MapCrontabCfg(eg *xorm.Engine) *[]model.SysCfg {
var c []model.SysCfg
// 数据库查询如果有下划线会认为是一个任意字符
if err := eg.Where("`key` LIKE 'mall_cron\\_%' AND val != ''").Cols("`key`,`val`").Find(&c); err != nil || len(c) == 0 {
logx.Warn(err)
return nil
}
return &c
}

// 获取官方域名
func GetOfficialDomainInfoByType(Db *xorm.Engine, masterId, key string) (string, error) {
type SysCfg struct {
K string
V string
Memo string
}
var domainBase SysCfg

has, err := Db.Where("k=?", "domain_base").Get(&domainBase)
if err != nil {
return "", err
}
if has == false {
return "", errors.New("can not find key by : domain_base")
}

if key == "wap" {
return "h5." + masterId + "." + domainBase.V, nil
}

if key == "api" {
var apiDomain SysCfg
has, err = Db.Where("k=?", "domain_api_base").Get(&apiDomain)
if err != nil {
return "", err
}
if has == false {
return "", errors.New("can not find key by : domain_api_base")
}
return apiDomain.V, nil
}

if key == "admin" {
return "admin." + masterId + "." + domainBase.V, nil
}
// 默认返回H5的
return "h5." + masterId + "." + domainBase.V, nil
}

+ 115
- 0
app/db/implement/admin_implement.go Целия файл

@@ -0,0 +1,115 @@
package implement

import (
"applet/app/db/dao"
"applet/app/db/model"
"applet/app/utils/logx"
"reflect"
"xorm.io/xorm"
)

func NewAdminDb(engine *xorm.Engine) dao.AdminDao {
return &AdminDb{Db: engine}
}

type AdminDb struct {
Db *xorm.Engine
}

func (a AdminDb) CreateAdminId() (admId int, err error) {
m := new(model.Admin)
has, err := a.Db.Desc("adm_id").Get(m)
if err != nil {
return 0, logx.Error(err)
}
if has == false {
return 0, nil
}
admId = m.AdmId + 1
return admId, nil
}

func (a AdminDb) AdminDeleteBySession(session *xorm.Session, admId interface{}) (int64, error) {
if reflect.TypeOf(admId).Kind() == reflect.Slice {
return session.In("adm_id", admId).Delete(model.Admin{})
} else {
return session.Where("id = ?", admId).Delete(model.Admin{})
}
}

func (a AdminDb) GetAdmin(id int) (m *model.Admin, err error) {
m = new(model.Admin)
has, err := a.Db.Where("adm_id =?", id).Get(m)
if err != nil {
return nil, logx.Error(err)
}
if has == false {
return nil, nil
}
return m, nil
}

func (a AdminDb) GetAdminByUserName(userName string) (m *model.Admin, err error) {
m = new(model.Admin)
has, err := a.Db.Where("username =?", userName).Get(m)
if err != nil {
return nil, logx.Error(err)
}
if has == false {
return nil, nil
}
return m, nil
}

func (a AdminDb) GetAdminRolePermission(admId int) (list []*dao.AdminRolePermission, total int64, err error) {
total, err = a.Db.Where("admin.adm_id =?", admId).
Join("LEFT", "admin_role", "admin.adm_id = admin_role.adm_id").
Join("LEFT", "role", "admin_role.role_id = role.id").
Join("LEFT", "role_permission_group", "role.id = role_permission_group.role_id").
Join("LEFT", "permission_group", "role_permission_group.group_id = permission_group.id").
Join("LEFT", "permission_group_permission", "permission_group.id = permission_group_permission.group_id").
Join("LEFT", "permission", "permission_group_permission.permission_id = permission.id").
FindAndCount(&list)
return
}

func (a AdminDb) FindAdmin(username string, state, page, limit int) (list []model.Admin, total int64, err error) {
sess := a.Db.Desc("adm_id").Limit(limit, (page-1)*limit)
if username != "" {
sess.And("username like ?", "%"+username+"%")
}
if state != 0 {
sess.And("state = ?", state)
}
total, err = sess.FindAndCount(&list)
if err != nil {
return nil, 0, err
}
return
}

func (a AdminDb) UpdateAdmin(m *model.Admin, columns ...string) (int64, error) {
affected, err := a.Db.Where("adm_id =?", m.AdmId).Cols(columns...).Update(m)
if err != nil {
return 0, err
}
return affected, nil
}

func (a AdminDb) AdminInsert(m *model.Admin) (int64, error) {
insertAffected, err := a.Db.InsertOne(m)
if err != nil {
return 0, err
}
return insertAffected, nil
}

func (a AdminDb) FindAdminRolePermissionGroup(admId int) (list []*dao.AdminRolePermissionGroup, total int64, err error) {
total, err = a.Db.Where("admin.adm_id =?", admId).
Join("LEFT", "admin_role", "admin.adm_id = admin_role.adm_id").
Join("LEFT", "role", "admin_role.role_id = role.id").
//Join("LEFT", "role_permission_group", "role.id = role_permission_group.role_id").
//Join("LEFT", "permission_group", "role_permission_group.group_id = permission_group.id").
FindAndCount(&list)
return
}

+ 77
- 0
app/db/implement/admin_role_implement.go Целия файл

@@ -0,0 +1,77 @@
package implement

import (
"applet/app/db/dao"
"applet/app/db/model"
"applet/app/utils/logx"
"reflect"
"xorm.io/xorm"
)

func NewAdminRoleDb(engine *xorm.Engine) dao.AdminRoleDao {
return &AdminRoleDb{Db: engine}
}

type AdminRoleDb struct {
Db *xorm.Engine
}

func (a AdminRoleDb) FindAdminRole(id int) (*[]model.AdminRole, error) {
var m []model.AdminRole
if err := a.Db.Where("adm_id =?", id).Find(&m); err != nil {
return nil, logx.Error(err)
}
return &m, nil
}

func (a AdminRoleDb) FindAdminRoleByRoleId(id int) (*[]model.AdminRole, error) {
var m []model.AdminRole
if err := a.Db.Where("role_id =?", id).Find(&m); err != nil {
return nil, logx.Error(err)
}
return &m, nil
}

func (a AdminRoleDb) AdminDeleteBySessionForAdmId(session *xorm.Session, admId interface{}) (int64, error) {
if reflect.TypeOf(admId).Kind() == reflect.Slice {
return session.In("adm_id", admId).Delete(model.AdminRole{})
} else {
return session.Where("adm_id = ?", admId).Delete(model.AdminRole{})
}
}

func (a AdminRoleDb) GetAdminRoleByRole(id int) (m *model.AdminRole, err error) {
m = new(model.AdminRole)
has, err := a.Db.Where("role_id =?", id).Get(m)
if err != nil {
return nil, logx.Error(err)
}
if has == false {
return nil, nil
}
return m, nil
}

func (a AdminRoleDb) AdminRoleDeleteForRoleBySession(session *xorm.Session, roleId interface{}) (int64, error) {
if reflect.TypeOf(roleId).Kind() == reflect.Slice {
return session.In("role_id", roleId).Delete(model.AdminRole{})
} else {
return session.Where("role_id = ?", roleId).Delete(model.AdminRole{})
}
}

func (a AdminRoleDb) AdminRoleDeleteBySession(session *xorm.Session, id interface{}) (int64, error) {
if reflect.TypeOf(id).Kind() == reflect.Slice {
return session.In("id", id).Delete(model.AdminRole{})
} else {
return session.Where("adm_id = ?", id).Delete(model.AdminRole{})
}
}

func (a AdminRoleDb) BatchAddAdminRoleBySession(session *xorm.Session, mm []*model.AdminRole) (int64, error) {
affected, err := session.Insert(mm)
if err != nil {
return 0, err
}
return affected, nil
}

+ 33
- 0
app/db/implement/permission_group_implement.go Целия файл

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

import (
"applet/app/db/dao"
"applet/app/db/model"
"applet/app/enum"
"applet/app/utils/logx"
"xorm.io/xorm"
)

func NewPermissionGroupDb(engine *xorm.Engine) dao.PermissionGroupDao {
return &PermissionGroupDb{Db: engine}
}

type PermissionGroupDb struct {
Db *xorm.Engine
}

func (p PermissionGroupDb) FindPermissionGroup() (*[]model.PermissionGroup, error) {
var m []model.PermissionGroup
if err := p.Db.Where("state = ?", enum.PermissionGroupStateForNormal).Asc("id").Find(&m); err != nil {
return nil, logx.Error(err)
}
return &m, nil
}

func (p PermissionGroupDb) FindPermissionGroupV2() (*[]model.PermissionGroup, error) {
var m []model.PermissionGroup
if err := p.Db.Asc("id").Find(&m); err != nil {
return nil, logx.Error(err)
}
return &m, nil
}

+ 14
- 0
app/db/implement/permission_group_permission_implement.go Целия файл

@@ -0,0 +1,14 @@
package implement

import (
"applet/app/db/dao"
"xorm.io/xorm"
)

type PermissionGroupPermissionDb struct {
Db *xorm.Engine
}

func NewPermissionGroupPermissionDb(engine *xorm.Engine) dao.PermissionGroupPermissionDao {
return &PermissionGroupPermissionDb{Db: engine}
}

+ 14
- 0
app/db/implement/permission_implement.go Целия файл

@@ -0,0 +1,14 @@
package implement

import (
"applet/app/db/dao"
"xorm.io/xorm"
)

type PermissionDb struct {
Db *xorm.Engine
}

func NewPermissionDb(engine *xorm.Engine) dao.PermissionDao {
return &PermissionDb{Db: engine}
}

+ 73
- 0
app/db/implement/role_implement.go Целия файл

@@ -0,0 +1,73 @@
package implement

import (
"applet/app/db/dao"
"applet/app/db/model"
"applet/app/utils/logx"
"reflect"
"xorm.io/xorm"
)

func NewRoleDb(engine *xorm.Engine, id int) dao.RoleDao {
return &RoleDb{
Db: engine,
Id: id,
}
}

type RoleDb struct {
Db *xorm.Engine
Id int `json:"id"`
}

func (r RoleDb) FindRole() (*[]model.Role, error) {
var m []model.Role
if err := r.Db.Desc("id").Find(&m); err != nil {
return nil, logx.Error(err)
}
return &m, nil
}

func (r RoleDb) GetRole() (m *model.Role, err error) {
m = new(model.Role)
has, err := r.Db.Where("id = ?", r.Id).Get(m)
if err != nil {
return nil, logx.Error(err)
}
if has == false {
return nil, nil
}
return m, nil
}

func (r RoleDb) UpdateRole(m *model.Role, columns ...string) (int64, error) {
affected, err := r.Db.Where("id =?", m.Id).Cols(columns...).Update(m)
if err != nil {
return 0, err
}
return affected, nil
}

func (r RoleDb) RoleDeleteBySession(session *xorm.Session, id interface{}) (int64, error) {
if reflect.TypeOf(id).Kind() == reflect.Slice {
return session.In("id", id).Delete(model.Role{})
} else {
return session.Where("id = ?", id).Delete(model.Role{})
}
}

func (r RoleDb) RoleInsert(m *model.Role) (int, error) {
_, err := r.Db.InsertOne(m)
if err != nil {
return 0, err
}
return m.Id, nil
}

func (r RoleDb) FindPermissionGroupByRole(roleId int) (list []*dao.RolePermissionGroup, total int64, err error) {
total, err = r.Db.Where("role.id =?", roleId).
Join("LEFT", "role_permission_group", "role_permission_group.role_id = role.id").
Join("LEFT", "permission_group", "permission_group.id = role_permission_group.group_id").
FindAndCount(&list)
return
}

+ 32
- 0
app/db/implement/role_permission_group_implement.go Целия файл

@@ -0,0 +1,32 @@
package implement

import (
"applet/app/db/dao"
"applet/app/db/model"
"reflect"
"xorm.io/xorm"
)

func NewRolePermissionGroupDb(engine *xorm.Engine) dao.RolePermissionGroupDao {
return &RolePermissionGroupDb{Db: engine}
}

type RolePermissionGroupDb struct {
Db *xorm.Engine
}

func (r RolePermissionGroupDb) RolePermissionGroupDeleteForRoleBySession(session *xorm.Session, roleId interface{}) (int64, error) {
if reflect.TypeOf(roleId).Kind() == reflect.Slice {
return session.In("role_id", roleId).Delete(model.RolePermissionGroup{})
} else {
return session.Where("role_id = ?", roleId).Delete(model.RolePermissionGroup{})
}
}

func (r RolePermissionGroupDb) BatchAddRolePermissionGroupBySession(session *xorm.Session, mm []*model.RolePermissionGroup) (int64, error) {
affected, err := session.Insert(mm)
if err != nil {
return 0, err
}
return affected, nil
}

+ 14
- 0
app/db/model/admin.go Целия файл

@@ -0,0 +1,14 @@
package model

type Admin struct {
AdmId int `json:"adm_id" xorm:"not null comment('管理员id') INT(11)"`
Username string `json:"username" xorm:"not null default '' comment('用户名') VARCHAR(255)"`
Password string `json:"password" xorm:"not null default '' comment('密码') VARCHAR(255)"`
State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"`
IsSuperAdministrator int `json:"is_super_administrator" xorm:"not null default 0 comment('是否为超级管理员(0:否 1:是)') TINYINT(1)"`
Memo string `json:"memo" xorm:"not null default '' comment('备注信息') VARCHAR(244)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
OpenId string `json:"open_id" xorm:"not null default '' comment('微信openid') VARCHAR(255)"`
UnionId string `json:"union_id" xorm:"not null default '' comment('微信用户id') VARCHAR(255)"`
}

+ 10
- 0
app/db/model/admin_role.go Целия файл

@@ -0,0 +1,10 @@
package model

type AdminRole struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
AdmId int `json:"adm_id" xorm:"not null default 0 comment('管理员id') INT(11)"`
RoleId int `json:"role_id" xorm:"not null default 0 comment('角色id') INT(11)"`
State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 14
- 0
app/db/model/db_mapping.go Целия файл

@@ -0,0 +1,14 @@
package model

type DbMapping struct {
DbMasterId string `json:"db_master_id" xorm:"not null pk comment('站长id') VARCHAR(16)"`
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 string `json:"created_at" xorm:"not null default 'CURRENT_TIMESTAMP' comment('创建时间') TIMESTAMP"`
UpdatedAt string `json:"updated_at" xorm:"not null default 'CURRENT_TIMESTAMP' comment('更新时间') TIMESTAMP"`
DeletedAt int `json:"deleted_at" xorm:"not null default 0 comment('是否已删除') TINYINT(1)"`
}

+ 10
- 0
app/db/model/permission.go Целия файл

@@ -0,0 +1,10 @@
package model

type Permission struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"`
Action string `json:"action" xorm:"not null default '' comment('路由') unique VARCHAR(255)"`
State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:废弃)') TINYINT(1)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 11
- 0
app/db/model/permission_group.go Целия файл

@@ -0,0 +1,11 @@
package model

type PermissionGroup struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
ParentId int `json:"parent_id" xorm:"not null default 0 comment('父级id') INT(11)"`
Key string `json:"key" xorm:"not null default '' VARCHAR(255)"`
Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"`
State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:废弃)') TINYINT(1)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 9
- 0
app/db/model/permission_group_permission.go Целия файл

@@ -0,0 +1,9 @@
package model

type PermissionGroupPermission struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
GroupId int `json:"group_id" xorm:"not null default 0 comment('权限组id') INT(11)"`
PermissionId int `json:"permission_id" xorm:"not null default 0 comment('权限id') INT(11)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 10
- 0
app/db/model/role.go Целия файл

@@ -0,0 +1,10 @@
package model

type Role struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
Name string `json:"name" xorm:"not null default '' comment('名称') VARCHAR(255)"`
State int `json:"state" xorm:"not null default 1 comment('状态(1:正常 2:冻结)') TINYINT(1)"`
Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 9
- 0
app/db/model/role_permission_group.go Целия файл

@@ -0,0 +1,9 @@
package model

type RolePermissionGroup struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
RoleId int `json:"role_id" xorm:"not null default 0 comment('角色id') INT(11)"`
GroupId int `json:"group_id" xorm:"not null default 0 comment('权限组id') INT(11)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 7
- 0
app/db/model/sys_cfg.go Целия файл

@@ -0,0 +1,7 @@
package model

type SysCfg struct {
Key string `json:"key" xorm:"not null pk comment('键') VARCHAR(127)"`
Val string `json:"val" xorm:"comment('值') TEXT"`
Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"`
}

+ 5
- 0
app/db/super/dao/medium_list_dao.go Целия файл

@@ -0,0 +1,5 @@
package dao

type MediumListDao interface {
//TODO:: You can add specific method definitions here
}

+ 14
- 0
app/db/super/implement/medium_list_implement.go Целия файл

@@ -0,0 +1,14 @@
package implement

import (
"applet/app/db/super/dao"
"xorm.io/xorm"
)

func NewMediumListDb(engine *xorm.Engine) dao.MediumListDao {
return &MediumListDb{Db: engine}
}

type MediumListDb struct {
Db *xorm.Engine
}

+ 25
- 0
app/db/super/model/medium_list.go Целия файл

@@ -0,0 +1,25 @@
package model

type MediumList struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
Uuid int `json:"uuid" xorm:"not null comment('站长id') index unique(IDX_UUID_TYPE) INT(11)"`
MediumId int `json:"medium_id" xorm:"not null comment('媒体id') unique(IDX_UUID_TYPE) INT(11)"`
Kind int `json:"kind" xorm:"not null default 1 comment('类型(1:企业 2:个人)') TINYINT(1)"`
CompanyName string `json:"company_name" xorm:"not null default '' comment('公司名称') VARCHAR(255)"`
CompanyAbbreviation string `json:"company_abbreviation" xorm:"not null default '' comment('公司简称') VARCHAR(255)"`
UnifiedSocialCreditCode string `json:"unified_social_credit_code" xorm:"not null default '' comment('统一社会信用代码') VARCHAR(255)"`
CertificateType int `json:"certificate_type" xorm:"not null default 1 comment('证件类型') TINYINT(1)"`
BusinessLicenseImgUrl string `json:"business_license_img_url" xorm:"not null default '' comment('营业执照照片') VARCHAR(255)"`
LegalRepresentative string `json:"legal_representative" xorm:"not null default '' comment('法定代表人') CHAR(50)"`
CountryRegionId int `json:"country_region_id" xorm:"not null default 1 comment('国家地区id') TINYINT(3)"`
CountryRegion string `json:"country_region" xorm:"not null default '' comment('国家地区') CHAR(50)"`
RegisteredAddressProvinceId int `json:"registered_address_province_id" xorm:"not null default 0 comment('注册地址-省份id') INT(11)"`
RegisteredAddressCityId int `json:"registered_address_city_id" xorm:"not null default 0 comment('注册地址-市id') INT(11)"`
RegisteredAddressCountyId int `json:"registered_address_county_id" xorm:"not null default 0 comment('注册地址-县/区id') INT(11)"`
RegisteredAddress string `json:"registered_address" xorm:"not null default '' comment('注册地址') VARCHAR(255)"`
BusinessLicenseAddress string `json:"business_license_address" xorm:"not null default '' comment('营业执照地址') VARCHAR(255)"`
CertificateValidity string `json:"certificate_validity" xorm:"not null default '0000-00-00' comment('证件有效期') CHAR(50)"`
State int `json:"state" xorm:"not null default 0 comment('状态(0:待审核 1:审核通过 2:审核拒绝)') TINYINT(1)"`
CreateAt string `json:"create_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
UpdateAt string `json:"update_at" xorm:"not null default 'CURRENT_TIMESTAMP' DATETIME"`
}

+ 236
- 0
app/e/code.go Целия файл

@@ -0,0 +1,236 @@
package e

const (
// 200 因为部分第三方接口不能返回错误头,因此在此定义部分错误
ERR_FILE_SAVE = 200001
// 400 系列
ERR_BAD_REQUEST = 400000
ERR_INVALID_ARGS = 400001
ERR_API_RESPONSE = 400002
ERR_NO_DATA = 400003
ERR_MOBILE_NIL = 400004
ERR_MOBILE_MATH = 400005
ERR_FILE_EXT = 400006
ERR_FILE_MAX_SIZE = 400007
ERR_SIGN = 400008
ERR_PASSWORD_MATH = 400009
ERR_PROVIDER_RESPONSE = 400010
ERR_AES_ENCODE = 400011
ERR_ADMIN_API = 400012
ERR_QINIUAPI_RESPONSE = 400013
ERR_URL_TURNCHAIN = 400014

// 401 未授权
ERR_UNAUTHORIZED = 401000
ERR_NOT_AUTH = 401001
ERR_SMS_AUTH = 401002
ERR_TOKEN_AUTH = 401003
ERR_TOKEN_FORMAT = 401004
ERR_TOKEN_GEN = 401005
ERR_CACHE_SET = 401006
// 403 禁止
ERR_FORBIDEN = 403000
ERR_PLATFORM = 403001
ERR_MOBILE_EXIST = 403002
ERR_USER_NO_EXIST = 403003
ERR_MOBILE_NO_EXIST = 403004
ERR_FORBIDEN_VALID = 403005
ERR_RELATE_ERR = 403006
ERR_REPEAT_RELATE = 403007
ERR_MOB_FORBIDEN = 403008
ERR_MOB_SMS_NO_AVA = 403009
ERR_USER_IS_REG = 403010
ERR_MASTER_ID = 403011
ERR_CASH_OUT_TIME = 403012
ERR_CASH_OUT_FEE = 403013
ERR_CASH_OUT_USER_NOT_FOUND = 403014
ERR_CASH_OUT_FAIL = 403015
ERR_CASH_OUT_TIMES = 403016
ERR_CASH_OUT_MINI = 403017
ERR_CASH_OUT_MUT = 403018
ERR_CASH_OUT_NOT_DECIMAL = 403019
ERR_CASH_OUT_NOT_DAY_AVA = 403020
ERR_USER_LEVEL_PAY_CHECK_TASK_NO_DONE = 403021
ERR_USER_LEVEL_PAY_CHECK_NO_CROSS = 403022
ERR_USER_LEVEL_ORD_EXP = 403023
ERR_IS_BIND_THIRDPARTY = 403024
ERR_USER_LEVEL_UPDATE_CHECK_TASK_NO_DONE = 403025
ERR_USER_LEVEL_UPDATE_CHECK_NOT_FOUND_ORDER = 403026
ERR_USER_LEVEL_UPDATE_REPEAT = 403027
ERR_USER_NO_ACTIVE = 403028
ERR_USER_IS_BAN = 403029
ERR_ALIPAY_SETTING = 403030
ERR_ALIPAY_ORDERTYPE = 403031
ERR_CLIPBOARD_UNSUP = 403032
ERR_SYSUNION_CONFIG = 403033
ERR_WECAHT_MINI = 403034
ERR_WECAHT_MINI_CACHE = 403035
ERR_WECAHT_MINI_DECODE = 403036
ERR_WECHAT_MINI_ACCESSTOKEN = 403037
ERR_CURRENT_VIP_LEVEL_AUDITING = 403038
ERR_LEVEL_RENEW_SHOULD_KEEP_CURRENT = 403039
ERR_LEVEL_UPGRADE_APPLY_AUDITTING = 403040
ERR_LEVEL_TASK_PAY_TYPE = 403041
ERR_BALANCE_NOT_ENOUGH = 403042
ERR_ADMIN_PUSH = 403043
ERR_PLAN = 403044
ERR_MOB_CONFIG = 403045
ERR_BAlANCE_PAY_ORDERTYPE = 403046
ERR_PHONE_EXISTED = 403047
ERR_NOT_RESULT = 403048
ERR_REVIEW = 403049
ERR_USER_LEVEL_HAS_PAID = 403050
ERR_USER_BIND_OWN = 403051
ERR_PARENTUID_ERR = 403052
ERR_USER_DEL = 403053
ERR_SEARCH_ERR = 403054
ERR_LEVEL_REACH_TOP = 403055
ERR_USER_CHECK_ERR = 403056
ERR_PASSWORD_ERR = 403057
// 404
ERR_USER_NOTFOUND = 404001
ERR_SUP_NOTFOUND = 404002
ERR_LEVEL_MAP = 404003
ERR_MOD_NOTFOUND = 404004
ERR_CLIPBOARD_PARSE = 404005
ERR_NOT_FAN = 404006
ERR_USER_LEVEL = 404007
ERR_LACK_PAY_CFG = 404008
ERR_NOT_LEVEL_TASK = 404009
ERR_ITEM_NOT_FOUND = 404010
ERR_WX_CHECKFILE_NOTFOUND = 404011

// 429 请求频繁
ERR_TOO_MANY_REQUESTS = 429000
// 500 系列
ERR = 500000
ERR_UNMARSHAL = 500001
ERR_UNKNOWN = 500002
ERR_SMS = 500003
ERR_ARKID_REGISTER = 500004
ERR_ARKID_WHITELIST = 500005
ERR_ARKID_LOGIN = 500006
ERR_CFG = 500007
ERR_DB_ORM = 500008
ERR_CFG_CACHE = 500009
ERR_ZHIMENG_CONVERT_ERR = 500010
ERR_ALIPAY_ERR = 500011
ERR_ALIPAY_ORDER_ERR = 500012
ERR_PAY_ERR = 500013
ERR_IS_BIND_THIRDOTHER = 500014
)

var MsgFlags = map[int]string{
// 200
ERR_FILE_SAVE: "文件保存失败",
// 400
ERR_BAD_REQUEST: "请求失败",
ERR_INVALID_ARGS: "请求参数错误",
ERR_API_RESPONSE: "API错误",
ERR_QINIUAPI_RESPONSE: "七牛请求API错误",
ERR_URL_TURNCHAIN: "转链失败",
ERR_NO_DATA: "暂无数据",
ERR_MOBILE_NIL: "电话号码不能为空",
ERR_MOBILE_MATH: "电话号码输入有误",
ERR_FILE_MAX_SIZE: "文件上传大小超限",
ERR_FILE_EXT: "文件类型不支持",
ERR_SIGN: "签名校验失败",
ERR_PROVIDER_RESPONSE: "提供商接口错误",
ERR_AES_ENCODE: "加解密错误",
ERR_ADMIN_API: "后台接口请求失败",
// 401
ERR_NOT_AUTH: "请登录后操作",
ERR_SMS_AUTH: "验证码过期或无效",
ERR_UNAUTHORIZED: "验证用户失败",
ERR_TOKEN_FORMAT: "Token格式不对",
ERR_TOKEN_GEN: "生成Token失败",
ERR_CACHE_SET: "生成缓存失败",
// 403
ERR_FORBIDEN: "禁止访问",
ERR_PLATFORM: "平台不支持",
ERR_MOBILE_EXIST: "该号码已注册过",
ERR_USER_NO_EXIST: "用户没有注册或账号密码不正确",
ERR_PASSWORD_ERR: "输入两次密码不一致",
ERR_RELATE_ERR: "推荐人不能是自己的粉丝",
ERR_PARENTUID_ERR: "推荐人不存在",
ERR_TOKEN_AUTH: "登录信息失效,请重新登录",
ERR_MOB_SMS_NO_AVA: "短信余额不足或智盟短信配置失败",
ERR_USER_IS_REG: "用户已注册",
ERR_MASTER_ID: "找不到对应站长的数据库",
ERR_CASH_OUT_TIME: "非可提现时间段",
ERR_CASH_OUT_USER_NOT_FOUND: "收款账号不存在",
ERR_CASH_OUT_FAIL: "提现失败",
ERR_CASH_OUT_FEE: "提现金额必须大于手续费",
ERR_CASH_OUT_TIMES: "当日提现次数已达上线",
ERR_CASH_OUT_MINI: "申请提现金额未达到最低金额要求",
ERR_CASH_OUT_MUT: "申请提现金额未达到整数倍要求",
ERR_CASH_OUT_NOT_DECIMAL: "提现申请金额只能是整数",
ERR_CASH_OUT_NOT_DAY_AVA: "不在可提现日期范围内",
ERR_USER_LEVEL_PAY_CHECK_TASK_NO_DONE: "请先完成其他任务",
ERR_USER_LEVEL_PAY_CHECK_NO_CROSS: "无法跨越升级",
ERR_USER_LEVEL_ORD_EXP: "付费订单已失效",
ERR_IS_BIND_THIRDPARTY: "该用户已经绑定了",
ERR_IS_BIND_THIRDOTHER: "该账号已经被绑定了",
ERR_USER_LEVEL_UPDATE_CHECK_TASK_NO_DONE: "请完成指定任务",
ERR_USER_LEVEL_UPDATE_CHECK_NOT_FOUND_ORDER: "没有找到对应的订单",
ERR_USER_LEVEL_UPDATE_REPEAT: "不允许重复升级",
ERR_USER_NO_ACTIVE: "账户没激活",
ERR_USER_IS_BAN: "账户已被冻结",
ERR_SYSUNION_CONFIG: "联盟设置错误,请检查配置",
ERR_WECAHT_MINI: "小程序响应错误,请检查小程序配置",
ERR_WECAHT_MINI_CACHE: "获取小程序缓存失败",
ERR_WECAHT_MINI_DECODE: "小程序解密失败",
ERR_WECHAT_MINI_ACCESSTOKEN: "无法获取accesstoekn",
ERR_CURRENT_VIP_LEVEL_AUDITING: "当前等级正在审核中",
ERR_LEVEL_RENEW_SHOULD_KEEP_CURRENT: "续费只能在当前等级续费",
ERR_LEVEL_UPGRADE_APPLY_AUDITTING: "已有申请正在审核中,暂时不能申请",
ERR_LEVEL_TASK_PAY_TYPE: "任务付费类型错误",
ERR_BALANCE_NOT_ENOUGH: "余额不足",
ERR_ADMIN_PUSH: "后台MOB推送错误",
ERR_PLAN: "分拥方案出错",
ERR_MOB_CONFIG: "Mob 配置错误",
ERR_BAlANCE_PAY_ORDERTYPE: "无效余额支付订单类型",
ERR_PHONE_EXISTED: "手机号码已存在",
ERR_NOT_RESULT: "已加载完毕",
ERR_REVIEW: "审核模板错误",
ERR_USER_LEVEL_HAS_PAID: "该等级已经付过款",
// 404
ERR_USER_NOTFOUND: "用户不存在",
ERR_USER_DEL: "账号被删除,如有疑问请联系客服",
ERR_SUP_NOTFOUND: "上级用户不存在",
ERR_LEVEL_MAP: "无等级映射关系",
ERR_MOD_NOTFOUND: "没有找到对应模块",
ERR_CLIPBOARD_PARSE: "无法解析剪切板内容",
ERR_NOT_FAN: "没有粉丝",
ERR_CLIPBOARD_UNSUP: "不支持该平台",
ERR_USER_LEVEL: "该等级已不存在",
ERR_LACK_PAY_CFG: "支付配置不完整",
ERR_NOT_LEVEL_TASK: "等级任务查找错误",
ERR_ITEM_NOT_FOUND: "找不到对应商品",
ERR_WX_CHECKFILE_NOTFOUND: "找不到微信校验文件",
ERR_USER_BIND_OWN: "不能填写自己的邀请码",
// 429
ERR_TOO_MANY_REQUESTS: "请求频繁,请稍后重试",
// 500 内部错误
ERR: "接口错误",
ERR_SMS: "短信发送出错",
ERR_CFG: "服务器配置错误",
ERR_UNMARSHAL: "JSON解码错误",
ERR_UNKNOWN: "未知错误",
ERR_ARKID_LOGIN: "登录失败",
ERR_MOBILE_NO_EXIST: "该用户未设定手机号",
ERR_FORBIDEN_VALID: "验证码错误",
ERR_CFG_CACHE: "获取配置缓存失败",
ERR_DB_ORM: "数据操作失败",
ERR_REPEAT_RELATE: "重复关联",
ERR_ZHIMENG_CONVERT_ERR: "智盟转链失败",
ERR_MOB_FORBIDEN: "Mob调用失败",
ERR_ALIPAY_ERR: "支付宝参数错误",
ERR_ALIPAY_SETTING: "请在后台正确配置支付宝",
ERR_ALIPAY_ORDERTYPE: "无效支付宝订单类型",
ERR_ALIPAY_ORDER_ERR: "订单创建错误",
ERR_PAY_ERR: "未找到支付方式",
ERR_SEARCH_ERR: "暂无该分类商品",
ERR_LEVEL_REACH_TOP: "已经是最高等级",
ERR_USER_CHECK_ERR: "校验失败",
}

+ 72
- 0
app/e/error.go Целия файл

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

import (
"fmt"
"path"
"runtime"
)

type E struct {
Code int // 错误码
msg string // 报错代码
st string // 堆栈信息
}

func NewErrCode(code int) error {
if msg, ok := MsgFlags[code]; ok {
return E{code, msg, stack(3)}
}
return E{ERR_UNKNOWN, "unknown", stack(3)}
}

func NewErr(code int, msg string) error {
return E{code, msg, stack(3)}
}

func NewErrf(code int, msg string, args ...interface{}) error {
return E{code, fmt.Sprintf(msg, args), stack(3)}
}

func (e E) Error() string {
return e.msg
}

func stack(skip int) string {
stk := make([]uintptr, 32)
str := ""
l := runtime.Callers(skip, stk[:])
for i := 0; i < l; i++ {
f := runtime.FuncForPC(stk[i])
name := f.Name()
file, line := f.FileLine(stk[i])
str += fmt.Sprintf("\n%-30s[%s:%d]", name, path.Base(file), line)
}
return str
}

// ErrorIsAccountBan is 检查这个账号是否被禁用的错误
func ErrorIsAccountBan(e error) bool {
err, ok := e.(E)
if ok && err.Code == 403029 {
return true
}
return false
}

// ErrorIsAccountNoActive is 检查这个账号是否被禁用的错误
func ErrorIsAccountNoActive(e error) bool {
err, ok := e.(E)
if ok && err.Code == 403028 {
return true
}
return false
}

// ErrorIsUserDel is 检查这个账号是否被删除
func ErrorIsUserDel(e error) bool {
err, ok := e.(E)
if ok && err.Code == 403053 {
return true
}
return false
}

+ 110
- 0
app/e/msg.go Целия файл

@@ -0,0 +1,110 @@
package e

import (
"applet/app/utils"
"encoding/json"
"net/http"

"github.com/gin-gonic/gin"

"applet/app/utils/logx"
)

// GetMsg get error information based on Code
// 因为这里code是自己控制的, 因此没考虑报错信息
func GetMsg(code int) (int, string) {
if msg, ok := MsgFlags[code]; ok {
return code / 1000, msg
}
if http.StatusText(code) == "" {
code = 200
}
return code, MsgFlags[ERR_BAD_REQUEST]
}

// 成功输出, fields 是额外字段, 与code, msg同级
func OutSuc(c *gin.Context, data interface{}, fields map[string]interface{}) {
res := gin.H{
"code": 1,
"msg": "ok",
"data": data,
}
if fields != nil {
for k, v := range fields {
res[k] = v
}
}
if utils.GetApiVersion(c) > 0 { //加了签名校验只返回加密的字符串
jsonData, _ := json.Marshal(res)
str := utils.ResultAes(c, jsonData)
c.Writer.WriteString(str)
} else {
c.AbortWithStatusJSON(200, res)
}
}

func OutSucPure(c *gin.Context, data interface{}, fields map[string]interface{}) {
res := gin.H{
"code": 1,
"msg": "ok",
"data": data,
}
if fields != nil {
for k, v := range fields {
res[k] = v
}
}
c.Abort()
c.PureJSON(200, res)
}

// 错误输出
func OutErr(c *gin.Context, code int, err ...interface{}) {
statusCode, msg := GetMsg(code)
if len(err) > 0 && err[0] != nil {
e := err[0]
switch v := e.(type) {
case E:
statusCode = v.Code / 1000
msg = v.Error()
logx.Error(v.msg + ": " + v.st) // 记录堆栈信息
case error:
logx.Error(v)
break
case string:
msg = v
case int:
if _, ok := MsgFlags[v]; ok {
msg = MsgFlags[v]
}
}
}
if utils.GetApiVersion(c) > 0 { //加了签名校验只返回加密的字符串
jsonData, _ := json.Marshal(gin.H{
"code": code,
"msg": msg,
"data": []struct{}{},
})
str := utils.ResultAes(c, jsonData)
if code > 100000 {
code = int(utils.FloatFormat(float64(code/1000), 0))
}
c.Status(500)
c.Writer.WriteString(str)
} else {
c.AbortWithStatusJSON(statusCode, gin.H{
"code": code,
"msg": msg,
"data": []struct{}{},
})
}
}

// 重定向
func OutRedirect(c *gin.Context, code int, loc string) {
if code < 301 || code > 308 {
code = 303
}
c.Redirect(code, loc)
c.Abort()
}

+ 8
- 0
app/e/set_cache.go Целия файл

@@ -0,0 +1,8 @@
package e

func SetCache(cacheTime int64) map[string]interface{} {
if cacheTime == 0 {
return map[string]interface{}{"cache_time": cacheTime}
}
return map[string]interface{}{"cache_time": cacheTime}
}

+ 37
- 0
app/enum/enum_admin.go Целия файл

@@ -0,0 +1,37 @@
package enum

type AdminState int32

const (
AdminStateForNormal = 1
AdminStateForFreeze = 2
)

func (gt AdminState) String() string {
switch gt {
case AdminStateForNormal:
return "正常"
case AdminStateForFreeze:
return "冻结"
default:
return "未知"
}
}

type IsSuperAdministrator int32

const (
IsSuperAdministratorTure = 1
IsSuperAdministratorFalse = 2
)

func (gt IsSuperAdministrator) String() string {
switch gt {
case IsSuperAdministratorTure:
return "超管"
case IsSuperAdministratorFalse:
return "非超管"
default:
return "未知"
}
}

+ 19
- 0
app/enum/enum_permission_group.go Целия файл

@@ -0,0 +1,19 @@
package enum

type PermissionGroupState int32

const (
PermissionGroupStateForNormal = 1
PermissionGroupStateForDiscard = 2
)

func (gt PermissionGroupState) String() string {
switch gt {
case PermissionGroupStateForNormal:
return "正常"
case PermissionGroupStateForDiscard:
return "废弃"
default:
return "未知"
}
}

+ 19
- 0
app/enum/enum_role.go Целия файл

@@ -0,0 +1,19 @@
package enum

type RoleState int32

const (
RoleStateForNormal = 1
RoleStateForFreeze = 2
)

func (gt RoleState) String() string {
switch gt {
case RoleStateForNormal:
return "正常"
case RoleStateForFreeze:
return "冻结"
default:
return "未知"
}
}

+ 23
- 0
app/hdl/hdl_demo.go Целия файл

@@ -0,0 +1,23 @@
package hdl

import (
"applet/app/e"
"applet/app/svc"
"github.com/gin-gonic/gin"
)

func Demo(c *gin.Context) {
var args interface{}
err := c.ShouldBindJSON(&args)
if err != nil {
err = svc.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}

e.OutSuc(c, map[string]interface{}{
"args": args,
}, nil)
return
}

+ 60
- 0
app/hdl/hdl_login.go Целия файл

@@ -0,0 +1,60 @@
package hdl

import (
"applet/app/db"
"applet/app/db/implement"
"applet/app/e"
"applet/app/lib/validate"
"applet/app/md"
"applet/app/svc"
"applet/app/utils"
"fmt"
"github.com/gin-gonic/gin"
)

// Login 登陆
// @Summary 登陆
// @Tags ADMIN
// @Description 登入
// @Accept json
// @Produce json
// @Param req body md2.LoginReq true "用户名密码"
// @Success 200 {object} md2.LoginResponse "token"
// @Failure 400 {object} md2.Response "具体错误"
// @Router /admin/login [post]
func Login(c *gin.Context) {
var req md.LoginReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
engine := db.DBs[svc.GetMasterId(c)]
adminDb := implement.NewAdminDb(engine)
admin, err := adminDb.GetAdminByUserName(req.UserName)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err)
return
}
if admin == nil {
e.OutErr(c, e.ERR_NO_DATA, "账号不存在!")
return
}
if utils.Md5(req.PassWord) != admin.Password {
e.OutErr(c, e.ERR_INVALID_ARGS, "密码错误")
return
}
ip := utils.GetIP(c.Request)
key := fmt.Sprintf(md.JwtTokenKey, ip, utils.AnyToString(admin.AdmId))
token, err := svc.HandleLoginToken(key, admin)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}
e.OutSuc(c, md.LoginResponse{
Token: token,
}, nil)
return
}

+ 649
- 0
app/hdl/hdl_role.go Целия файл

@@ -0,0 +1,649 @@
package hdl

import (
"applet/app/db"
"applet/app/db/implement"
"applet/app/db/model"
"applet/app/e"
"applet/app/enum"
"applet/app/lib/validate"
"applet/app/md"
"applet/app/svc"
"applet/app/utils"
"github.com/gin-gonic/gin"
"time"
)

// PermissionGroupList
// @Summary 权限组列表
// @Tags ADMIN-权限管理
// @Description 权限组列表
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @param adm_id query string true "管理员id"
// @Success 200 {string} "具体看返回内容"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/permissionGroupList [GET]
func PermissionGroupList(c *gin.Context) {
roleId := c.DefaultQuery("role_id", "")
engine := db.DBs[svc.GetMasterId(c)]
qrcodeWithBatchRecordsDb := implement.NewPermissionGroupDb(engine)
groupList, err := qrcodeWithBatchRecordsDb.FindPermissionGroup()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}

roleDb := implement.NewRoleDb(engine, utils.StrToInt(roleId))
list, _, err := roleDb.FindPermissionGroupByRole(utils.StrToInt(roleId))
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
var isHasPermissionGroupId []string
for _, v := range list {
isHasPermissionGroupId = append(isHasPermissionGroupId, utils.IntToStr(v.PermissionGroup.Id))
}

var tempRespMap = map[string]*md.PermissionGroupListResp{}
var tempRespMapKeys []string
for _, v := range *groupList {
isCheck := false
if utils.InArr(utils.IntToStr(v.Id), isHasPermissionGroupId) {
isCheck = true
}
tempRespMap[utils.IntToStr(v.Id)] = &md.PermissionGroupListResp{
Id: v.Id,
Name: v.Name,
Key: v.Key,
State: v.State,
ParentId: v.ParentId,
CreateAt: v.CreateAt,
UpdateAt: v.UpdateAt,
IsCheck: isCheck,
}
tempRespMapKeys = append(tempRespMapKeys, utils.IntToStr(v.Id))
}
for _, v := range tempRespMap {
if v.ParentId != 0 && tempRespMap[utils.IntToStr(v.ParentId)].ParentId != 0 {
tempRespMap[utils.IntToStr(v.ParentId)].SubPermissionGroupList = append(tempRespMap[utils.IntToStr(v.ParentId)].SubPermissionGroupList, *v)
}
}
for _, v := range tempRespMap {
if v.ParentId != 0 && tempRespMap[utils.IntToStr(v.ParentId)].ParentId == 0 {
tempRespMap[utils.IntToStr(v.ParentId)].SubPermissionGroupList = append(tempRespMap[utils.IntToStr(v.ParentId)].SubPermissionGroupList, *v)
}
}

var resp []*md.PermissionGroupListResp
for _, v := range tempRespMapKeys {
if tempRespMap[v].ParentId == 0 {
resp = append(resp, tempRespMap[v])
}
}

e.OutSuc(c, map[string]interface{}{
"list": resp,
"state": []map[string]interface{}{
{
"name": enum.PermissionGroupState(enum.PermissionGroupStateForNormal).String(),
"value": enum.PermissionGroupStateForNormal,
},
{
"name": enum.PermissionGroupState(enum.PermissionGroupStateForDiscard).String(),
"value": enum.PermissionGroupStateForDiscard,
},
},
}, nil)
return
}

// RoleList
// @Summary 角色列表
// @Tags ADMIN-权限管理
// @Description 角色列表
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Success 200 {string} "具体看返回内容"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/roleList [GET]
func RoleList(c *gin.Context) {
engine := db.DBs[svc.GetMasterId(c)]
roleDb := implement.NewRoleDb(engine, 0)
roleList, err := roleDb.FindRole()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}

adminRoleDb := implement.NewAdminRoleDb(engine)
adminDb := implement.NewAdminDb(engine)
var result []*md.RoleListResp
for _, v := range *roleList {
var temp md.RoleListResp
temp.Data = v
adminRoles, err1 := adminRoleDb.FindAdminRoleByRoleId(v.Id)
if err1 != nil {
e.OutErr(c, e.ERR_DB_ORM, err1.Error())
return
}
for _, adminRole := range *adminRoles {
admin, err2 := adminDb.GetAdmin(adminRole.AdmId)
if err2 != nil {
e.OutErr(c, e.ERR_DB_ORM, err2.Error())
return
}
temp.AdminList = append(temp.AdminList, struct {
Name string `json:"name"`
}{
Name: admin.Username,
})
}
result = append(result, &temp)
}
e.OutSuc(c, map[string]interface{}{
"list": result,
"state": []map[string]interface{}{
{
"name": enum.RoleState(enum.RoleStateForNormal).String(),
"value": enum.RoleStateForNormal,
},
{
"name": enum.RoleState(enum.RoleStateForFreeze).String(),
"value": enum.RoleStateForFreeze,
},
},
}, nil)
return
}

// AddRole
// @Summary 添加角色
// @Tags ADMIN-权限管理
// @Description 添加角色
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.AddRoleReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/addRole [POST]
func AddRole(c *gin.Context) {
var req md.AddRoleReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
engine := db.DBs[svc.GetMasterId(c)]
roleDb := implement.NewRoleDb(engine, 0)
now := time.Now()
_, err = roleDb.RoleInsert(&model.Role{
Name: req.Name,
State: enum.RoleStateForNormal,
Memo: req.Memo,
CreateAt: now.Format("2006-01-02 15:04:05"),
UpdateAt: now.Format("2006-01-02 15:04:05"),
})
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}

e.OutSuc(c, "success", nil)
return
}

// UpdateRole
// @Summary 修改角色
// @Tags ADMIN-权限管理
// @Description 修改角色
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.UpdateRoleReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/updateRole [POST]
func UpdateRole(c *gin.Context) {
var req md.UpdateRoleReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
engine := db.DBs[svc.GetMasterId(c)]
roleDb := implement.NewRoleDb(engine, req.RoleId)
role, err := roleDb.GetRole()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
if role == nil {
e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录")
return
}
role.Name = req.Name
role.Memo = req.Memo
_, err = roleDb.UpdateRole(role, "name", "memo")
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// RoleBindPermissionGroup
// @Summary 角色绑定权限组
// @Tags ADMIN-权限管理
// @Description 角色绑定权限组
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.RoleBindPermissionGroupReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/roleBindPermissionGroup [POST]
func RoleBindPermissionGroup(c *gin.Context) {
var req md.RoleBindPermissionGroupReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
err = svc.RoleBindPermissionGroup(c, req)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}

e.OutSuc(c, "success", nil)
return
}

// UpdateRoleState
// @Summary 修改角色状态
// @Tags ADMIN-权限管理
// @Description 修改角色状态
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.UpdateRoleStateReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/updateRole [POST]
func UpdateRoleState(c *gin.Context) {
var req md.UpdateRoleStateReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
engine := db.DBs[svc.GetMasterId(c)]
roleDb := implement.NewRoleDb(engine, req.RoleId)
role, err := roleDb.GetRole()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
if role == nil {
e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录")
return
}
role.State = req.State
_, err = roleDb.UpdateRole(role, "state")
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// DeleteRole
// @Summary 删除角色
// @Tags ADMIN-权限管理
// @Description 删除角色
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.UpdateRoleStateReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/deleteRole/{$id} [DELETE]
func DeleteRole(c *gin.Context) {
id := c.Param("id")
engine := db.DBs[svc.GetMasterId(c)]
roleDb := implement.NewRoleDb(engine, utils.StrToInt(id))
role, err := roleDb.GetRole()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
if role == nil {
e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录")
return
}

err = svc.DeleteRole(c, utils.StrToInt(id))
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}

e.OutSuc(c, "success", nil)
return
}

// AdminList
// @Summary 管理员列表
// @Tags ADMIN-权限管理
// @Description 管理员列表
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.AdminListReq true "请求参数"
// @Success 200 {string} "具体看返回内容"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/adminList [POST]
func AdminList(c *gin.Context) {
var req md.AdminListReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
if req.Limit == 0 {
req.Limit = 10
}
if req.Page == 0 {
req.Page = 10
}
engine := db.DBs[svc.GetMasterId(c)]
adminDb := implement.NewAdminDb(engine)
adminList, total, err := adminDb.FindAdmin(req.UserName, req.State, req.Page, req.Limit)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
var result []md.AdminListResp
for _, v := range adminList {
permissionGroupList, _, err1 := adminDb.FindAdminRolePermissionGroup(v.AdmId)
if err1 != nil {
e.OutErr(c, e.ERR_DB_ORM, err1.Error())
return
}
var roleList []string
for _, v1 := range permissionGroupList {
roleList = append(roleList, v1.Role.Name)
}

result = append(result, md.AdminListResp{
AdmId: v.AdmId,
Username: v.Username,
State: v.State,
IsSuperAdministrator: v.IsSuperAdministrator,
Memo: v.Memo,
CreateAt: v.CreateAt,
UpdateAt: v.UpdateAt,
RoleList: roleList,
})
}

e.OutSuc(c, map[string]interface{}{
"list": result,
"total": total,
"state": []map[string]interface{}{
{
"name": enum.RoleState(enum.RoleStateForNormal).String(),
"value": enum.RoleStateForNormal,
},
{
"name": enum.RoleState(enum.RoleStateForFreeze).String(),
"value": enum.RoleStateForFreeze,
},
},
}, nil)
return
}

// UpdateAdminState
// @Summary 修改管理员状态
// @Tags ADMIN-权限管理
// @Description 修改管理员状态
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.UpdateAdminStateReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/updateAdminState [POST]
func UpdateAdminState(c *gin.Context) {
var req md.UpdateAdminStateReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}

engine := db.DBs[svc.GetMasterId(c)]
admDb := implement.NewAdminDb(engine)
admin, err := admDb.GetAdmin(req.AdmId)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
if admin == nil {
e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录")
return
}
admin.State = req.State
_, err = admDb.UpdateAdmin(admin, "state")
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// UpdateAdmin
// @Summary 修改管理员信息
// @Tags ADMIN-权限管理
// @Description 修改管理员信息
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.UpdateAdminReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/updateAdmin [POST]
func UpdateAdmin(c *gin.Context) {
var req md.UpdateAdminReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
engine := db.DBs[svc.GetMasterId(c)]
admDb := implement.NewAdminDb(engine)
admin, err := admDb.GetAdmin(req.AdmId)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
if admin == nil {
e.OutErr(c, e.ERR_NO_DATA, "未查询到相应记录")
return
}
admin.Username = req.Username
admin.Memo = req.Memo
admin.Password = utils.Md5(req.Password)
_, err = admDb.UpdateAdmin(admin, "username", "memo", "password")
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// AddAdmin
// @Summary 新增管理员
// @Tags ADMIN-权限管理
// @Description 新增管理员
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.AddAdminReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/addAdmin [POST]
func AddAdmin(c *gin.Context) {
var req md.AddAdminReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
engine := db.DBs[svc.GetMasterId(c)]
admDb := implement.NewAdminDb(engine)
isHasAdmin, err := admDb.GetAdminByUserName(req.Username)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
if isHasAdmin != nil {
e.OutErr(c, e.ERR, "当前用户名已存在,请勿重复添加")
return
}

admId, err := admDb.CreateAdminId()
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
admin := model.Admin{
AdmId: admId,
Username: req.Username,
Password: utils.Md5(req.Password),
State: enum.AdminStateForNormal,
IsSuperAdministrator: 0,
Memo: req.Memo,
CreateAt: time.Now().Format("2006-01-02 15:04:05"),
UpdateAt: time.Now().Format("2006-01-02 15:04:05"),
}
_, err = admDb.AdminInsert(&admin)
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// DeleteAdmin
// @Summary 删除管理员
// @Tags ADMIN-权限管理
// @Description 删除管理员
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/deleteAdmin/{$adm_id} [DELETE]
func DeleteAdmin(c *gin.Context) {
admId := c.Param("adm_id")
err := svc.AdminDelete(c, []int{utils.StrToInt(admId)})
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// BindAdminRole
// @Summary 管理员绑定角色
// @Tags ADMIN-权限管理
// @Description 管理员绑定角色
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @Param args body md.BindAdminRoleReq true "请求参数"
// @Success 200 {string} "success"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/bindAdminRole/ [POST]
func BindAdminRole(c *gin.Context) {
var req md.BindAdminRoleReq
err := c.ShouldBindJSON(&req)
if err != nil {
err = validate.HandleValidateErr(err)
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
}
err = svc.BindAdminRole(c, req)
if err != nil {
e.OutErr(c, e.ERR, err.Error())
return
}
e.OutSuc(c, "success", nil)
return
}

// AdminInfo
// @Summary 管理员信息
// @Tags ADMIN-权限管理
// @Description 管理员信息
// @param Authorization header string true "验证参数Bearer和token空格拼接"
// @Accept json
// @Produce json
// @param adm_id query string true "管理员id"
// @Success 200 {string} "具体看返回内容"
// @Failure 400 {object} md.Response "具体错误"
// @Router /admin/role/adminInfo [GET]
func AdminInfo(c *gin.Context) {
admId := c.DefaultQuery("adm_id", "")
engine := db.DBs[svc.GetMasterId(c)]
admDb := implement.NewAdminDb(engine)
admin, err := admDb.GetAdmin(utils.StrToInt(admId))
if err != nil {
e.OutErr(c, e.ERR_DB_ORM, err.Error())
return
}
admin.Password = ""
e.OutSuc(c, map[string]interface{}{
"info": admin,
"state": []map[string]interface{}{
{
"name": enum.RoleState(enum.RoleStateForNormal).String(),
"value": enum.RoleStateForNormal,
},
{
"name": enum.RoleState(enum.RoleStateForFreeze).String(),
"value": enum.RoleStateForFreeze,
},
},
}, nil)
return
}

+ 262
- 0
app/lib/alipay/api.go Целия файл

@@ -0,0 +1,262 @@
package alipay

import (
"applet/app/cfg"
"applet/app/pay/md"
"applet/app/utils/logx"
"fmt"

"github.com/iGoogle-ink/gopay"
"github.com/iGoogle-ink/gopay/alipay"
)

// TradeAppPay is 支付宝APP支付
// 抖音头条小程序使用APP调起
func TradeAppPay(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS string, paySet *md.PayData) (string, error) {
//初始化支付宝客户端
// appID 是在支付宝申请的APPID
// priKey 是支付宝私钥
// subject 是支付订单的主题
// orderID 是智莺这边生成的订单id
// amount 是付费金额
// notiURL 通知地址url
// passback_params 回调通知参数

client := alipay.NewClient(appID, priKey, true)
client.DebugSwitch = gopay.DebugOn
//判断密钥的类型
rsa_type := alipay.RSA2
pkcs_type := alipay.PKCS1
if RSA == "1" {
rsa_type = alipay.RSA
}
if PKCS == "1" {
pkcs_type = alipay.PKCS8
}
if paySet.PayAliUseType == "1" {
rsa_type = alipay.RSA2
pkcs_type = alipay.PKCS8
}
//配置公共参数
client.SetCharset("utf-8").
SetSignType(rsa_type).
SetPrivateKeyType(pkcs_type)
if notiURL != "" {
client.SetNotifyUrl(notiURL)
}
//新支付宝支付
if paySet.PayAliUseType == "1" {
appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAppCertSn)
fmt.Println("-应用-")
fmt.Println(appCertSN)
if err != nil {
fmt.Println(err)
return "", err
}
if appCertSN == "" {
fmt.Println(err)
return "", err
}
client.SetAppCertSN(appCertSN)
//aliPayRootCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayRootCertSn)
aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6"
client.SetAliPayRootCertSN(aliPayRootCertSN)
aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayrsaPublicKey)
fmt.Println("-公钥-")
fmt.Println(aliPayPublicCertSN)

if err != nil {
fmt.Println(err)
return "", err
}
if aliPayPublicCertSN == "" {
fmt.Println(err)
return "", err
}
client.SetAliPayPublicCertSN(aliPayPublicCertSN)
}
fmt.Println(client)
//请求参数
body := make(gopay.BodyMap)
body.Set("subject", subject)
body.Set("body", subject)
body.Set("out_trade_no", orderID)
body.Set("total_amount", amount)
body.Set("timeout_express", "30m")

// body.Set("passback_params", orderID)
//手机APP支付参数请求
payParam, err := client.TradeAppPay(body)
if err != nil {
return "", logx.Warn(err)
}
return payParam, nil
}

// TradeAppPay is 支付宝H5支付
func TradeWapPay(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS, page_url string, paySet *md.PayData) (string, error) {
fmt.Println("notifyURL is:>>>>>>>>>>", notiURL)
//aliPayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wn1sU/8Q0rYLlZ6sq3enrPZw2ptp6FecHR2bBFLjJ+sKzepROd0bKddgj+Mr1ffr3Ej78mLdWV8IzLfpXUi945DkrQcOUWLY0MHhYVG2jSs/qzFfpzmtut2Cl2TozYpE84zom9ei06u2AXLMBkU6VpznZl+R4qIgnUfByt3Ix5b3h4Cl6gzXMAB1hJrrrCkq+WvWb3Fy0vmk/DUbJEz8i8mQPff2gsHBE1nMPvHVAMw1GMk9ImB4PxucVek4ZbUzVqxZXphaAgUXFK2FSFU+Q+q1SPvHbUsjtIyL+cLA6H/6ybFF9Ffp27Y14AHPw29+243/SpMisbGcj2KD+evBwIDAQAB"
privateKey := priKey
//判断密钥的类型
rsa_type := alipay.RSA2
pkcs_type := alipay.PKCS1
if RSA == "1" {
rsa_type = alipay.RSA
}
if PKCS == "1" {
pkcs_type = alipay.PKCS8
}
if paySet.PayAliUseType == "1" {
rsa_type = alipay.RSA2
pkcs_type = alipay.PKCS8
}
//初始化支付宝客户端
// appId:应用ID
// privateKey:应用秘钥
// isProd:是否是正式环境
client := alipay.NewClient(appID, privateKey, true)
//配置公共参数
client.SetCharset("utf-8").
SetSignType(rsa_type).
SetPrivateKeyType(pkcs_type).
SetReturnUrl(page_url).
SetNotifyUrl(notiURL)
//新支付宝支付
if paySet.PayAliUseType == "1" {
appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAppCertSn)
if err != nil {
fmt.Println("appCertSn_err:>>>>>>>>", err)
return "", err
}
if appCertSN == "" {
fmt.Println(err)
return "", err
}
client.SetAppCertSN(appCertSN)
//aliPayRootCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayRootCertSn)
//if err != nil {
//
// fmt.Println("rootcertsn_err:>>>>>>>>>>", err)
// fmt.Println("rootcertsn_err:>>>>>>>>>>", cfg.WxappletFilepath.URL)
// fmt.Println("rootcertsn_err:>>>>>>>>>>", paySet.PayAlipayRootCertSn)
// return "", err
//}
//if aliPayRootCertSN == "" {
// fmt.Println(err)
// return "", err
//}
aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6"
client.SetAliPayRootCertSN(aliPayRootCertSN)
aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayrsaPublicKey)
if err != nil {
fmt.Println("publicCertSn_err:>>>>>>>>>>>", err)
return "", err
}
if aliPayPublicCertSN == "" {
fmt.Println(err)
return "", err
}
client.SetAliPayPublicCertSN(aliPayPublicCertSN)
}
//请求参数
body := make(gopay.BodyMap)
body.Set("subject", subject)
body.Set("out_trade_no", orderID)
// quit_url is 用户付款中途退出返回商户网站的地址
body.Set("quit_url", notiURL)
body.Set("total_amount", amount)
// product_code is 销售产品码,商家和支付宝签约的产品码
body.Set("product_code", "QUICK_WAP_WAY")
//手机网站支付请求
payUrl, err := client.TradeWapPay(body)
if err != nil {
return "", logx.Warn(err)

}
return payUrl, nil
}

// TradeAppPay is 支付宝小程序本身支付
func TradeCreate(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS string, paySet *md.PayData) (*alipay.TradeCreateResponse, error) {
//aliPayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wn1sU/8Q0rYLlZ6sq3enrPZw2ptp6FecHR2bBFLjJ+sKzepROd0bKddgj+Mr1ffr3Ej78mLdWV8IzLfpXUi945DkrQcOUWLY0MHhYVG2jSs/qzFfpzmtut2Cl2TozYpE84zom9ei06u2AXLMBkU6VpznZl+R4qIgnUfByt3Ix5b3h4Cl6gzXMAB1hJrrrCkq+WvWb3Fy0vmk/DUbJEz8i8mQPff2gsHBE1nMPvHVAMw1GMk9ImB4PxucVek4ZbUzVqxZXphaAgUXFK2FSFU+Q+q1SPvHbUsjtIyL+cLA6H/6ybFF9Ffp27Y14AHPw29+243/SpMisbGcj2KD+evBwIDAQAB"
privateKey := priKey
rsa_type := alipay.RSA2
pkcs_type := alipay.PKCS1
if RSA == "1" {
rsa_type = alipay.RSA
}
if PKCS == "1" {
pkcs_type = alipay.PKCS8
}
if paySet.PayAliUseType == "1" {
rsa_type = alipay.RSA2
pkcs_type = alipay.PKCS8
}
//初始化支付宝客户端
// appId:应用ID
// privateKey:应用私钥,支持PKCS1和PKCS8
// isProd:是否是正式环境
client := alipay.NewClient(appID, privateKey, true)
//配置公共参数
client.SetCharset("utf-8").
SetSignType(rsa_type).
SetPrivateKeyType(pkcs_type).
SetNotifyUrl(notiURL)
if paySet.PayAliUseType == "1" {
appCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAppCertSn)
if err != nil {
fmt.Println(err)
return nil, err
}
if appCertSN == "" {
fmt.Println(err)
return nil, err
}
client.SetAppCertSN(appCertSN)
//aliPayRootCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayRootCertSn)
//if err != nil {
// fmt.Println(err)
// return nil, err
//}
//if aliPayRootCertSN == "" {
// fmt.Println(err)
// return nil, err
//}
aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6"
client.SetAliPayRootCertSN(aliPayRootCertSN)
aliPayPublicCertSN, err := alipay.GetCertSN(cfg.WxappletFilepath.URL + "/" + paySet.PayAlipayrsaPublicKey)
if err != nil {
fmt.Println(err)
return nil, err
}
if aliPayPublicCertSN == "" {
fmt.Println(err)
return nil, err
}
client.SetAliPayPublicCertSN(aliPayPublicCertSN)
}
//请求参数
body := make(gopay.BodyMap)
body.Set("subject", subject)
// 支付宝小程序支付时 buyer_id 为必传参数,需要提前获取,获取方法如下两种
// 1、alipay.SystemOauthToken() 返回取值:rsp.SystemOauthTokenResponse.UserId
// 2、client.SystemOauthToken() 返回取值:aliRsp.SystemOauthTokenResponse.UserId
buyer_id, err := client.SystemOauthToken(body)
if err != nil {
return nil, logx.Warn(err)
}
body.Set("buyer_id", buyer_id)
body.Set("out_trade_no", orderID)
body.Set("total_amount", amount)
//创建订单
aliRsp, err := client.TradeCreate(body)

if err != nil {
return nil, logx.Warn(err)
}
logx.Warn("aliRsp:", *aliRsp)
logx.Warn("aliRsp.TradeNo:", aliRsp.Response.TradeNo)
return aliRsp, nil

}

+ 39
- 0
app/lib/auth/auth.go Целия файл

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

import (
"errors"
"github.com/dgrijalva/jwt-go"
"time"
)

// GenToken 生成JWT
func GenToken(admId int, username string) (string, error) {
// 创建一个我们自己的声明
c := JWTUser{
AdmId: admId,
Username: username,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
Issuer: "advertisement", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// 使用指定的secret签名并获得完整的编码后的字符串token
return token.SignedString(Secret)
}

// ParseToken 解析JWT
func ParseToken(tokenString string) (*JWTUser, error) {
// 解析token
token, err := jwt.ParseWithClaims(tokenString, &JWTUser{}, func(token *jwt.Token) (i interface{}, err error) {
return Secret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*JWTUser); ok && token.Valid { // 校验token
return claims, nil
}
return nil, errors.New("invalid token")
}

+ 19
- 0
app/lib/auth/base.go Целия файл

@@ -0,0 +1,19 @@
package auth

import (
"time"

"github.com/dgrijalva/jwt-go"
)

// TokenExpireDuration is jwt 过期时间
const TokenExpireDuration = time.Hour * 24

var Secret = []byte("micro_group_admin")

// JWTUser 如果想要保存更多信息,都可以添加到这个结构体中
type JWTUser struct {
AdmId int `json:"adm_id"`
Username string `json:"username"`
jwt.StandardClaims
}

+ 16
- 0
app/lib/qiniu/bucket_create.go Целия файл

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

import (
"github.com/qiniu/api.v7/v7/auth"
"github.com/qiniu/api.v7/v7/storage"
)

func BucketCreate() error {
mac := auth.New(AK, SK)
cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}
bucketManager := storage.NewBucketManager(mac, &cfg)
return bucketManager.CreateBucket("", storage.RIDHuanan)
}

+ 18
- 0
app/lib/qiniu/bucket_delete.go Целия файл

@@ -0,0 +1,18 @@
package qiniu

import (
"github.com/qiniu/api.v7/v7/auth"
"github.com/qiniu/api.v7/v7/storage"
)

func BucketDelete(bucketName string) error {
mac := auth.New(AK, SK)

cfg := storage.Config{
// 是否使用https域名进行资源管理
UseHTTPS: false,
}

bucketManager := storage.NewBucketManager(mac, &cfg)
return bucketManager.DropBucket(bucketName)
}

+ 18
- 0
app/lib/qiniu/bucket_get_domain.go Целия файл

@@ -0,0 +1,18 @@
package qiniu

import (
"github.com/qiniu/api.v7/v7/auth"
"github.com/qiniu/api.v7/v7/storage"
)

func BucketGetDomain(bucketName string) (string, error) {
mac := auth.New(AK, SK)

cfg := storage.Config{UseHTTPS: false}
bucketManager := storage.NewBucketManager(mac, &cfg)
b, err := bucketManager.ListBucketDomains(bucketName)
if err != nil {
return "", err
}
return b[0].Domain, nil
}

+ 22
- 0
app/lib/qiniu/init.go Целия файл

@@ -0,0 +1,22 @@
package qiniu

import (
"applet/app/utils"
)

var (
AK = "MmxNdai23egjNUHjdzEVaTPdPCIbWzENz9BQuak3"
SK = "mElaFlM9O16rXp-ihoQdJ9KOH56naKm3MoyQBA59"
BUCKET = "dev-fnuoos" // 桶子名称
BUCKET_SCHEME = "http"
BUCKET_REGION = "up-z2.qiniup.com"
Expires uint64 = 3600
)

func Init(ak, sk, bucket, region, scheme string) {
AK, SK, BUCKET, BUCKET_REGION, BUCKET_SCHEME = ak, sk, bucket, region, scheme
}

func Sign(t string) string {
return utils.Md5(AK + SK + t)
}

+ 55
- 0
app/lib/qiniu/req_img_upload.go Целия файл

@@ -0,0 +1,55 @@
package qiniu

import (
"time"

"github.com/qiniu/api.v7/v7/auth/qbox"
_ "github.com/qiniu/api.v7/v7/conf"
"github.com/qiniu/api.v7/v7/storage"

"applet/app/md"
"applet/app/utils"
)

// 请求图片上传地址信息
func ReqImgUpload(f *md.FileCallback, callbackUrl string) interface{} {
if ext := utils.FileExt(f.FileName); ext == "png" || ext == "jpg" || ext == "jpeg" || ext == "gif" || ext == "bmp" || ext == "webp" {
f.Width = "$(imageInfo.width)"
f.Height = "$(imageInfo.height)"
}
f.Provider = "qiniu"
f.FileSize = "$(fsize)"
f.Hash = "$(etag)"
f.Bucket = "$(bucket)"
f.Mime = "$(mimeType)"
f.Time = utils.Int64ToStr(time.Now().Unix())
f.Sign = Sign(f.Time)
putPolicy := storage.PutPolicy{
Scope: BUCKET + ":" + f.FileName, // 使用覆盖方式时候必须请求里面有key,否则报错
Expires: Expires,
ForceSaveKey: true,
SaveKey: f.FileName,
MimeLimit: "image/*", // 只允许上传图片
CallbackURL: callbackUrl,
CallbackBody: utils.SerializeStr(f),
CallbackBodyType: "application/json",
}
return &struct {
Method string `json:"method"`
Key string `json:"key"`
Host string `json:"host"`
Token string `json:"token"`
}{Key: f.FileName, Method: "POST", Host: BUCKET_SCHEME + "://" + BUCKET_REGION, Token: putPolicy.UploadToken(qbox.NewMac(AK, SK))}
}

/*
form表单上传
地址 : http://upload-z2.qiniup.com
header
- Content-Type : multipart/form-data

body :
- key : 文件名
- token : 生成token
- file : 待上传文件
*/

+ 33
- 0
app/lib/validate/validate_comm.go Целия файл

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

import (
"applet/app/e"
"applet/app/utils"
"applet/app/utils/logx"
"encoding/json"
"fmt"
"github.com/go-playground/validator/v10"
)

func HandleValidateErr(err error) error {
switch err.(type) {
case *json.UnmarshalTypeError:
return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误")
case validator.ValidationErrors:
errs := err.(validator.ValidationErrors)
transMsgMap := errs.Translate(utils.ValidatorTrans) // utils.ValidatorTrans \app\utils\validator_err_trans.go::ValidatorTransInit初始化获得
transMsgOne := transMsgMap[GetOneKeyOfMapString(transMsgMap)]
return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne)
default:
_ = logx.Error(err)
return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err))
}
}

// GetOneKeyOfMapString 取出Map的一个key
func GetOneKeyOfMapString(collection map[string]string) string {
for k := range collection {
return k
}
return ""
}


+ 305
- 0
app/lib/wxpay/api.go Целия файл

@@ -0,0 +1,305 @@
package wxpay

import (
"applet/app/utils"
"applet/app/utils/logx"
"fmt"
"github.com/iGoogle-ink/gopay"
"github.com/iGoogle-ink/gopay/pkg/util"
"github.com/iGoogle-ink/gopay/wechat"
v3 "github.com/iGoogle-ink/gopay/wechat/v3"
"strconv"
"time"
)

func NewClient(appId, mchId, apiKey string, isProd bool) *wechat.Client {
// 初始化微信客户端
// appId:应用ID
// mchId:商户ID
// apiKey:API秘钥值
// isProd:是否是正式环境
client := wechat.NewClient(appId, mchId, apiKey, isProd)
// 打开Debug开关,输出请求日志,默认关闭
client.DebugSwitch = gopay.DebugOn
// 设置国家:不设置默认 中国国内
// wechat.China:中国国内
// wechat.China2:中国国内备用
// wechat.SoutheastAsia:东南亚
// wechat.Other:其他国家
client.SetCountry(wechat.China)
// 添加微信证书 Path 路径
// certFilePath:apiclient_cert.pem 路径
// keyFilePath:apiclient_key.pem 路径
// pkcs12FilePath:apiclient_cert.p12 路径
// 返回err
//client.AddCertFilePath()

// 添加微信证书内容 Content
// certFileContent:apiclient_cert.pem 内容
// keyFileContent:apiclient_key.pem 内容
// pkcs12FileContent:apiclient_cert.p12 内容
// 返回err
//client.AddCertFileContent()
return client
}

// TradeAppPay is 微信APP支付
func TradeAppPay(client *wechat.Client, subject, orderID, amount, notifyUrl string) (map[string]string, error) {
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("nonce_str", util.GetRandomString(32)).
Set("body", subject).
Set("out_trade_no", orderID).
Set("total_fee", amount).
Set("spbill_create_ip", "127.0.0.1").
Set("notify_url", notifyUrl).
Set("trade_type", wechat.TradeType_App).
Set("sign_type", wechat.SignType_MD5)
/*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
// 预下单
wxRsp, err := client.UnifiedOrder(bm)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
//if !ok {
// return nil, errors.New("验签失败")
//}
timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
res := map[string]string{
"appid": client.AppId,
"partnerid": client.MchId,
"prepayid": wxRsp.PrepayId,
"sign": paySign,
"package": "Sign=WXPay",
"noncestr": wxRsp.NonceStr,
"timestamp": timeStamp,
}
return res, nil
}

// TradeAppPay is 微信H5支付
func TradeH5Pay(client *wechat.Client, subject, orderID, amount, notifyUrl, ip string) (map[string]string, error) {
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("nonce_str", util.GetRandomString(32)).
Set("body", subject).
Set("out_trade_no", orderID).
Set("total_fee", amount).
//Set("spbill_create_ip", "121.196.29.49").
Set("spbill_create_ip", ip).
Set("notify_url", notifyUrl).
Set("trade_type", wechat.TradeType_H5).
Set("sign_type", wechat.SignType_MD5).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "Wap")
bm.Set("wap_url", "https://www.fumm.cc")
bm.Set("wap_name", "zyos")
})
})
/*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
// 预下单
fmt.Println(bm)
wxRsp, err := client.UnifiedOrder(bm)
fmt.Println(wxRsp)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
packages := "prepay_id=" + wxRsp.PrepayId
paySign := wechat.GetH5PaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey)
fmt.Println("paySign===", paySign)
r := map[string]string{
"redirect_url": wxRsp.MwebUrl,
}
return r, nil
}

// TradeMiniProgPay is 微信小程序支付 ☑️
func TradeMiniProgPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) {
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("nonce_str", util.GetRandomString(32)).
Set("body", subject).
Set("openid", openid).
Set("out_trade_no", orderID).
Set("total_fee", amount).
Set("spbill_create_ip", "127.0.0.1").
Set("notify_url", notifyUrl).
Set("trade_type", wechat.TradeType_Mini).
Set("sign_type", wechat.SignType_MD5)
// 预下单
wxRsp, err := client.UnifiedOrder(bm)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
packages := "prepay_id=" + wxRsp.PrepayId
paySign := wechat.GetMiniPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey)
res := map[string]string{
"appId": client.AppId,
"paySign": paySign,
"signType": wechat.SignType_MD5,
"package": packages,
"nonceStr": wxRsp.NonceStr,
"timeStamp": timeStamp,
}
return res, nil
}

// TradeAppPayV3 is 微信APP支付v3
func TradeAppPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (map[string]string, error) {
// 初始化 BodyMap
amountNew := utils.AnyToFloat64(amount) * 100
bm := make(gopay.BodyMap)
bm.Set("nonce_str", util.GetRandomString(32)).
Set("body", subject).
Set("out_trade_no", orderID).
Set("total_fee", amountNew).
Set("spbill_create_ip", "127.0.0.1").
Set("notify_url", notifyUrl).
Set("trade_type", wechat.TradeType_App).
Set("sign_type", wechat.SignType_MD5)
/*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
//// 预下单
//wxRsp, err := v3.UnifiedOrder(bm)
//if err != nil {
// _ = logx.Warn(err)
// return nil, err
//}
//_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
//if err != nil {
// _ = logx.Warn(err)
// return nil, err
//}
////if !ok {
//// return nil, errors.New("验签失败")
////}
//timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
//paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
//res := map[string]string{
// "appid": client.AppId,
// "partnerid": client.MchId,
// "prepayid": wxRsp.PrepayId,
// "sign": paySign,
// "package": "Sign=WXPay",
// "noncestr": wxRsp.NonceStr,
// "timestamp": timeStamp,
//}
//return res, nil
return nil, nil
}

//// TradeJSAPIPay is 微信JSAPI支付
func TradeJSAPIPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) {
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("nonce_str", util.GetRandomString(32)).
Set("body", subject).
Set("out_trade_no", orderID).
Set("total_fee", amount).
Set("spbill_create_ip", "121.196.29.49").
Set("notify_url", notifyUrl).
Set("trade_type", wechat.TradeType_JsApi).
Set("sign_type", wechat.SignType_MD5).
Set("openid", openid).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "Wap")
bm.Set("wap_url", "https://www.fumm.cc")
bm.Set("wap_name", "zyos")
})
})
// 预下单
wxRsp, err := client.UnifiedOrder(bm)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp)
if err != nil {
_ = logx.Warn(err)
return nil, err
}
//if !ok {
// return nil, errors.New("验签失败")
//}
timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
//paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
packages := "prepay_id=" + wxRsp.PrepayId
paySign := wechat.GetJsapiPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey)

logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId)
logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId)
logx.Info("wxRsp.PrepayId:" + openid)
res := map[string]string{
"appid": client.AppId,
"partnerid": client.MchId,
"prepayid": wxRsp.PrepayId,
"sign": paySign,
"package": "prepay_id=" + wxRsp.PrepayId,
"noncestr": wxRsp.NonceStr,
"timestamp": timeStamp,
}
return res, nil
}

// TradeH5PayV3 is 微信H5支付v3
func TradeH5PayV3(client *wechat.Client, subject, orderID, amount, notifyUrl string) (string, error) {
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("nonce_str", util.GetRandomString(32)).
Set("body", subject).
Set("out_trade_no", orderID).
Set("total_fee", amount).
Set("spbill_create_ip", "127.0.0.1").
Set("notify_url", notifyUrl).
Set("trade_type", wechat.TradeType_App).
Set("device_info", "WEB").
Set("sign_type", wechat.SignType_MD5).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "Wap")
bm.Set("wap_url", "https://www.fumm.cc")
bm.Set("wap_name", "H5测试支付")
})
}) /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/
// 预下单
wxRsp, err := client.UnifiedOrder(bm)
if err != nil {
_ = logx.Warn(err)
return "", err
}
// ====APP支付 paySign====
timeStamp := strconv.FormatInt(time.Now().Unix(), 10)
// 获取APP支付的 paySign
// 注意:package 参数因为是固定值,无需开发者再传入
// appId:AppID
// partnerid:partnerid
// nonceStr:随机字符串
// prepayId:统一下单成功后得到的值
// signType:签名方式,务必与统一下单时用的签名方式一致
// timeStamp:时间
// apiKey:API秘钥值
paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey)
return paySign, nil
}

// TradeMiniProgPayV3 is 微信小程序支付v3
func TradeMiniProgPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (string, error) {
return "", nil
}

+ 18
- 0
app/md/app_redis_key.go Целия файл

@@ -0,0 +1,18 @@
package md

// 缓存key统一管理, %s格式化为masterId
const (
JwtTokenKey = "%s:advertisement_jwt_token:%s" // jwt, 占位符:ip, admin:id

JwtTokenCacheTime = 3600 * 24

AppCfgCacheKey = "%s:cfg_cache:%s" // 占位符: masterId, key的第一个字母

UserFinValidUpdateLock = "%s:user_fin_valid_update_lock:%s" // 用户余额更新锁(能拿到锁才能更新余额)

AdminRolePermissionCacheTime = 3600 * 24 * 0.5

KEY_SYS_CFG_CACHE = "sys_cfg_cache"

CfgCacheTime = 86400
)

+ 10
- 0
app/md/md_login.go Целия файл

@@ -0,0 +1,10 @@
package md

type LoginReq struct {
UserName string `json:"username" binding:"required" label:"登录账号"`
PassWord string `json:"password" binding:"required" label:"登录密码"`
}

type LoginResponse struct {
Token string `json:"token"`
}

+ 91
- 0
app/md/md_role.go Целия файл

@@ -0,0 +1,91 @@
package md

import (
"applet/app/db/model"
)

type RoleListResp struct {
Data model.Role `json:"data"`
AdminList []struct {
Name string `json:"name"`
} `json:"admin_list"`
}

type UpdateRoleStateReq struct {
RoleId int `json:"role_id" binding:"required" label:"id"`
State int `json:"state" binding:"required" label:"状态"`
}

type AddRoleReq struct {
Name string `json:"name" binding:"required" label:"名称"`
Memo string `json:"memo" binding:"required" label:"备注"`
}

type UpdateRoleReq struct {
RoleId int `json:"role_id" binding:"required" label:"id"`
Name string `json:"name" binding:"required" label:"名称"`
Memo string `json:"memo" binding:"required" label:"备注"`
}

type RoleBindPermissionGroupReq struct {
RoleId int `json:"role_id" binding:"required" label:"id"`
PermissionIds []int `json:"permission_ids" label:"权限组id"`
}

type PermissionGroupListResp struct {
Id int `json:"id"`
Name string `json:"name"` //菜单名称
Key string `json:"key"` //唯一标识符
State int `json:"state"`
ParentId int `json:"parent_id"` //父级id,为0则代表没有父级
CreateAt string `json:"create_at"`
UpdateAt string `json:"update_at"`
IsCheck bool `json:"is_check"` //是否用用
SubPermissionGroupList []PermissionGroupListResp `json:"sub_permission_group_list"` //子集菜单
}

type AdminListReq struct {
Limit int `json:"limit"`
Page int `json:"page" `
UserName string `json:"username"`
State int `json:"state"`
}

type AdminListResp struct {
AdmId int `json:"adm_id"`
Username string `json:"username"`
State int `json:"state"`
IsSuperAdministrator int `json:"is_super_administrator"`
Memo string `json:"memo"`
CreateAt string `json:"create_at"`
UpdateAt string `json:"update_at"`
RoleList []string `json:"role_list"`
}

type UpdateAdminStateReq struct {
AdmId int `json:"adm_id" binding:"required" label:"管理员id"`
State int `json:"state" binding:"required" label:"状态"`
}

type AddAdminReq struct {
Username string `json:"username" binding:"required" label:"名称"`
Password string `json:"password" binding:"required" label:"密码"`
Memo string `json:"memo" label:"备注"`
}

type UpdateAdminReq struct {
AdmId int `json:"adm_id" binding:"required" label:"管理员id"`
Username string `json:"username" binding:"required" label:"名称"`
Password string `json:"password" binding:"required" label:"密码"`
Memo string `json:"memo" label:"备注"`
}

type BindAdminRoleReq struct {
AdmId int `json:"adm_id" binding:"required" label:"管理员id"`
RoleIds []int `json:"role_ids" label:"角色id"`
}

type BindAdminWithEnterpriseReq struct {
AdmId int `json:"adm_id" binding:"required" label:"管理员id"`
Ids []int `json:"ids" label:"记录id"`
}

+ 5
- 0
app/md/md_white_uri.go Целия файл

@@ -0,0 +1,5 @@
package md

var WhiteUri = []string{
"/api/admin/comm/getMenuList",
}

+ 42
- 0
app/md/split_db.go Целия файл

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

import (
"regexp"

"xorm.io/xorm"
)

type DbInfo struct {
User string
Psw string
Name string
Host string
}

func SplitDbInfo(eg *xorm.Engine) *DbInfo {
if eg == nil {
return &DbInfo{
User: "nil",
Psw: "nil",
Host: "nil",
Name: "nil",
}
}
pattern := `(\w+):(.*)@tcp\(([\w\.\-\:\_]+)\)\/(\w+)`
reg := regexp.MustCompile(pattern).FindStringSubmatch(eg.DataSourceName())

if len(reg) < 5 {
return &DbInfo{
User: "unknown",
Psw: "unknown",
Host: "unknown",
Name: "unknown",
}
}
return &DbInfo{
User: reg[1],
Psw: reg[2],
Host: reg[3],
Name: reg[4],
}
}

+ 31
- 0
app/mw/mw_access_log.go Целия файл

@@ -0,0 +1,31 @@
package mw

import (
"time"

"github.com/gin-gonic/gin"
"go.uber.org/zap"

"applet/app/utils/logx"
)

// access log
func AccessLog(c *gin.Context) {
start := time.Now()
c.Next()
cost := time.Since(start)

logx.Info(c.Request.URL.Path)

logger := &zap.Logger{}
logger.Info(c.Request.URL.Path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", c.Request.URL.Path),
zap.String("query", c.Request.URL.RawQuery),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}

+ 26
- 0
app/mw/mw_auth.go Целия файл

@@ -0,0 +1,26 @@
package mw

import (
"applet/app/e"
"applet/app/svc"
"github.com/gin-gonic/gin"
)

// 检查权限, 签名等等
func Auth(c *gin.Context) {
admin, err := svc.CheckUser(c)
if err != nil {
switch err.(type) {
case e.E:
err1 := err.(e.E)
e.OutErr(c, err1.Code, err1.Error())
return
default:
e.OutErr(c, e.ERR_TOKEN_AUTH, err.Error())
return
}
}
// 将当前请求的username信息保存到请求的上下文c上
c.Set("admin", admin)
c.Next()
}

+ 30
- 0
app/mw/mw_breaker.go Целия файл

@@ -0,0 +1,30 @@
package mw

import (
"errors"
"net/http"
"strconv"

"github.com/afex/hystrix-go/hystrix"
"github.com/gin-gonic/gin"
)

// 熔断器, 此组件需要在gin.Recovery中间之前进行调用, 否则可能会导致panic时候, 无法recovery, 正确顺序如下
//r.Use(BreakerWrapper)
//r.Use(gin.Recovery())
func Breaker(c *gin.Context) {
name := c.Request.Method + "-" + c.Request.RequestURI
hystrix.Do(name, func() error {
c.Next()
statusCode := c.Writer.Status()
if statusCode >= http.StatusInternalServerError {
return errors.New("status code " + strconv.Itoa(statusCode))
}
return nil
}, func(e error) error {
if e == hystrix.ErrCircuitOpen {
c.String(http.StatusAccepted, "请稍后重试") //todo 修改报错方法
}
return e
})
}

+ 17
- 0
app/mw/mw_change_header.go Целия файл

@@ -0,0 +1,17 @@
package mw

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

// 修改传过来的头部字段
func ChangeHeader(c *gin.Context) {
appvserison := c.GetHeader("AppVersionName")
if appvserison == "" {
appvserison = c.GetHeader("app_version_name")
}
if appvserison != "" {
c.Request.Header.Add("app_version_name", appvserison)
}
c.Next()
}

+ 34
- 0
app/mw/mw_check_sign.go Целия файл

@@ -0,0 +1,34 @@
package mw

import (
"applet/app/e"
"applet/app/utils"
"bytes"
"fmt"
"github.com/gin-gonic/gin"
"io/ioutil"
)

// CheckSign is 中间件 用来检查签名
func CheckSign(c *gin.Context) {

bools := utils.SignCheck(c)
if bools == false {
e.OutErr(c, 400, e.NewErr(400, "签名校验错误,请求失败"))
return
}
c.Next()
}
func CheckBody(c *gin.Context) {
if utils.GetApiVersion(c) > 0 {
body, _ := ioutil.ReadAll(c.Request.Body)
fmt.Println(string(body))
if string(body) != "" {
str := utils.ResultAesDecrypt(c, string(body))
if str != "" {
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(str)))
}
}
}
c.Next()
}

+ 29
- 0
app/mw/mw_cors.go Целия файл

@@ -0,0 +1,29 @@
package mw

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

// cors跨域
func Cors(c *gin.Context) {
// 放行所有OPTIONS方法
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}

origin := c.Request.Header.Get("Origin") // 请求头部
if origin != "" {
c.Header("Access-Control-Allow-Origin", origin) // 这是允许访问来源域
c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
// header的类型
c.Header("Access-Control-Allow-Headers", "Authorization,Content-Length,X-CSRF-Token,Token,session,X_Requested_With,Accept,Origin,Host,Connection,Accept-Encoding,Accept-Language,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Pragma,X-Mx-ReqToken")
// 允许跨域设置,可以返回其他子段
// 跨域关键设置 让浏览器可以解析
c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true
c.Set("Content-Type", "Application/json") // 设置返回格式是json
}
c.Next()
}

+ 136
- 0
app/mw/mw_csrf.go Целия файл

@@ -0,0 +1,136 @@
package mw

import (
"crypto/sha1"
"encoding/base64"
"errors"
"io"

"github.com/dchest/uniuri"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)

// csrf,xsrf检查
const (
csrfSecret = "csrfSecret"
csrfSalt = "csrfSalt"
csrfToken = "csrfToken"
)

var defaultIgnoreMethods = []string{"GET", "HEAD", "OPTIONS"}

var defaultErrorFunc = func(c *gin.Context) {
panic(errors.New("CSRF token mismatch"))
}

var defaultTokenGetter = func(c *gin.Context) string {
r := c.Request

if t := r.FormValue("_csrf"); len(t) > 0 {
return t
} else if t := r.URL.Query().Get("_csrf"); len(t) > 0 {
return t
} else if t := r.Header.Get("X-CSRF-TOKEN"); len(t) > 0 {
return t
} else if t := r.Header.Get("X-XSRF-TOKEN"); len(t) > 0 {
return t
}

return ""
}

// Options stores configurations for a CSRF middleware.
type Options struct {
Secret string
IgnoreMethods []string
ErrorFunc gin.HandlerFunc
TokenGetter func(c *gin.Context) string
}

func tokenize(secret, salt string) string {
h := sha1.New()
io.WriteString(h, salt+"-"+secret)
hash := base64.URLEncoding.EncodeToString(h.Sum(nil))

return hash
}

func inArray(arr []string, value string) bool {
inarr := false

for _, v := range arr {
if v == value {
inarr = true
break
}
}

return inarr
}

// Middleware validates CSRF token.
func Middleware(options Options) gin.HandlerFunc {
ignoreMethods := options.IgnoreMethods
errorFunc := options.ErrorFunc
tokenGetter := options.TokenGetter

if ignoreMethods == nil {
ignoreMethods = defaultIgnoreMethods
}

if errorFunc == nil {
errorFunc = defaultErrorFunc
}

if tokenGetter == nil {
tokenGetter = defaultTokenGetter
}

return func(c *gin.Context) {
session := sessions.Default(c)
c.Set(csrfSecret, options.Secret)

if inArray(ignoreMethods, c.Request.Method) {
c.Next()
return
}

salt, ok := session.Get(csrfSalt).(string)

if !ok || len(salt) == 0 {
errorFunc(c)
return
}

token := tokenGetter(c)

if tokenize(options.Secret, salt) != token {
errorFunc(c)
return
}

c.Next()
}
}

// GetToken returns a CSRF token.
func GetToken(c *gin.Context) string {
session := sessions.Default(c)
secret := c.MustGet(csrfSecret).(string)

if t, ok := c.Get(csrfToken); ok {
return t.(string)
}

salt, ok := session.Get(csrfSalt).(string)
if !ok {
salt = uniuri.New()
session.Set(csrfSalt, salt)
session.Save()
}
token := tokenize(secret, salt)
c.Set(csrfToken, token)

return token
}

+ 30
- 0
app/mw/mw_db.go Целия файл

@@ -0,0 +1,30 @@
package mw

import (
"applet/app/db"
"applet/app/e"
"applet/app/svc"
"errors"
"fmt"
"github.com/gin-gonic/gin"
)

// DB is 中间件 用来检查master_id是否有对应的数据库engine
func DB(c *gin.Context) {
fmt.Println(c.Request.Header)
masterID := svc.GetMasterId(c)
fmt.Println("master_id", masterID)
if masterID == "" {
e.OutErr(c, e.ERR_MASTER_ID, errors.New("not found master_id"))
return
}

_, ok := db.DBs[masterID]
if !ok {
e.OutErr(c, e.ERR_MASTER_ID, errors.New("not found master_id in DBs"))
return
}
fmt.Println("master_id", masterID)
c.Set("mid", masterID)
c.Next()
}

+ 58
- 0
app/mw/mw_limiter.go Целия файл

@@ -0,0 +1,58 @@
package mw

import (
"bytes"
"io/ioutil"

"github.com/gin-gonic/gin"

"applet/app/utils"
"applet/app/utils/cache"
)

// 限流器
func Limiter(c *gin.Context) {
limit := 100 // 限流次数
ttl := 1 // 限流过期时间
ip := c.ClientIP()
// 读取token或者ip
token := c.GetHeader("Authorization")
// 判断是否已经超出限额次数
method := c.Request.Method
host := c.Request.Host
uri := c.Request.URL.String()

buf := make([]byte, 2048)
num, _ := c.Request.Body.Read(buf)
body := buf[:num]
// Write body back
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
Md5 := utils.Md5(ip + token + method + host + uri + string(body))
if cache.Exists(Md5) {
c.AbortWithStatusJSON(429, gin.H{
"code": 429,
"msg": "don't repeat the request",
"data": struct{}{},
})
return
}
// 2s后没返回自动释放
go cache.SetEx(Md5, "0", ttl)
key := "LIMITER_" + ip
reqs, _ := cache.GetInt(key)
if reqs >= limit {
c.AbortWithStatusJSON(429, gin.H{
"code": 429,
"msg": "too many requests",
"data": struct{}{},
})
return
}
if reqs > 0 {
go cache.Incr(key)
} else {
go cache.SetEx(key, 1, ttl)
}
c.Next()
go cache.Del(Md5)
}

+ 57
- 0
app/mw/mw_recovery.go Целия файл

@@ -0,0 +1,57 @@
package mw

import (
"net"
"net/http"
"net/http/httputil"
"os"
"runtime/debug"
"strings"

"github.com/gin-gonic/gin"
"go.uber.org/zap"
)

func Recovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}

httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error))
c.Abort()
return
}

if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}

+ 74
- 0
app/router/router.go Целия файл

@@ -0,0 +1,74 @@
package router

import (
"applet/app/cfg"
"applet/app/hdl"
"applet/app/mw"
_ "applet/docs"
"github.com/gin-gonic/gin"
)

// 初始化路由
func Init() *gin.Engine {
// debug, release, test 项目阶段
mode := "release"
if cfg.Debug {
mode = "debug"
}
gin.SetMode(mode)
//创建一个新的启动器
r := gin.New()
r.Use(mw.ChangeHeader)

// 是否打印访问日志, 在非正式环境都打印
if mode != "release" {
r.Use(gin.Logger())
}
r.Use(gin.Recovery())
// r.Use(mw.Limiter)
//r.LoadHTMLGlob("static/html/*")

r.GET("/favicon.ico", func(c *gin.Context) {
c.Status(204)
})
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"code": 404, "msg": "page not found", "data": []struct{}{}})
})
r.NoMethod(func(c *gin.Context) {
c.JSON(405, gin.H{"code": 405, "msg": "method not allowed", "data": []struct{}{}})
})
r.Use(mw.Cors)
route(r.Group("/api"))
return r
}

func route(r *gin.RouterGroup) {
r.GET("/test", hdl.Demo)

r.Use(mw.DB) // 以下接口需要用到数据库
{
r.POST("/login", hdl.Login)
}
r.Use(mw.CheckBody) //body参数转换
r.Use(mw.CheckSign) //签名校验

r.Use(mw.Auth) // 以下接口需要JWT验证
rRole(r.Group("/role")) //权限管理
}

func rRole(r *gin.RouterGroup) {
r.GET("/roleList", hdl.RoleList) //角色列表
r.POST("/addRole", hdl.AddRole) //角色添加
r.POST("/roleBindPermissionGroup", hdl.RoleBindPermissionGroup) //角色绑定权限组
r.POST("/updateRoleState", hdl.UpdateRoleState) //修改角色状态
r.POST("/updateRole", hdl.UpdateRole) //修改角色状态
r.DELETE("/deleteRole/:id", hdl.DeleteRole) //删除角色
r.GET("/permissionGroupList", hdl.PermissionGroupList) //权限组列表
r.POST("/adminList", hdl.AdminList) //管理员列表
r.POST("/updateAdminState", hdl.UpdateAdminState) //修改管理员状态
r.POST("/updateAdmin", hdl.UpdateAdmin) //修改管理员信息
r.POST("/addAdmin", hdl.AddAdmin) //新增管理员
r.DELETE("/deleteAdmin/:adm_id", hdl.DeleteAdmin) //删除管理员
r.GET("/adminInfo", hdl.AdminInfo) //获取管理员信息
r.POST("/bindAdminRole", hdl.BindAdminRole) //绑定角色
}

+ 31
- 0
app/svc/svc_admin.go Целия файл

@@ -0,0 +1,31 @@
package svc

import (
"applet/app/db"
"applet/app/db/implement"
"github.com/gin-gonic/gin"
)

func AdminDelete(c *gin.Context, admIds []int) (err error) {
engine := db.DBs[GetMasterId(c)]
session := engine.NewSession()
defer session.Close()
session.Begin()
//1、删除 `admin`
adminDb := implement.NewAdminDb(engine)
_, err = adminDb.AdminDeleteBySession(session, admIds)
if err != nil {
_ = session.Rollback()
return
}

//2、删除 `admin_role`
adminRoleDb := implement.NewAdminRoleDb(engine)
_, err = adminRoleDb.AdminDeleteBySessionForAdmId(session, admIds)
if err != nil {
_ = session.Rollback()
return
}

return session.Commit()
}

+ 55
- 0
app/svc/svc_auth.go Целия файл

@@ -0,0 +1,55 @@
package svc

import (
"applet/app/db"
"applet/app/db/implement"
"applet/app/db/model"
"applet/app/lib/auth"
"errors"
"github.com/gin-gonic/gin"
"strings"
)

func GetUser(c *gin.Context) *model.Admin {
user, _ := c.Get("admin")
if user == nil {
return &model.Admin{
AdmId: 0,
Username: "",
Password: "",
State: 0,
IsSuperAdministrator: 0,
Memo: "",
CreateAt: "",
UpdateAt: "",
OpenId: "",
UnionId: "",
}
}
return user.(*model.Admin)
}

func CheckUser(c *gin.Context) (*model.Admin, error) {
token := c.GetHeader("Authorization")
if token == "" {
return nil, errors.New("token not exist")
}
// 按空格分割
parts := strings.SplitN(token, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
return nil, errors.New("token format error")
}
// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
mc, err := auth.ParseToken(parts[1])
if err != nil {
return nil, err
}

// 获取admin
adminDb := implement.NewAdminDb(db.DBs[GetMasterId(c)])
admin, err := adminDb.GetAdmin(mc.AdmId)
if err != nil {
return nil, err
}
return admin, nil
}

+ 11
- 0
app/svc/svc_db.go Целия файл

@@ -0,0 +1,11 @@
package svc

import (
"applet/app/db"
"github.com/gin-gonic/gin"
"xorm.io/xorm"
)

func MasterDb(c *gin.Context) *xorm.Engine {
return db.DBs[c.GetString("mid")]
}

+ 33
- 0
app/svc/svc_login.go Целия файл

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

import (
"applet/app/db/model"
"applet/app/lib/auth"
"applet/app/md"
"applet/app/utils/cache"
"applet/app/utils/logx"
)

func HandleLoginToken(cacheKey string, admin *model.Admin) (string, error) {
// 获取之前生成的token
token, err := cache.GetString(cacheKey)
if err != nil {
_ = logx.Error(err)
}
// 没有获取到
if err != nil || token == "" {
// 生成token
token, err = auth.GenToken(admin.AdmId, admin.Username)
if err != nil {
return "", err
}
// 缓存token
_, err = cache.SetEx(cacheKey, token, md.JwtTokenCacheTime)
if err != nil {
return "", err
}
return token, nil
}

return token, nil
}

+ 58
- 0
app/svc/svc_master.go Целия файл

@@ -0,0 +1,58 @@
package svc

import (
"fmt"
"github.com/gin-gonic/gin"
"strconv"
)

func GetMasterId(c *gin.Context) (masterId string) {
masterId = c.GetHeader("master_id")
if masterId != "" {
return
}

masterId = c.GetString("master_id")
if masterId == "" {
//TODO::通过域名查找masterId
//host := c.Request.Host

//fmt.Println("not found master_id found MasterId start")
//masterId = c.GetHeader("MasterId")
//if masterId == "" && c.GetHeader("Platform") == md.PLATFORM_WAP { // H5 要根据域名去获取mid
// hostList := strings.Split(c.Request.Host, ".")
// if isNumeric(hostList[0]) {
// masterId = hostList[0]
// } else if isNumeric(hostList[1]) {
// masterId = hostList[1]
// } else {
// // 自定义域名
// masterId = svc.GetWebSiteDomainMasterId(baseDb, md.PLATFORM_WAP, c.Request.Host)
// }
//}
//if masterId == "" && c.GetHeader("Platform") == md.PLATFORM_PC { // H5 要根据域名去获取mid
// hostList := strings.Split(c.Request.Host, ".")
// if isNumeric(hostList[0]) {
// masterId = hostList[0]
// } else if isNumeric(hostList[1]) {
// masterId = hostList[1]
// } else {
// // 自定义域名
// masterId = svc.GetWebSiteDomainMasterId(baseDb, md.PLATFORM_PC, c.Request.Host)
// }
//}
//if masterId == "" && c.GetHeader("Platform") == "" { // 无平台访问
// hostList := strings.Split(c.Request.Host, ".")
// if isNumeric(hostList[0]) {
// masterId = hostList[0]
// }
//}
}
fmt.Println("master_id:::::::", masterId)
return
}

func isNumeric(str string) bool {
_, err := strconv.ParseFloat(str, 64)
return err == nil
}

+ 85
- 0
app/svc/svc_redis_mutex_lock.go Целия файл

@@ -0,0 +1,85 @@
package svc

import (
"applet/app/md"
"applet/app/utils"
"applet/app/utils/cache"
"errors"
"fmt"
"math/rand"
"reflect"
"time"
)

const redisMutexLockExpTime = 15

// TryGetDistributedLock 分布式锁获取
// requestId 用于标识请求客户端,可以是随机字符串,需确保唯一
func TryGetDistributedLock(lockKey, requestId string, isNegative bool) bool {
if isNegative { // 多次尝试获取
retry := 1
for {
ok, err := cache.Do("SET", lockKey, requestId, "EX", redisMutexLockExpTime, "NX")
// 获取锁成功
if err == nil && ok == "OK" {
return true
}
// 尝试多次没获取成功
if retry > 10 {
return false
}
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
retry += 1
}
} else { // 只尝试一次
ok, err := cache.Do("SET", lockKey, requestId, "EX", redisMutexLockExpTime, "NX")
// 获取锁成功
if err == nil && ok == "OK" {
return true
}

return false
}
}

// ReleaseDistributedLock 释放锁,通过比较requestId,用于确保客户端只释放自己的锁,使用lua脚本保证操作的原子型
func ReleaseDistributedLock(lockKey, requestId string) (bool, error) {
luaScript := `
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end`

do, err := cache.Do("eval", luaScript, 1, lockKey, requestId)
fmt.Println(reflect.TypeOf(do))
fmt.Println(do)

if utils.AnyToInt64(do) == 1 {
return true, err
} else {
return false, err
}
}

func GetDistributedLockRequestId(prefix string) string {
return prefix + utils.IntToStr(rand.Intn(100000000))
}

// HandleBalanceDistributedLock 处理余额更新时获取锁和释放锁 如果加锁成功,使用语句 ` defer cb() ` 释放锁
func HandleBalanceDistributedLock(masterId, uid, requestIdPrefix string) (cb func(), err error) {
// 获取余额更新锁
balanceLockKey := fmt.Sprintf(md.UserFinValidUpdateLock, masterId, uid)
requestId := GetDistributedLockRequestId(requestIdPrefix)
balanceLockOk := TryGetDistributedLock(balanceLockKey, requestId, true)
if !balanceLockOk {
return nil, errors.New("系统繁忙,请稍后再试")
}

cb = func() {
_, _ = ReleaseDistributedLock(balanceLockKey, requestId)
}

return cb, nil
}

+ 184
- 0
app/svc/svc_role.go Целия файл

@@ -0,0 +1,184 @@
package svc

import (
"applet/app/db"
"applet/app/db/implement"
"applet/app/db/model"
"applet/app/md"
"applet/app/utils"
"applet/app/utils/cache"
"encoding/json"
"errors"
"github.com/gin-gonic/gin"
"regexp"
"strings"
"time"
)

func CheckUserRole(c *gin.Context, cacheKey, uri string, admId int) (isHasPermission bool, err error) {
uri = utils.UriFilterExcludeQueryString(uri) //去除uri中?后的query参数
isHasPermission = false
var rolePermission []string
var rolePermissionString string
rolePermissionString, _ = cache.GetString(cacheKey)

//TODO::判断是否在白名单中
if utils.InArr(uri, md.WhiteUri) {
isHasPermission = true
return
}

if rolePermissionString != "" {
//if false {
if err = json.Unmarshal([]byte(rolePermissionString), &rolePermission); err != nil {
return
}
} else {
adminDb := implement.NewAdminDb(db.DBs[GetMasterId(c)])
list, _, err1 := adminDb.GetAdminRolePermission(admId)
if err1 != nil {
return isHasPermission, err1
}
for _, v := range list {
rolePermission = append(rolePermission, v.Permission.Action)
}
marshal, err1 := json.Marshal(rolePermission)
if err1 != nil {
return isHasPermission, err1
}
rolePermissionString = string(marshal)
_, err = cache.SetEx(cacheKey, rolePermissionString, md.AdminRolePermissionCacheTime)
}

if utils.InArr(uri, rolePermission) {
isHasPermission = true
} else {
//正则匹配占位符情况
compileRegex := regexp.MustCompile("[0-9]+")
matchArr := compileRegex.FindAllString(uri, -1)
if len(matchArr) > 0 {
uri = strings.Replace(uri, matchArr[len(matchArr)-1], ":id", 1)
if utils.InArr(uri, rolePermission) {
isHasPermission = true
}
}
}
return
}

func DeleteRole(c *gin.Context, roleId int) (err error) {
session := db.Db.NewSession()
defer session.Close()
session.Begin()

//1、删除 `role`
roleDb := implement.NewRoleDb(db.DBs[GetMasterId(c)], roleId)
_, err = roleDb.RoleDeleteBySession(session, roleId)
if err != nil {
_ = session.Rollback()
return
}

//2、删除 `role_permission_group`
rolePermissionGroupDb := implement.NewRolePermissionGroupDb(db.DBs[GetMasterId(c)])
_, err = rolePermissionGroupDb.RolePermissionGroupDeleteForRoleBySession(session, roleId)
if err != nil {
_ = session.Rollback()
return
}

//3、删除 `admin_role`
adminRoleDb := implement.NewAdminRoleDb(db.DBs[GetMasterId(c)])
_, err = adminRoleDb.AdminRoleDeleteForRoleBySession(session, roleId)
if err != nil {
_ = session.Rollback()
return
}

return session.Commit()
}

func RoleBindPermissionGroup(c *gin.Context, req md.RoleBindPermissionGroupReq) (err error) {
session := db.Db.NewSession()
defer session.Close()
session.Begin()
//1、查询 `role`
roleDb := implement.NewRoleDb(db.DBs[GetMasterId(c)], req.RoleId)
role, err := roleDb.GetRole()
if err != nil {
return
}
if role == nil {
return errors.New("未查询到相应记录")
}

//1、删除 `role_permission_group`
rolePermissionGroupDb := implement.NewRolePermissionGroupDb(db.DBs[GetMasterId(c)])
_, err = rolePermissionGroupDb.RolePermissionGroupDeleteForRoleBySession(session, req.RoleId)
if err != nil {
_ = session.Rollback()
return
}

//2、新增 `role_permission_group``
var mm []*model.RolePermissionGroup
now := time.Now()
for _, v := range req.PermissionIds {
mm = append(mm, &model.RolePermissionGroup{
RoleId: role.Id,
GroupId: v,
CreateAt: now.Format("2006-01-02 15:04:05"),
UpdateAt: now.Format("2006-01-02 15:04:05"),
})
}
_, err = rolePermissionGroupDb.BatchAddRolePermissionGroupBySession(session, mm)
if err != nil {
_ = session.Rollback()
return
}

return session.Commit()
}

func BindAdminRole(c *gin.Context, req md.BindAdminRoleReq) (err error) {
session := db.Db.NewSession()
defer session.Close()
session.Begin()
//1、查询 `role`
adminDb := implement.NewAdminDb(db.DBs[GetMasterId(c)])
role, err := adminDb.GetAdmin(req.AdmId)
if err != nil {
return
}
if role == nil {
return errors.New("未查询到相应记录")
}

//1、删除 `admin_role`
adminRoleDb := implement.NewAdminRoleDb(db.DBs[GetMasterId(c)])
_, err = adminRoleDb.AdminRoleDeleteBySession(session, req.AdmId)
if err != nil {
_ = session.Rollback()
return
}

//2、新增 `删除 `admin_role``
var mm []*model.AdminRole
now := time.Now()
for _, v := range req.RoleIds {
mm = append(mm, &model.AdminRole{
AdmId: req.AdmId,
RoleId: v,
State: 1,
CreateAt: now.Format("2006-01-02 15:04:05"),
UpdateAt: now.Format("2006-01-02 15:04:05"),
})
}
_, err = adminRoleDb.BatchAddAdminRoleBySession(session, mm)
if err != nil {
_ = session.Rollback()
return
}

return session.Commit()
}

+ 100
- 0
app/svc/svc_sys_cfg_get.go Целия файл

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

import (
"github.com/gin-gonic/gin"
"xorm.io/xorm"

"applet/app/db"
"applet/app/md"
"applet/app/utils/cache"
)

// 单挑记录获取
func SysCfgGet(c *gin.Context, key string) string {
mid := c.GetString("mid")
eg := db.DBs[mid]
return db.SysCfgGetWithDb(eg, mid, key)
}

// 多条记录获取
func SysCfgFind(c *gin.Context, keys ...string) map[string]string {
var masterId string
if c == nil {
masterId = ""
} else {
masterId = c.GetString("mid")
}
tmp := SysCfgFindComm(masterId, keys...)
return tmp
}
func SysCfgFindComm(masterId string, keys ...string) map[string]string {
var eg *xorm.Engine
if masterId == "" {
eg = db.Db
} else {
eg = db.DBs[masterId]
}
res := map[string]string{}
//TODO::判断keys长度(大于10个直接查数据库)
if len(keys) > 10 {
cfgList, _ := db.SysCfgGetAll(eg)
if cfgList == nil {
return nil
}
for _, v := range *cfgList {
res[v.Key] = v.Val
}
} else {
for _, key := range keys {
res[key] = db.SysCfgGetWithDb(eg, masterId, key)
}
}
return res
}

// 多条记录获取
func EgSysCfgFind(keys ...string) map[string]string {
var e *xorm.Engine
res := map[string]string{}
if len(res) == 0 {
cfgList, _ := db.SysCfgGetAll(e)
if cfgList == nil {
return nil
}
for _, v := range *cfgList {
res[v.Key] = v.Val
}
// 先不设置缓存
// cache.SetJson(md.KEY_SYS_CFG_CACHE, res, 60)
}
if len(keys) == 0 {
return res
}
tmp := map[string]string{}
for _, v := range keys {
if val, ok := res[v]; ok {
tmp[v] = val
} else {
tmp[v] = ""
}
}
return tmp
}

// 清理系统配置信息
func SysCfgCleanCache() {
cache.Del(md.KEY_SYS_CFG_CACHE)
}

// 写入系统设置
func SysCfgSet(c *gin.Context, key, val, memo string) bool {
cfg, err := db.SysCfgGetOne(db.DBs[c.GetString("mid")], key)
if err != nil || cfg == nil {
return db.SysCfgInsert(db.DBs[c.GetString("mid")], key, val, memo)
}
if memo != "" && cfg.Memo != memo {
cfg.Memo = memo
}
SysCfgCleanCache()
return db.SysCfgUpdate(db.DBs[c.GetString("mid")], key, val, cfg.Memo)
}

+ 26
- 0
app/svc/svc_validate_common.go Целия файл

@@ -0,0 +1,26 @@
package svc

import (
"applet/app/e"
"applet/app/utils"
"applet/app/utils/logx"
"encoding/json"
"fmt"
"github.com/go-playground/validator/v10"
)

// HandleValidateErr 通用请求参数错误处理
func HandleValidateErr(err error) error {
switch err.(type) {
case *json.UnmarshalTypeError:
return e.NewErr(e.ERR_UNMARSHAL, "参数格式错误")
case validator.ValidationErrors:
errs := err.(validator.ValidationErrors)
transMsgMap := errs.Translate(utils.ValidatorTrans)
transMsgOne := transMsgMap[utils.GetOneKeyOfMapString(transMsgMap)]
return e.NewErr(e.ERR_INVALID_ARGS, transMsgOne)
default:
_ = logx.Error(err)
return e.NewErr(e.ERR, fmt.Sprintf("validate request params, err:%v\n", err))
}
}

+ 117
- 0
app/task/init.go Целия файл

@@ -0,0 +1,117 @@
package task

import (
taskMd "applet/app/task/md"
"time"

"github.com/robfig/cron/v3"
"xorm.io/xorm"

"applet/app/cfg"
"applet/app/db"
"applet/app/db/model"
"applet/app/md"
"applet/app/utils"
"applet/app/utils/logx"
)

var (
timer *cron.Cron
jobs = map[string]func(*xorm.Engine, string){}
baseEntryId cron.EntryID
entryIds []cron.EntryID
taskCfgList map[string]*[]model.SysCfg
ch = make(chan int, 30)
workerNum = 15 // 智盟跟单并发数量
otherCh = make(chan int, 30)
otherWorkerNum = 18 // 淘宝, 苏宁, 考拉并发量
)

func Init() {
// 初始化任务列表
initTasks()
var err error
timer = cron.New()
// reload为初始化数据库方法
if baseEntryId, err = timer.AddFunc("@every 15m", reload); err != nil {
_ = logx.Fatal(err)
}
}

func Run() {
reload()
timer.Start()
_ = logx.Info("auto tasks running...")
}

func reload() {
// 重新初始化数据库
db.InitMapDbs(cfg.DB, cfg.Prd)

if len(taskCfgList) == 0 {
taskCfgList = map[string]*[]model.SysCfg{}
}

// 获取所有站长的配置信息
for dbName, v := range db.DBs {
if conf := db.MapCrontabCfg(v); conf != nil {
if cfg.Debug {
dbInfo := md.SplitDbInfo(v)
// 去掉模版库
if dbName == "000000" {
continue
}
_ = logx.Debugf("【MasterId】%s, 【Host】%s, 【Name】%s, 【User】%s, 【prd】%v, 【Task】%v\n", dbName, dbInfo.Host, dbInfo.Name, dbInfo.User, cfg.Prd, utils.SerializeStr(*conf))
}
taskCfgList[dbName] = conf
}
}
if len(taskCfgList) > 0 {
// 删除原有所有任务
if len(entryIds) > 0 {
for _, v := range entryIds {
if v != baseEntryId {
timer.Remove(v)
}
}
entryIds = nil
}
var (
entryId cron.EntryID
err error
)
// 添加任务
for dbName, v := range taskCfgList {
for _, vv := range *v {
if _, ok := jobs[vv.Key]; ok && vv.Val != "" {
// fmt.Println(vv.Val)
if entryId, err = timer.AddFunc(vv.Val, doTask(dbName, vv.Key)); err == nil {
entryIds = append(entryIds, entryId)
}
}
}
}

}
}

func doTask(dbName, fnName string) func() {
return func() {
begin := time.Now().Local()
jobs[fnName](db.DBs[dbName], dbName)
end := time.Now().Local()
logx.Infof(
"[%s] AutoTask <%s> started at <%s>, ended at <%s> duration <%s>",
dbName,
fnName,
begin.Format("2006-01-02 15:04:05.000"),
end.Format("2006-01-02 15:04:05.000"),
time.Duration(end.UnixNano()-begin.UnixNano()).String(),
)
}
}

// 增加自动任务队列
func initTasks() {
jobs[taskMd.MallCronOrderCancel] = taskCancelOrder // 取消订单
}

+ 5
- 0
app/task/md/cron_key.go Целия файл

@@ -0,0 +1,5 @@
package md

const (
MallCronOrderCancel = "mall_cron_order_cancel" // 取消订单任务
)

+ 64
- 0
app/task/svc/svc_cancel_order.go Целия файл

@@ -0,0 +1,64 @@
package svc

import (
"applet/app/db"
"applet/app/utils"
"applet/app/utils/logx"
"errors"
"fmt"
"time"
"xorm.io/xorm"
)

func CancelOrder(eg *xorm.Engine, dbName string) {
fmt.Println("cancel order...")
defer func() {
if err := recover(); err != nil {
_ = logx.Error(err)
}
}()

timeStr, err := getCancelCfg(eg, dbName)
if err != nil {
fmt.Println(err.Error())
return
}

now := time.Now()
// x 分钟后取消订单
expTime := now.Add(-time.Hour * time.Duration(utils.StrToInt64(timeStr)))
expTimeStr := utils.Time2String(expTime, "")

page := 1

for {
isEmpty, err := handleOnePage(eg, dbName, expTimeStr)
if err != nil {
_ = logx.Error(err)
break
}
if isEmpty {
break
}

if page > 100 {
break
}

page += 1

}
}

func handleOnePage(eg *xorm.Engine, dbName, expTimeStr string) (isEmpty bool, err error) {
return false, nil
}

func getCancelCfg(eg *xorm.Engine, masterId string) (string, error) {
cfg := db.SysCfgGetWithDb(eg, masterId, "order_expiration_time")

if cfg == "" {
return "", errors.New("order_expiration_time no found")
}
return cfg, nil
}

+ 23
- 0
app/task/task_cancel_order.go Целия файл

@@ -0,0 +1,23 @@
package task

import (
"applet/app/task/svc"
"math/rand"
"time"
"xorm.io/xorm"
)

// 取消订单
func taskCancelOrder(eg *xorm.Engine, dbName string) {
for {
if len(ch) > workerNum {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
} else {
goto START
}
}
START:
ch <- 1
svc.CancelOrder(eg, dbName)
<-ch
}

+ 123
- 0
app/utils/aes.go Целия файл

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

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"fmt"
)

func AesEncrypt(rawData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
rawData = PKCS5Padding(rawData, blockSize)
// rawData = ZeroPadding(rawData, block.BlockSize())
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
encrypted := make([]byte, len(rawData))
// 根据CryptBlocks方法的说明,如下方式初始化encrypted也可以
// encrypted := rawData
blockMode.CryptBlocks(encrypted, rawData)
return encrypted, nil
}

func AesDecrypt(encrypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
rawData := make([]byte, len(encrypted))
// rawData := encrypted
blockMode.CryptBlocks(rawData, encrypted)
rawData = PKCS5UnPadding(rawData)
// rawData = ZeroUnPadding(rawData)
return rawData, nil
}

func ZeroPadding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{0}, padding)
return append(cipherText, padText...)
}

func ZeroUnPadding(rawData []byte) []byte {
length := len(rawData)
unPadding := int(rawData[length-1])
return rawData[:(length - unPadding)]
}

func PKCS5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}

func PKCS5UnPadding(rawData []byte) []byte {
length := len(rawData)
// 去掉最后一个字节 unPadding 次
unPadding := int(rawData[length-1])
return rawData[:(length - unPadding)]
}

// 填充0
func zeroFill(key *string) {
l := len(*key)
if l != 16 && l != 24 && l != 32 {
if l < 16 {
*key = *key + fmt.Sprintf("%0*d", 16-l, 0)
} else if l < 24 {
*key = *key + fmt.Sprintf("%0*d", 24-l, 0)
} else if l < 32 {
*key = *key + fmt.Sprintf("%0*d", 32-l, 0)
} else {
*key = string([]byte(*key)[:32])
}
}
}

type AesCrypt struct {
Key []byte
Iv []byte
}

func (a *AesCrypt) Encrypt(data []byte) ([]byte, error) {
aesBlockEncrypt, err := aes.NewCipher(a.Key)
if err != nil {
println(err.Error())
return nil, err
}

content := pKCS5Padding(data, aesBlockEncrypt.BlockSize())
cipherBytes := make([]byte, len(content))
aesEncrypt := cipher.NewCBCEncrypter(aesBlockEncrypt, a.Iv)
aesEncrypt.CryptBlocks(cipherBytes, content)
return cipherBytes, nil
}

func (a *AesCrypt) Decrypt(src []byte) (data []byte, err error) {
decrypted := make([]byte, len(src))
var aesBlockDecrypt cipher.Block
aesBlockDecrypt, err = aes.NewCipher(a.Key)
if err != nil {
println(err.Error())
return nil, err
}
aesDecrypt := cipher.NewCBCDecrypter(aesBlockDecrypt, a.Iv)
aesDecrypt.CryptBlocks(decrypted, src)
return pKCS5Trimming(decrypted), nil
}

func pKCS5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}

func pKCS5Trimming(encrypt []byte) []byte {
padding := encrypt[len(encrypt)-1]
return encrypt[:len(encrypt)-int(padding)]
}

+ 95
- 0
app/utils/base64.go Целия файл

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

import (
"encoding/base64"
"fmt"
)

const (
Base64Std = iota
Base64Url
Base64RawStd
Base64RawUrl
)

func Base64StdEncode(str interface{}) string {
return Base64Encode(str, Base64Std)
}

func Base64StdDecode(str interface{}) string {
return Base64Decode(str, Base64Std)
}

func Base64UrlEncode(str interface{}) string {
return Base64Encode(str, Base64Url)
}

func Base64UrlDecode(str interface{}) string {
return Base64Decode(str, Base64Url)
}

func Base64RawStdEncode(str interface{}) string {
return Base64Encode(str, Base64RawStd)
}

func Base64RawStdDecode(str interface{}) string {
return Base64Decode(str, Base64RawStd)
}

func Base64RawUrlEncode(str interface{}) string {
return Base64Encode(str, Base64RawUrl)
}

func Base64RawUrlDecode(str interface{}) string {
return Base64Decode(str, Base64RawUrl)
}

func Base64Encode(str interface{}, encode int) string {
newEncode := base64Encode(encode)
if newEncode == nil {
return ""
}
switch v := str.(type) {
case string:
return newEncode.EncodeToString([]byte(v))
case []byte:
return newEncode.EncodeToString(v)
}
return newEncode.EncodeToString([]byte(fmt.Sprint(str)))
}

func Base64Decode(str interface{}, encode int) string {
var err error
var b []byte
newEncode := base64Encode(encode)
if newEncode == nil {
return ""
}
switch v := str.(type) {
case string:
b, err = newEncode.DecodeString(v)
case []byte:
b, err = newEncode.DecodeString(string(v))
default:
return ""
}
if err != nil {
return ""
}
return string(b)
}

func base64Encode(encode int) *base64.Encoding {
switch encode {
case Base64Std:
return base64.StdEncoding
case Base64Url:
return base64.URLEncoding
case Base64RawStd:
return base64.RawStdEncoding
case Base64RawUrl:
return base64.RawURLEncoding
default:
return nil
}
}

+ 26
- 0
app/utils/boolean.go Целия файл

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

import "reflect"

// 检验一个值是否为空
func Empty(val interface{}) bool {
v := reflect.ValueOf(val)
switch v.Kind() {
case reflect.String, reflect.Array:
return v.Len() == 0
case reflect.Map, reflect.Slice:
return v.Len() == 0 || v.IsNil()
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}

return reflect.DeepEqual(val, reflect.Zero(v.Type()).Interface())
}

+ 421
- 0
app/utils/cache/base.go Целия файл

@@ -0,0 +1,421 @@
package cache

import (
"errors"
"fmt"
"strconv"
"time"
)

const (
redisDialTTL = 10 * time.Second
redisReadTTL = 3 * time.Second
redisWriteTTL = 3 * time.Second
redisIdleTTL = 10 * time.Second
redisPoolTTL = 10 * time.Second
redisPoolSize int = 512
redisMaxIdleConn int = 64
redisMaxActive int = 512
)

var (
ErrNil = errors.New("nil return")
ErrWrongArgsNum = errors.New("args num error")
ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64")
)

// 以下为提供类型转换

func Int(reply interface{}, err error) (int, error) {
if err != nil {
return 0, err
}
switch reply := reply.(type) {
case int:
return reply, nil
case int8:
return int(reply), nil
case int16:
return int(reply), nil
case int32:
return int(reply), nil
case int64:
x := int(reply)
if int64(x) != reply {
return 0, strconv.ErrRange
}
return x, nil
case uint:
n := int(reply)
if n < 0 {
return 0, strconv.ErrRange
}
return n, nil
case uint8:
return int(reply), nil
case uint16:
return int(reply), nil
case uint32:
n := int(reply)
if n < 0 {
return 0, strconv.ErrRange
}
return n, nil
case uint64:
n := int(reply)
if n < 0 {
return 0, strconv.ErrRange
}
return n, nil
case []byte:
data := string(reply)
if len(data) == 0 {
return 0, ErrNil
}

n, err := strconv.ParseInt(data, 10, 0)
return int(n), err
case string:
if len(reply) == 0 {
return 0, ErrNil
}

n, err := strconv.ParseInt(reply, 10, 0)
return int(n), err
case nil:
return 0, ErrNil
case error:
return 0, reply
}
return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply)
}

func Int64(reply interface{}, err error) (int64, error) {
if err != nil {
return 0, err
}
switch reply := reply.(type) {
case int:
return int64(reply), nil
case int8:
return int64(reply), nil
case int16:
return int64(reply), nil
case int32:
return int64(reply), nil
case int64:
return reply, nil
case uint:
n := int64(reply)
if n < 0 {
return 0, strconv.ErrRange
}
return n, nil
case uint8:
return int64(reply), nil
case uint16:
return int64(reply), nil
case uint32:
return int64(reply), nil
case uint64:
n := int64(reply)
if n < 0 {
return 0, strconv.ErrRange
}
return n, nil
case []byte:
data := string(reply)
if len(data) == 0 {
return 0, ErrNil
}

n, err := strconv.ParseInt(data, 10, 64)
return n, err
case string:
if len(reply) == 0 {
return 0, ErrNil
}

n, err := strconv.ParseInt(reply, 10, 64)
return n, err
case nil:
return 0, ErrNil
case error:
return 0, reply
}
return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply)
}

func Uint64(reply interface{}, err error) (uint64, error) {
if err != nil {
return 0, err
}
switch reply := reply.(type) {
case uint:
return uint64(reply), nil
case uint8:
return uint64(reply), nil
case uint16:
return uint64(reply), nil
case uint32:
return uint64(reply), nil
case uint64:
return reply, nil
case int:
if reply < 0 {
return 0, ErrNegativeInt
}
return uint64(reply), nil
case int8:
if reply < 0 {
return 0, ErrNegativeInt
}
return uint64(reply), nil
case int16:
if reply < 0 {
return 0, ErrNegativeInt
}
return uint64(reply), nil
case int32:
if reply < 0 {
return 0, ErrNegativeInt
}
return uint64(reply), nil
case int64:
if reply < 0 {
return 0, ErrNegativeInt
}
return uint64(reply), nil
case []byte:
data := string(reply)
if len(data) == 0 {
return 0, ErrNil
}

n, err := strconv.ParseUint(data, 10, 64)
return n, err
case string:
if len(reply) == 0 {
return 0, ErrNil
}

n, err := strconv.ParseUint(reply, 10, 64)
return n, err
case nil:
return 0, ErrNil
case error:
return 0, reply
}
return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply)
}

func Float64(reply interface{}, err error) (float64, error) {
if err != nil {
return 0, err
}

var value float64
err = nil
switch v := reply.(type) {
case float32:
value = float64(v)
case float64:
value = v
case int:
value = float64(v)
case int8:
value = float64(v)
case int16:
value = float64(v)
case int32:
value = float64(v)
case int64:
value = float64(v)
case uint:
value = float64(v)
case uint8:
value = float64(v)
case uint16:
value = float64(v)
case uint32:
value = float64(v)
case uint64:
value = float64(v)
case []byte:
data := string(v)
if len(data) == 0 {
return 0, ErrNil
}
value, err = strconv.ParseFloat(string(v), 64)
case string:
if len(v) == 0 {
return 0, ErrNil
}
value, err = strconv.ParseFloat(v, 64)
case nil:
err = ErrNil
case error:
err = v
default:
err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v)
}

return value, err
}

func Bool(reply interface{}, err error) (bool, error) {
if err != nil {
return false, err
}
switch reply := reply.(type) {
case bool:
return reply, nil
case int64:
return reply != 0, nil
case []byte:
data := string(reply)
if len(data) == 0 {
return false, ErrNil
}

return strconv.ParseBool(data)
case string:
if len(reply) == 0 {
return false, ErrNil
}

return strconv.ParseBool(reply)
case nil:
return false, ErrNil
case error:
return false, reply
}
return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply)
}

func Bytes(reply interface{}, err error) ([]byte, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []byte:
if len(reply) == 0 {
return nil, ErrNil
}
return reply, nil
case string:
data := []byte(reply)
if len(data) == 0 {
return nil, ErrNil
}
return data, nil
case nil:
return nil, ErrNil
case error:
return nil, reply
}
return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply)
}

func String(reply interface{}, err error) (string, error) {
if err != nil {
return "", err
}

value := ""
err = nil
switch v := reply.(type) {
case string:
if len(v) == 0 {
return "", ErrNil
}

value = v
case []byte:
if len(v) == 0 {
return "", ErrNil
}

value = string(v)
case int:
value = strconv.FormatInt(int64(v), 10)
case int8:
value = strconv.FormatInt(int64(v), 10)
case int16:
value = strconv.FormatInt(int64(v), 10)
case int32:
value = strconv.FormatInt(int64(v), 10)
case int64:
value = strconv.FormatInt(v, 10)
case uint:
value = strconv.FormatUint(uint64(v), 10)
case uint8:
value = strconv.FormatUint(uint64(v), 10)
case uint16:
value = strconv.FormatUint(uint64(v), 10)
case uint32:
value = strconv.FormatUint(uint64(v), 10)
case uint64:
value = strconv.FormatUint(v, 10)
case float32:
value = strconv.FormatFloat(float64(v), 'f', -1, 32)
case float64:
value = strconv.FormatFloat(v, 'f', -1, 64)
case bool:
value = strconv.FormatBool(v)
case nil:
err = ErrNil
case error:
err = v
default:
err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v)
}

return value, err
}

func Strings(reply interface{}, err error) ([]string, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []interface{}:
result := make([]string, len(reply))
for i := range reply {
if reply[i] == nil {
continue
}
switch subReply := reply[i].(type) {
case string:
result[i] = subReply
case []byte:
result[i] = string(subReply)
default:
return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i])
}
}
return result, nil
case []string:
return reply, nil
case nil:
return nil, ErrNil
case error:
return nil, reply
}
return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply)
}

func Values(reply interface{}, err error) ([]interface{}, error) {
if err != nil {
return nil, err
}
switch reply := reply.(type) {
case []interface{}:
return reply, nil
case nil:
return nil, ErrNil
case error:
return nil, reply
}
return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply)
}

+ 107
- 0
app/utils/cache/cache/cache.go Целия файл

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

import (
"fmt"
"time"
)

var c Cache

type Cache interface {
// get cached value by key.
Get(key string) interface{}
// GetMulti is a batch version of Get.
GetMulti(keys []string) []interface{}
// set cached value with key and expire time.
Put(key string, val interface{}, timeout time.Duration) error
// delete cached value by key.
Delete(key string) error
// increase cached int value by key, as a counter.
Incr(key string) error
// decrease cached int value by key, as a counter.
Decr(key string) error
// check if cached value exists or not.
IsExist(key string) bool
// clear all cache.
ClearAll() error
// start gc routine based on config string settings.
StartAndGC(config string) error
}

// Instance is a function create a new Cache Instance
type Instance func() Cache

var adapters = make(map[string]Instance)

// Register makes a cache adapter available by the adapter name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, adapter Instance) {
if adapter == nil {
panic("cache: Register adapter is nil")
}
if _, ok := adapters[name]; ok {
panic("cache: Register called twice for adapter " + name)
}
adapters[name] = adapter
}

// NewCache Create a new cache driver by adapter name and config string.
// config need to be correct JSON as string: {"interval":360}.
// it will start gc automatically.
func NewCache(adapterName, config string) (adapter Cache, err error) {
instanceFunc, ok := adapters[adapterName]
if !ok {
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
return
}
adapter = instanceFunc()
err = adapter.StartAndGC(config)
if err != nil {
adapter = nil
}
return
}

func InitCache(adapterName, config string) (err error) {
instanceFunc, ok := adapters[adapterName]
if !ok {
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
return
}
c = instanceFunc()
err = c.StartAndGC(config)
if err != nil {
c = nil
}
return
}

func Get(key string) interface{} {
return c.Get(key)
}

func GetMulti(keys []string) []interface{} {
return c.GetMulti(keys)
}
func Put(key string, val interface{}, ttl time.Duration) error {
return c.Put(key, val, ttl)
}
func Delete(key string) error {
return c.Delete(key)
}
func Incr(key string) error {
return c.Incr(key)
}
func Decr(key string) error {
return c.Decr(key)
}
func IsExist(key string) bool {
return c.IsExist(key)
}
func ClearAll() error {
return c.ClearAll()
}
func StartAndGC(cfg string) error {
return c.StartAndGC(cfg)
}

+ 86
- 0
app/utils/cache/cache/conv.go Целия файл

@@ -0,0 +1,86 @@
package cache

import (
"fmt"
"strconv"
)

// GetString convert interface to string.
func GetString(v interface{}) string {
switch result := v.(type) {
case string:
return result
case []byte:
return string(result)
default:
if v != nil {
return fmt.Sprint(result)
}
}
return ""
}

// GetInt convert interface to int.
func GetInt(v interface{}) int {
switch result := v.(type) {
case int:
return result
case int32:
return int(result)
case int64:
return int(result)
default:
if d := GetString(v); d != "" {
value, _ := strconv.Atoi(d)
return value
}
}
return 0
}

// GetInt64 convert interface to int64.
func GetInt64(v interface{}) int64 {
switch result := v.(type) {
case int:
return int64(result)
case int32:
return int64(result)
case int64:
return result
default:

if d := GetString(v); d != "" {
value, _ := strconv.ParseInt(d, 10, 64)
return value
}
}
return 0
}

// GetFloat64 convert interface to float64.
func GetFloat64(v interface{}) float64 {
switch result := v.(type) {
case float64:
return result
default:
if d := GetString(v); d != "" {
value, _ := strconv.ParseFloat(d, 64)
return value
}
}
return 0
}

// GetBool convert interface to bool.
func GetBool(v interface{}) bool {
switch result := v.(type) {
case bool:
return result
default:
if d := GetString(v); d != "" {
value, _ := strconv.ParseBool(d)
return value
}
}
return false
}

+ 241
- 0
app/utils/cache/cache/file.go Целия файл

@@ -0,0 +1,241 @@
package cache

import (
"bytes"
"crypto/md5"
"encoding/gob"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strconv"
"time"
)

// FileCacheItem is basic unit of file cache adapter.
// it contains data and expire time.
type FileCacheItem struct {
Data interface{}
LastAccess time.Time
Expired time.Time
}

// FileCache Config
var (
FileCachePath = "cache" // cache directory
FileCacheFileSuffix = ".bin" // cache file suffix
FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
)

// FileCache is cache adapter for file storage.
type FileCache struct {
CachePath string
FileSuffix string
DirectoryLevel int
EmbedExpiry int
}

// NewFileCache Create new file cache with no config.
// the level and expiry need set in method StartAndGC as config string.
func NewFileCache() Cache {
// return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
return &FileCache{}
}

// StartAndGC will start and begin gc for file cache.
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
func (fc *FileCache) StartAndGC(config string) error {

var cfg map[string]string
json.Unmarshal([]byte(config), &cfg)
if _, ok := cfg["CachePath"]; !ok {
cfg["CachePath"] = FileCachePath
}
if _, ok := cfg["FileSuffix"]; !ok {
cfg["FileSuffix"] = FileCacheFileSuffix
}
if _, ok := cfg["DirectoryLevel"]; !ok {
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
}
if _, ok := cfg["EmbedExpiry"]; !ok {
cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
}
fc.CachePath = cfg["CachePath"]
fc.FileSuffix = cfg["FileSuffix"]
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])

fc.Init()
return nil
}

// Init will make new dir for file cache if not exist.
func (fc *FileCache) Init() {
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
}
}

// get cached file name. it's md5 encoded.
func (fc *FileCache) getCacheFileName(key string) string {
m := md5.New()
io.WriteString(m, key)
keyMd5 := hex.EncodeToString(m.Sum(nil))
cachePath := fc.CachePath
switch fc.DirectoryLevel {
case 2:
cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
case 1:
cachePath = filepath.Join(cachePath, keyMd5[0:2])
}

if ok, _ := exists(cachePath); !ok { // todo : error handle
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
}

return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
}

// Get value from file cache.
// if non-exist or expired, return empty string.
func (fc *FileCache) Get(key string) interface{} {
fileData, err := FileGetContents(fc.getCacheFileName(key))
if err != nil {
return ""
}
var to FileCacheItem
GobDecode(fileData, &to)
if to.Expired.Before(time.Now()) {
return ""
}
return to.Data
}

// GetMulti gets values from file cache.
// if non-exist or expired, return empty string.
func (fc *FileCache) GetMulti(keys []string) []interface{} {
var rc []interface{}
for _, key := range keys {
rc = append(rc, fc.Get(key))
}
return rc
}

// Put value into file cache.
// timeout means how long to keep this file, unit of ms.
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
gob.Register(val)

item := FileCacheItem{Data: val}
if timeout == FileCacheEmbedExpiry {
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
} else {
item.Expired = time.Now().Add(timeout)
}
item.LastAccess = time.Now()
data, err := GobEncode(item)
if err != nil {
return err
}
return FilePutContents(fc.getCacheFileName(key), data)
}

// Delete file cache value.
func (fc *FileCache) Delete(key string) error {
filename := fc.getCacheFileName(key)
if ok, _ := exists(filename); ok {
return os.Remove(filename)
}
return nil
}

// Incr will increase cached int value.
// fc value is saving forever unless Delete.
func (fc *FileCache) Incr(key string) error {
data := fc.Get(key)
var incr int
if reflect.TypeOf(data).Name() != "int" {
incr = 0
} else {
incr = data.(int) + 1
}
fc.Put(key, incr, FileCacheEmbedExpiry)
return nil
}

// Decr will decrease cached int value.
func (fc *FileCache) Decr(key string) error {
data := fc.Get(key)
var decr int
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
decr = 0
} else {
decr = data.(int) - 1
}
fc.Put(key, decr, FileCacheEmbedExpiry)
return nil
}

// IsExist check value is exist.
func (fc *FileCache) IsExist(key string) bool {
ret, _ := exists(fc.getCacheFileName(key))
return ret
}

// ClearAll will clean cached files.
// not implemented.
func (fc *FileCache) ClearAll() error {
return nil
}

// check file exist.
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

// FileGetContents Get bytes to file.
// if non-exist, create this file.
func FileGetContents(filename string) (data []byte, e error) {
return ioutil.ReadFile(filename)
}

// FilePutContents Put bytes to file.
// if non-exist, create this file.
func FilePutContents(filename string, content []byte) error {
return ioutil.WriteFile(filename, content, os.ModePerm)
}

// GobEncode Gob encodes file cache item.
func GobEncode(data interface{}) ([]byte, error) {
buf := bytes.NewBuffer(nil)
enc := gob.NewEncoder(buf)
err := enc.Encode(data)
if err != nil {
return nil, err
}
return buf.Bytes(), err
}

// GobDecode Gob decodes file cache item.
func GobDecode(data []byte, to *FileCacheItem) error {
buf := bytes.NewBuffer(data)
dec := gob.NewDecoder(buf)
return dec.Decode(&to)
}

func init() {
Register("file", NewFileCache)
}

+ 239
- 0
app/utils/cache/cache/memory.go Целия файл

@@ -0,0 +1,239 @@
package cache

import (
"encoding/json"
"errors"
"sync"
"time"
)

var (
// DefaultEvery means the clock time of recycling the expired cache items in memory.
DefaultEvery = 60 // 1 minute
)

// MemoryItem store memory cache item.
type MemoryItem struct {
val interface{}
createdTime time.Time
lifespan time.Duration
}

func (mi *MemoryItem) isExpire() bool {
// 0 means forever
if mi.lifespan == 0 {
return false
}
return time.Now().Sub(mi.createdTime) > mi.lifespan
}

// MemoryCache is Memory cache adapter.
// it contains a RW locker for safe map storage.
type MemoryCache struct {
sync.RWMutex
dur time.Duration
items map[string]*MemoryItem
Every int // run an expiration check Every clock time
}

// NewMemoryCache returns a new MemoryCache.
func NewMemoryCache() Cache {
cache := MemoryCache{items: make(map[string]*MemoryItem)}
return &cache
}

// Get cache from memory.
// if non-existed or expired, return nil.
func (bc *MemoryCache) Get(name string) interface{} {
bc.RLock()
defer bc.RUnlock()
if itm, ok := bc.items[name]; ok {
if itm.isExpire() {
return nil
}
return itm.val
}
return nil
}

// GetMulti gets caches from memory.
// if non-existed or expired, return nil.
func (bc *MemoryCache) GetMulti(names []string) []interface{} {
var rc []interface{}
for _, name := range names {
rc = append(rc, bc.Get(name))
}
return rc
}

// Put cache to memory.
// if lifespan is 0, it will be forever till restart.
func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
bc.Lock()
defer bc.Unlock()
bc.items[name] = &MemoryItem{
val: value,
createdTime: time.Now(),
lifespan: lifespan,
}
return nil
}

// Delete cache in memory.
func (bc *MemoryCache) Delete(name string) error {
bc.Lock()
defer bc.Unlock()
if _, ok := bc.items[name]; !ok {
return errors.New("key not exist")
}
delete(bc.items, name)
if _, ok := bc.items[name]; ok {
return errors.New("delete key error")
}
return nil
}

// Incr increase cache counter in memory.
// it supports int,int32,int64,uint,uint32,uint64.
func (bc *MemoryCache) Incr(key string) error {
bc.RLock()
defer bc.RUnlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
case int:
itm.val = itm.val.(int) + 1
case int32:
itm.val = itm.val.(int32) + 1
case int64:
itm.val = itm.val.(int64) + 1
case uint:
itm.val = itm.val.(uint) + 1
case uint32:
itm.val = itm.val.(uint32) + 1
case uint64:
itm.val = itm.val.(uint64) + 1
default:
return errors.New("item val is not (u)int (u)int32 (u)int64")
}
return nil
}

// Decr decrease counter in memory.
func (bc *MemoryCache) Decr(key string) error {
bc.RLock()
defer bc.RUnlock()
itm, ok := bc.items[key]
if !ok {
return errors.New("key not exist")
}
switch itm.val.(type) {
case int:
itm.val = itm.val.(int) - 1
case int64:
itm.val = itm.val.(int64) - 1
case int32:
itm.val = itm.val.(int32) - 1
case uint:
if itm.val.(uint) > 0 {
itm.val = itm.val.(uint) - 1
} else {
return errors.New("item val is less than 0")
}
case uint32:
if itm.val.(uint32) > 0 {
itm.val = itm.val.(uint32) - 1
} else {
return errors.New("item val is less than 0")
}
case uint64:
if itm.val.(uint64) > 0 {
itm.val = itm.val.(uint64) - 1
} else {
return errors.New("item val is less than 0")
}
default:
return errors.New("item val is not int int64 int32")
}
return nil
}

// IsExist check cache exist in memory.
func (bc *MemoryCache) IsExist(name string) bool {
bc.RLock()
defer bc.RUnlock()
if v, ok := bc.items[name]; ok {
return !v.isExpire()
}
return false
}

// ClearAll will delete all cache in memory.
func (bc *MemoryCache) ClearAll() error {
bc.Lock()
defer bc.Unlock()
bc.items = make(map[string]*MemoryItem)
return nil
}

// StartAndGC start memory cache. it will check expiration in every clock time.
func (bc *MemoryCache) StartAndGC(config string) error {
var cf map[string]int
json.Unmarshal([]byte(config), &cf)
if _, ok := cf["interval"]; !ok {
cf = make(map[string]int)
cf["interval"] = DefaultEvery
}
dur := time.Duration(cf["interval"]) * time.Second
bc.Every = cf["interval"]
bc.dur = dur
go bc.vacuum()
return nil
}

// check expiration.
func (bc *MemoryCache) vacuum() {
bc.RLock()
every := bc.Every
bc.RUnlock()

if every < 1 {
return
}
for {
<-time.After(bc.dur)
if bc.items == nil {
return
}
if keys := bc.expiredKeys(); len(keys) != 0 {
bc.clearItems(keys)
}
}
}

// expiredKeys returns key list which are expired.
func (bc *MemoryCache) expiredKeys() (keys []string) {
bc.RLock()
defer bc.RUnlock()
for key, itm := range bc.items {
if itm.isExpire() {
keys = append(keys, key)
}
}
return
}

// clearItems removes all the items which key in keys.
func (bc *MemoryCache) clearItems(keys []string) {
bc.Lock()
defer bc.Unlock()
for _, key := range keys {
delete(bc.items, key)
}
}

func init() {
Register("memory", NewMemoryCache)
}

+ 412
- 0
app/utils/cache/redis.go Целия файл

@@ -0,0 +1,412 @@
package cache

import (
"encoding/json"
"errors"
"log"
"strings"
"time"

redigo "github.com/gomodule/redigo/redis"
)

// configuration
type Config struct {
Server string
Password string
MaxIdle int // Maximum number of idle connections in the pool.

// Maximum number of connections allocated by the pool at a given time.
// When zero, there is no limit on the number of connections in the pool.
MaxActive int

// Close connections after remaining idle for this duration. If the value
// is zero, then idle connections are not closed. Applications should set
// the timeout to a value less than the server's timeout.
IdleTimeout time.Duration

// If Wait is true and the pool is at the MaxActive limit, then Get() waits
// for a connection to be returned to the pool before returning.
Wait bool
KeyPrefix string // prefix to all keys; example is "dev environment name"
KeyDelimiter string // delimiter to be used while appending keys; example is ":"
KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?"
}

var pool *redigo.Pool
var conf *Config

func NewRedis(addr string) {
if addr == "" {
panic("\nredis connect string cannot be empty\n")
}
pool = &redigo.Pool{
MaxIdle: redisMaxIdleConn,
IdleTimeout: redisIdleTTL,
MaxActive: redisMaxActive,
// MaxConnLifetime: redisDialTTL,
Wait: true,
Dial: func() (redigo.Conn, error) {
c, err := redigo.Dial("tcp", addr,
redigo.DialConnectTimeout(redisDialTTL),
redigo.DialReadTimeout(redisReadTTL),
redigo.DialWriteTimeout(redisWriteTTL),
)
if err != nil {
log.Println("Redis Dial failed: ", err)
return nil, err
}
return c, err
},
TestOnBorrow: func(c redigo.Conn, t time.Time) error {
_, err := c.Do("PING")
if err != nil {
log.Println("Unable to ping to redis server:", err)
}
return err
},
}
conn := pool.Get()
defer conn.Close()
if conn.Err() != nil {
println("\nredis connect " + addr + " error: " + conn.Err().Error())
} else {
println("\nredis connect " + addr + " success!\n")
}
}

func Do(cmd string, args ...interface{}) (reply interface{}, err error) {
conn := pool.Get()
defer conn.Close()
return conn.Do(cmd, args...)
}

func GetPool() *redigo.Pool {
return pool
}

func ParseKey(key string, vars []string) (string, error) {
arr := strings.Split(key, conf.KeyPlaceholder)
actualKey := ""
if len(arr) != len(vars)+1 {
return "", errors.New("redis/connection.go: Insufficient arguments to parse key")
} else {
for index, val := range arr {
if index == 0 {
actualKey = arr[index]
} else {
actualKey += vars[index-1] + val
}
}
}
return getPrefixedKey(actualKey), nil
}

func getPrefixedKey(key string) string {
return conf.KeyPrefix + conf.KeyDelimiter + key
}
func StripEnvKey(key string) string {
return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter)
}
func SplitKey(key string) []string {
return strings.Split(key, conf.KeyDelimiter)
}
func Expire(key string, ttl int) (interface{}, error) {
return Do("EXPIRE", key, ttl)
}
func Persist(key string) (interface{}, error) {
return Do("PERSIST", key)
}

func Del(key string) (interface{}, error) {
return Do("DEL", key)
}
func Set(key string, data interface{}) (interface{}, error) {
// set
return Do("SET", key, data)
}
func SetNX(key string, data interface{}) (interface{}, error) {
return Do("SETNX", key, data)
}
func SetEx(key string, data interface{}, ttl int) (interface{}, error) {
return Do("SETEX", key, ttl, data)
}

func SetJson(key string, data interface{}, ttl int) bool {
c, err := json.Marshal(data)
if err != nil {
return false
}
if ttl < 1 {
_, err = Set(key, c)
} else {
_, err = SetEx(key, c, ttl)
}
if err != nil {
return false
}
return true
}

func GetJson(key string, dst interface{}) error {
b, err := GetBytes(key)
if err != nil {
return err
}
if err = json.Unmarshal(b, dst); err != nil {
return err
}
return nil
}

func Get(key string) (interface{}, error) {
// get
return Do("GET", key)
}
func GetTTL(key string) (time.Duration, error) {
ttl, err := redigo.Int64(Do("TTL", key))
return time.Duration(ttl) * time.Second, err
}
func GetBytes(key string) ([]byte, error) {
return redigo.Bytes(Do("GET", key))
}
func GetString(key string) (string, error) {
return redigo.String(Do("GET", key))
}
func GetStringMap(key string) (map[string]string, error) {
return redigo.StringMap(Do("GET", key))
}
func GetInt(key string) (int, error) {
return redigo.Int(Do("GET", key))
}
func GetInt64(key string) (int64, error) {
return redigo.Int64(Do("GET", key))
}
func GetStringLength(key string) (int, error) {
return redigo.Int(Do("STRLEN", key))
}
func ZAdd(key string, score float64, data interface{}) (interface{}, error) {
return Do("ZADD", key, score, data)
}
func ZAddNX(key string, score float64, data interface{}) (interface{}, error) {
return Do("ZADD", key, "NX", score, data)
}
func ZRem(key string, data interface{}) (interface{}, error) {
return Do("ZREM", key, data)
}
func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) {
if withScores {
return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES"))
}
return redigo.Values(Do("ZRANGE", key, start, end))
}
func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) {
return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end))
}
func ZCard(setName string) (int64, error) {
return redigo.Int64(Do("ZCARD", setName))
}
func ZScan(setName string) (int64, error) {
return redigo.Int64(Do("ZCARD", setName))
}
func SAdd(setName string, data interface{}) (interface{}, error) {
return Do("SADD", setName, data)
}
func SCard(setName string) (int64, error) {
return redigo.Int64(Do("SCARD", setName))
}
func SIsMember(setName string, data interface{}) (bool, error) {
return redigo.Bool(Do("SISMEMBER", setName, data))
}
func SMembers(setName string) ([]string, error) {
return redigo.Strings(Do("SMEMBERS", setName))
}
func SRem(setName string, data interface{}) (interface{}, error) {
return Do("SREM", setName, data)
}
func HSet(key string, HKey string, data interface{}) (interface{}, error) {
return Do("HSET", key, HKey, data)
}

func HGet(key string, HKey string) (interface{}, error) {
return Do("HGET", key, HKey)
}

func HMGet(key string, hashKeys ...string) ([]interface{}, error) {
ret, err := Do("HMGET", key, hashKeys)
if err != nil {
return nil, err
}
reta, ok := ret.([]interface{})
if !ok {
return nil, errors.New("result not an array")
}
return reta, nil
}

func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) {
if len(hashKeys) == 0 || len(hashKeys) != len(vals) {
var ret interface{}
return ret, errors.New("bad length")
}
input := []interface{}{key}
for i, v := range hashKeys {
input = append(input, v, vals[i])
}
return Do("HMSET", input...)
}

func HGetString(key string, HKey string) (string, error) {
return redigo.String(Do("HGET", key, HKey))
}
func HGetFloat(key string, HKey string) (float64, error) {
f, err := redigo.Float64(Do("HGET", key, HKey))
return f, err
}
func HGetInt(key string, HKey string) (int, error) {
return redigo.Int(Do("HGET", key, HKey))
}
func HGetInt64(key string, HKey string) (int64, error) {
return redigo.Int64(Do("HGET", key, HKey))
}
func HGetBool(key string, HKey string) (bool, error) {
return redigo.Bool(Do("HGET", key, HKey))
}
func HDel(key string, HKey string) (interface{}, error) {
return Do("HDEL", key, HKey)
}

func HGetAll(key string) (map[string]interface{}, error) {
vals, err := redigo.Values(Do("HGETALL", key))
if err != nil {
return nil, err
}
num := len(vals) / 2
result := make(map[string]interface{}, num)
for i := 0; i < num; i++ {
key, _ := redigo.String(vals[2*i], nil)
result[key] = vals[2*i+1]
}
return result, nil
}

func FlushAll() bool {
res, _ := redigo.String(Do("FLUSHALL"))
if res == "" {
return false
}
return true
}

// NOTE: Use this in production environment with extreme care.
// Read more here:https://redigo.io/commands/keys
func Keys(pattern string) ([]string, error) {
return redigo.Strings(Do("KEYS", pattern))
}

func HKeys(key string) ([]string, error) {
return redigo.Strings(Do("HKEYS", key))
}

func Exists(key string) bool {
count, err := redigo.Int(Do("EXISTS", key))
if count == 0 || err != nil {
return false
}
return true
}

func Incr(key string) (int64, error) {
return redigo.Int64(Do("INCR", key))
}

func Decr(key string) (int64, error) {
return redigo.Int64(Do("DECR", key))
}

func IncrBy(key string, incBy int64) (int64, error) {
return redigo.Int64(Do("INCRBY", key, incBy))
}

func DecrBy(key string, decrBy int64) (int64, error) {
return redigo.Int64(Do("DECRBY", key))
}

func IncrByFloat(key string, incBy float64) (float64, error) {
return redigo.Float64(Do("INCRBYFLOAT", key, incBy))
}

func DecrByFloat(key string, decrBy float64) (float64, error) {
return redigo.Float64(Do("DECRBYFLOAT", key, decrBy))
}

// use for message queue
func LPush(key string, data interface{}) (interface{}, error) {
// set
return Do("LPUSH", key, data)
}

func LPop(key string) (interface{}, error) {
return Do("LPOP", key)
}

func LPopString(key string) (string, error) {
return redigo.String(Do("LPOP", key))
}
func LPopFloat(key string) (float64, error) {
f, err := redigo.Float64(Do("LPOP", key))
return f, err
}
func LPopInt(key string) (int, error) {
return redigo.Int(Do("LPOP", key))
}
func LPopInt64(key string) (int64, error) {
return redigo.Int64(Do("LPOP", key))
}

func RPush(key string, data interface{}) (interface{}, error) {
// set
return Do("RPUSH", key, data)
}

func RPop(key string) (interface{}, error) {
return Do("RPOP", key)
}

func RPopString(key string) (string, error) {
return redigo.String(Do("RPOP", key))
}
func RPopFloat(key string) (float64, error) {
f, err := redigo.Float64(Do("RPOP", key))
return f, err
}
func RPopInt(key string) (int, error) {
return redigo.Int(Do("RPOP", key))
}
func RPopInt64(key string) (int64, error) {
return redigo.Int64(Do("RPOP", key))
}

func Scan(cursor int64, pattern string, count int64) (int64, []string, error) {
var items []string
var newCursor int64

values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count))
if err != nil {
return 0, nil, err
}
values, err = redigo.Scan(values, &newCursor, &items)
if err != nil {
return 0, nil, err
}
return newCursor, items, nil
}

func LPushMax(key string, data ...interface{}) (interface{}, error) {
// set
return Do("LPUSH", key, data)
}

func SelectDb(db int) (interface{}, error) {
return Do("SELECT", db)
}

Някои файлове не бяха показани, защото твърде много файлове са промени

Зареждане…
Отказ
Запис