wangfei 6 years ago
parent
commit
ed08b7cbae

+ 5
- 0
.gitignore View File

@@ -0,0 +1,5 @@
1
+*.exe
2
+*.exe~
3
+*.test
4
+*.log
5
+log

+ 22
- 0
bootstrap/bootstrap.go View File

@@ -0,0 +1,22 @@
1
+package bootstrap
2
+
3
+import (
4
+	"wechat-conf/models"
5
+	"wechat-conf/routers"
6
+	"wechat-conf/utils"
7
+)
8
+
9
+// SystemInit 系统初始化
10
+func SystemInit() {
11
+	// 数据库连接
12
+	models.InitDB()
13
+
14
+	// 系统日志
15
+	utils.LogInit()
16
+
17
+	// 微信系统
18
+	utils.WechatInit()
19
+
20
+	// 路由系统
21
+	routers.RouteInit()
22
+}

+ 5
- 0
conf/aliyun.conf View File

@@ -0,0 +1,5 @@
1
+[oss]
2
+Endpoint=oss-cn-shanghai.aliyuncs.com
3
+AccessKeyId=LTAIkc75dpkJw8Lb
4
+AccessKeySecret=v4bvXCaix6vSDTCFfwSAdqV53iFEQw
5
+Bucket=jingcheng-h5temp

+ 17
- 0
conf/app.conf View File

@@ -0,0 +1,17 @@
1
+appname = wechat-conf
2
+httpport = 8080
3
+runmode = prod
4
+autorender = false
5
+copyrequestbody = true
6
+EnableDocs = true
7
+excelpath = ./
8
+
9
+[cros]
10
+allowMode = dev
11
+allowCredentials = true
12
+allowOrigin = http://localhost:9528
13
+
14
+# GET, POST etc
15
+allowMethods = *
16
+
17
+

+ 33
- 0
conf/db.conf View File

@@ -0,0 +1,33 @@
1
+; 数据库类型,目前只支持mysql
2
+db_type      = mysql
3
+
4
+; 连接协议
5
+con_protocol = tcp
6
+
7
+; 数据库地址,可以使用IP
8
+db_addr      = 47.101.36.130
9
+# db_addr        = 192.168.0.122
10
+; db_addr = localhost
11
+# db_addr      = rm-uf6z3z6jq11x653d77o.mysql.rds.aliyuncs.com
12
+
13
+; 端口
14
+db_port      = 3306
15
+
16
+; 用户名
17
+username     = spaceofcheng
18
+
19
+; 密码
20
+password     = spaceofcheng
21
+# password     = c;a|vK)Xv/=L@c[6Fx,v[ITD5;WpR}+Y
22
+
23
+; 数据库名或者schema
24
+database     = spaceofcheng
25
+
26
+; 前缀,目前尚未使用
27
+dbprefix     = 
28
+
29
+; 模式,目前尚未使用
30
+db_debug     = false
31
+
32
+; 字符集
33
+char_set     = utf8mb4

+ 4
- 0
conf/log.conf View File

@@ -0,0 +1,4 @@
1
+[common]
2
+filename="E:\\GoProjects\\src\\spaceofcheng\\services\\log\\common.log"
3
+# log level "emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"
4
+level="debug"

+ 54
- 0
controllers/auth.go View File

@@ -0,0 +1,54 @@
1
+package controllers
2
+
3
+// Authenticate 权限验证
4
+// 其中 token 的处理方式是
5
+// 1、获取 request 中 token
6
+// 2、放入 Context 中
7
+// 3、校验 token
8
+// 4、结束后,设置过期并从 Context 中删除
9
+// 5、生成新的 token, 并放入 Context 中
10
+// func (c *BaseController) authenticate() {
11
+// 	c.serv = service.NewSysServ(c.Context)
12
+
13
+// 	// 鉴权 - 并初始化上下文
14
+// 	res := c.serv.AuthAndInitCtx(c.Ctx)
15
+
16
+// 	if res != nil {
17
+// 		code := http.StatusOK
18
+// 		if res["code"] != nil {
19
+// 			code = res["code"].(int)
20
+// 		}
21
+
22
+// 		if code != http.StatusOK {
23
+// 			err := res["error"].(error)
24
+// 			data := map[string]interface{}{}
25
+
26
+// 			if res["message"] != nil {
27
+// 				data = res["message"].(map[string]interface{})
28
+// 			}
29
+
30
+// 			c.ResponseData(data, err, code)
31
+// 		}
32
+// 	}
33
+// }
34
+
35
+// // SetTokenExipre 设置 token 过期
36
+// func (c *BaseController) SetTokenExipre(uid string) {
37
+// 	token := c.Context.Get("token")
38
+// 	if token != nil {
39
+// 		c.serv.UpdateTokenExpire(token.(string), uid)
40
+// 	}
41
+
42
+// 	c.Context.Set("token", "")
43
+// }
44
+
45
+// // CreateNewToken 新 token
46
+// func (c *BaseController) CreateNewToken() {
47
+
48
+// 	batchNo := ""
49
+// 	if c.Context.Get("token-batch") != nil {
50
+// 		batchNo = c.Context.Get("token-batch").(string)
51
+// 	}
52
+
53
+// 	c.Context.Set("token", c.serv.NewToken(batchNo))
54
+// }

+ 120
- 0
controllers/base.go View File

@@ -0,0 +1,120 @@
1
+package controllers
2
+
3
+import (
4
+	"encoding/json"
5
+	"net/http"
6
+	"wechat-conf/service"
7
+	"wechat-conf/utils"
8
+
9
+	"github.com/astaxie/beego"
10
+	"github.com/astaxie/beego/config"
11
+)
12
+
13
+//BaseController 基础controller
14
+type BaseController struct {
15
+	beego.Controller
16
+	Context  *utils.Context
17
+	Configer map[string]config.Configer
18
+	RunMode  string
19
+	serv     *service.SysServ
20
+}
21
+
22
+//Prepare 继承beego的
23
+func (c *BaseController) Prepare() {
24
+	// 读取配置文件
25
+	c.initConfig()
26
+
27
+	// 初始化上下文
28
+	c.initContext()
29
+
30
+	// 鉴权
31
+	// c.authenticate()
32
+
33
+	// 路由对应的实际 Controller 初始化
34
+	c.initController()
35
+}
36
+
37
+// ResponseJSON 返回JSON数据
38
+func (c *BaseController) ResponseJSON(data interface{}) {
39
+	c.ResponseData(data, "", http.StatusOK)
40
+}
41
+
42
+// ResponseError 返回错误
43
+func (c *BaseController) ResponseError(err error, code ...int) {
44
+	if len(code) > 0 {
45
+		c.ResponseData(nil, err, code[0])
46
+	}
47
+
48
+	c.ResponseData(nil, err, http.StatusBadRequest)
49
+}
50
+
51
+// ResponseRaw 返回
52
+func (c *BaseController) ResponseRaw(msg []byte, stop ...bool) {
53
+	c.Ctx.ResponseWriter.Write(msg)
54
+
55
+	if stop != nil && len(stop) > 0 {
56
+		if !stop[0] {
57
+			return
58
+		}
59
+	}
60
+
61
+	c.destroyContext(true)
62
+	c.StopRun()
63
+}
64
+
65
+// ResponseData 自定义的JSON返回
66
+func (c *BaseController) ResponseData(data interface{}, msg interface{}, code int) {
67
+	status := code
68
+
69
+	sendMessage := ""
70
+	switch msgVal := msg.(type) {
71
+	case error:
72
+		sendMessage = msgVal.Error()
73
+	case string:
74
+		sendMessage = msgVal
75
+	default:
76
+		sendMessage = ""
77
+	}
78
+
79
+	c.Data["json"] = JSONMessage{status, sendMessage, data}
80
+	c.Ctx.Output.Header("Access-Control-Expose-Headers", utils.TokenHeader)
81
+
82
+	if status == http.StatusOK {
83
+		// 设置旧 token 过期
84
+		// c.SetTokenExipre()
85
+
86
+		// 生成新 token
87
+		// c.CreateNewToken()
88
+
89
+		token := c.Context.Get("token").(string)
90
+		c.Ctx.Output.Header(utils.TokenHeader, utils.TokenSchema+" "+token)
91
+	}
92
+
93
+	c.destroyContext(status < http.StatusMultipleChoices)
94
+	c.crosPolicy()
95
+	c.ServeJSON()
96
+	c.StopRun()
97
+}
98
+
99
+// initAppController 执行当前路由请求对应的 Controller 初始化
100
+func (c *BaseController) initController() {
101
+	if ctrl, ok := c.AppController.(ControllerInterface); ok {
102
+		ctrl.Constructor()
103
+	}
104
+}
105
+
106
+func (c *BaseController) GetBodyData() (map[string]interface{}, error) {
107
+	var data map[string]interface{}
108
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &data)
109
+	return data, err
110
+}
111
+
112
+func (c *BaseController) ResponseOtherEndPoint(msg map[string]interface{}) {
113
+	c.destroyContext(true)
114
+	c.Context.Destroy()
115
+
116
+	c.Data["json"] = msg
117
+
118
+	c.ServeJSON()
119
+	c.StopRun()
120
+}

+ 43
- 0
controllers/config.go View File

@@ -0,0 +1,43 @@
1
+package controllers
2
+
3
+import (
4
+	"wechat-conf/utils"
5
+
6
+	"github.com/astaxie/beego/config"
7
+)
8
+
9
+// 配置文件列表
10
+const (
11
+	AppConf    = "app"
12
+	AliYunConf = "aliyun"
13
+	WeChatConf = "wechat"
14
+	SMSConf    = "sms"
15
+)
16
+
17
+func (c *BaseController) initConfig() {
18
+	// c.RunMode = beego.AppConfig.String("runmode")
19
+
20
+	if c.Configer == nil {
21
+		c.Configer = make(map[string]config.Configer)
22
+	}
23
+
24
+	// 系统
25
+	appConf, _ := utils.GetConfiger("conf/app.conf")
26
+	if appConf != nil {
27
+		c.Configer[AppConf] = appConf
28
+	}
29
+
30
+	c.RunMode = appConf.String("runmode")
31
+
32
+	// 阿里云
33
+	aliConf, _ := utils.GetConfiger("conf/aliyun.conf")
34
+	if aliConf != nil {
35
+		c.Configer[AliYunConf] = aliConf
36
+	}
37
+
38
+	// 微信
39
+	wechatConf, _ := utils.GetConfiger("conf/wechat.conf")
40
+	if wechatConf != nil {
41
+		c.Configer[WeChatConf] = wechatConf
42
+	}
43
+}

+ 39
- 0
controllers/context.go View File

@@ -0,0 +1,39 @@
1
+package controllers
2
+
3
+import (
4
+	"wechat-conf/models"
5
+	"wechat-conf/utils"
6
+)
7
+
8
+/**
9
+* Context 说明
10
+* - 所有 stuct 类型均不是指针
11
+* - 包含内容如下
12
+* user					SysUser 									用户基本信息
13
+* customer			TaCustomer 								会员基本信息
14
+* userMap				TaUserMapping 						用户/会员 映射第三方账户
15
+* cases					[]SysUserCase 						用户所有案场信息
16
+* currentCase		SysUserCase 							当前案场
17
+* org						SysOrg 										用户当前组织
18
+**/
19
+
20
+// initContext 初始化 Context
21
+func (c *BaseController) initContext() {
22
+	c.Context = utils.NewContext(models.DBEngine, nil)
23
+	c.Context.Ready()
24
+}
25
+
26
+// initContext 销毁 Context
27
+func (c *BaseController) destroyContext(ok ...bool) {
28
+	if len(ok) == 0 || ok[0] {
29
+		c.Context.DB.Commit()
30
+	} else {
31
+		c.Context.DB.Rollback()
32
+	}
33
+
34
+	c.Context.Destroy()
35
+}
36
+
37
+func (c *BaseController) DestroyContext() {
38
+	c.destroyContext()
39
+}

+ 25
- 0
controllers/cros.go View File

@@ -0,0 +1,25 @@
1
+package controllers
2
+
3
+// Options 解决跨域先发送 options
4
+func (c *BaseController) Options() {
5
+	c.ResponseJSON("")
6
+}
7
+
8
+// crosPolicy 跨域策略
9
+func (c *BaseController) crosPolicy() {
10
+	appConf, ok := c.Configer[AppConf]
11
+	if ok {
12
+		runMode := appConf.String("runmode")
13
+		allowMode := appConf.String("cros::allowMode")
14
+		allowMethods := appConf.String("cros::allowMethods")
15
+		allowOrigin := appConf.String("cros::allowOrigin")
16
+		allowCredentials := appConf.String("cros::allowCredentials")
17
+
18
+		if runMode == allowMode {
19
+			c.Ctx.Output.Header("Access-Control-Allow-Origin", allowOrigin)
20
+			c.Ctx.Output.Header("Access-Control-Allow-Methods", allowMethods)
21
+			c.Ctx.Output.Header("Access-Control-Allow-Credentials", allowCredentials)
22
+			c.Ctx.Output.Header("Access-Control-Allow-Headers", "X-Token,Authorization")
23
+		}
24
+	}
25
+}

+ 98
- 0
controllers/file.go View File

@@ -0,0 +1,98 @@
1
+package controllers
2
+
3
+import (
4
+	"errors"
5
+	"strconv"
6
+	"time"
7
+	"wechat-conf/utils"
8
+
9
+	"net/http"
10
+	"net/url"
11
+)
12
+
13
+// FileUpload 文件上传
14
+func (c *BaseController) FileUpload() {
15
+	file, err := c.uploadFileToOSS("file")
16
+	if err != nil {
17
+		c.ResponseError(err, http.StatusInternalServerError)
18
+	}
19
+
20
+	c.ResponseJSON(map[string]interface{}{
21
+		"url": file,
22
+	})
23
+}
24
+
25
+func (c *BaseController) uploadFileToOSS(field string) (string, error) {
26
+	aliConf, ok := c.Configer[AliYunConf]
27
+	if !ok {
28
+		return "", errors.New("没有找到阿里云相关配置")
29
+	}
30
+
31
+	endpoint := aliConf.String("oss::Endpoint")
32
+	accessKeyID := aliConf.String("oss::AccessKeyId")
33
+	accessKeySecret := aliConf.String("oss::AccessKeySecret")
34
+	bucket := aliConf.String("oss::Bucket")
35
+
36
+	aliCli, err := utils.GetOssClient(endpoint, accessKeyID, accessKeySecret)
37
+	if err != nil {
38
+		return "", err
39
+	}
40
+
41
+	fNameExtra := strconv.FormatInt(time.Now().Unix(), 10)
42
+
43
+	fileURL, err := utils.UploadFileToBucket(aliCli, c.Ctx.Request, bucket, field, fNameExtra)
44
+	if err != nil {
45
+		return "", err
46
+	}
47
+
48
+	return fileURL, nil
49
+}
50
+
51
+// UploadBase64Image Upload base64-image to ali-oss
52
+// @Title Upload image to ali-oss
53
+// @Description 上传base64图片到阿里云
54
+// @Param   UpImgStr     form    string  true        "图片控件name"
55
+// @Success 200 { Url } 图片URL
56
+// @Failure >300 error message
57
+func (c *BaseController) UploadBase64Image() {
58
+	base64Str, err := url.QueryUnescape(c.GetString("base64str"))
59
+	if err != nil {
60
+		c.ResponseError(utils.LogError("上传 Base64 图片失败", err))
61
+	}
62
+
63
+	imgURL, err := c.uploadStringToAliOSS(base64Str)
64
+	if err != nil {
65
+		c.ResponseError(err)
66
+	}
67
+
68
+	resp := map[string]interface{}{
69
+		"url": imgURL,
70
+	}
71
+
72
+	c.ResponseJSON(resp)
73
+}
74
+
75
+// uploadStringToAliOSS 上传文件到阿里云
76
+func (c *BaseController) uploadStringToAliOSS(fStr string) (string, error) {
77
+	aliConf, ok := c.Configer[AliYunConf]
78
+	if !ok {
79
+		return "", errors.New("没有找到阿里云相关配置")
80
+	}
81
+
82
+	endpoint := aliConf.String("oss::Endpoint")
83
+	accessKeyID := aliConf.String("oss::AccessKeyId")
84
+	accessKeySecret := aliConf.String("oss::AccessKeySecret")
85
+	bucket := aliConf.String("oss::Bucket")
86
+
87
+	aliCli, err := utils.GetOssClient(endpoint, accessKeyID, accessKeySecret)
88
+	if err != nil {
89
+		return "", utils.LogError("配置阿里云客户端失败", err)
90
+	}
91
+
92
+	fileURL, err := utils.UploadStringToBucket(aliCli, bucket, fStr)
93
+	if err != nil {
94
+		return "", utils.LogError("上传文件到阿里云失败", err)
95
+	}
96
+
97
+	return fileURL, nil
98
+}

+ 13
- 0
controllers/types.go View File

@@ -0,0 +1,13 @@
1
+package controllers
2
+
3
+// JSONMessage 主要用于 Response 返回
4
+type JSONMessage struct {
5
+	Code    int         `json:"code"`
6
+	Message string      `json:"message"`
7
+	Result  interface{} `json:"result"`
8
+}
9
+
10
+// ControllerInterface 项目约定的 Controller 须满足的接口
11
+type ControllerInterface interface {
12
+	Constructor()
13
+}

+ 18
- 0
main.go View File

@@ -0,0 +1,18 @@
1
+package main
2
+
3
+import (
4
+	"wechat-conf/bootstrap"
5
+
6
+	"github.com/astaxie/beego"
7
+)
8
+
9
+func main() {
10
+	if beego.BConfig.RunMode == "dev" {
11
+		beego.BConfig.WebConfig.DirectoryIndex = true
12
+		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
13
+	}
14
+
15
+	bootstrap.SystemInit()
16
+
17
+	beego.Run()
18
+}

+ 16
- 0
models/constant.go View File

@@ -0,0 +1,16 @@
1
+package models
2
+
3
+// 状态列表
4
+const (
5
+	// 删除
6
+	STATUS_DEL = iota - 1
7
+
8
+	// 新录入
9
+	STATUS_READY
10
+
11
+	// 正常
12
+	STATUS_NORMAL
13
+
14
+	// 已使用
15
+	STATUS_USED
16
+)

+ 55
- 0
models/models.go View File

@@ -0,0 +1,55 @@
1
+package models
2
+
3
+import (
4
+	"wechat-conf/utils"
5
+
6
+	"github.com/astaxie/beego/config"
7
+	_ "github.com/go-sql-driver/mysql"
8
+	"github.com/go-xorm/xorm"
9
+)
10
+
11
+var (
12
+	DBEngine *xorm.Engine
13
+)
14
+
15
+func InitDB() {
16
+	DBEngine = NewDBEngine()
17
+}
18
+
19
+// NewDBEngine 初始化数据库连接
20
+func NewDBEngine() *xorm.Engine {
21
+	dbType := "mysql"
22
+	dns := getMySQLDNS()
23
+
24
+	engine, err := xorm.NewEngine(dbType, dns)
25
+	// engine.ShowSQL()
26
+
27
+	if err != nil {
28
+		panic(err)
29
+	}
30
+
31
+	return engine
32
+}
33
+
34
+func getMySQLDNS() (dns string) {
35
+	appRoot := utils.GetAppRoot()
36
+	dbconf, _ := config.NewConfig("ini", appRoot+"/conf/db.conf")
37
+
38
+	conProt := dbconf.DefaultString("con_protocol", "tcp")
39
+	dbAddr := dbconf.DefaultString("db_addr", "localhost")
40
+	dbPort := dbconf.DefaultString("db_port", "3306")
41
+	userName := dbconf.DefaultString("username", "root")
42
+	password := dbconf.String("password")
43
+	database := dbconf.String("database")
44
+	charSet := dbconf.DefaultString("char_set", "utf8")
45
+
46
+	dns = userName
47
+
48
+	if len(password) > 0 {
49
+		dns += ":" + password
50
+	}
51
+
52
+	dns += "@" + conProt + "(" + dbAddr + ":" + dbPort + ")" + "/" + database + "?charset=" + charSet
53
+
54
+	return
55
+}

+ 24
- 0
routers/router.go View File

@@ -0,0 +1,24 @@
1
+// @APIVersion 1.0.0
2
+// @Title beego Test API
3
+// @Description beego has a very cool tools to autogenerate documents for your API
4
+// @Contact astaxie@gmail.com
5
+// @TermsOfServiceUrl http://beego.me/
6
+// @License Apache 2.0
7
+// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html
8
+package routers
9
+
10
+import (
11
+	"wechat-conf/controllers"
12
+
13
+	"github.com/astaxie/beego"
14
+)
15
+
16
+func RouteInit() {
17
+
18
+	ns := beego.NewNamespace("/api",
19
+		// 解决跨域时 先发送 options 问题
20
+		beego.NSRouter("*", &controllers.BaseController{}, "options:Options"),
21
+	)
22
+
23
+	beego.AddNamespace(ns)
24
+}

+ 1
- 0
service/service.go View File

@@ -0,0 +1 @@
1
+package service

+ 508
- 0
service/sys.go View File

@@ -0,0 +1,508 @@
1
+package service
2
+
3
+import (
4
+	"wechat-conf/utils"
5
+)
6
+
7
+const (
8
+	PAGENUM = 10
9
+)
10
+
11
+// SysServ 系统处理
12
+type SysServ struct {
13
+	ctx *utils.Context
14
+}
15
+
16
+// NewSysServ 初始化
17
+func NewSysServ(ctx *utils.Context) *SysServ {
18
+	return &SysServ{
19
+		ctx: ctx,
20
+	}
21
+}
22
+
23
+// // AuthAndInitCtx 鉴权
24
+// // gctx 是 beego 框架中的 Context
25
+// func (s *SysServ) AuthAndInitCtx(gctx *context.Context) map[string]interface{} {
26
+// 	// 确认机构
27
+// 	orgID := gctx.Input.Query(":org")
28
+// 	if orgID == "" {
29
+// 		return map[string]interface{}{
30
+// 			"code":  http.StatusBadRequest,
31
+// 			"error": errors.New("接口地址访问不正确"),
32
+// 		}
33
+// 	}
34
+// 	if err := s.SetOrgByID(orgID); err != nil {
35
+// 		return map[string]interface{}{
36
+// 			"code":  http.StatusInternalServerError,
37
+// 			"error": err,
38
+// 		}
39
+// 	}
40
+
41
+// 	// 客户端类型
42
+// 	// 通过 UA 判断
43
+// 	clientType := utils.GetClientType(gctx.Request)
44
+
45
+// 	// pc 管理端
46
+// 	if clientType == utils.ClientAdmin {
47
+// 		return s.authPCAdmin(gctx)
48
+// 	}
49
+
50
+// 	// 小程序 端
51
+// 	if strings.Index(gctx.Input.URI(), "/wechat/mini") > -1 {
52
+// 		return s.authMini(gctx)
53
+// 	}
54
+
55
+// 	// wechat 端
56
+// 	if clientType == utils.ClientWechat {
57
+// 		return s.authWechat(gctx)
58
+// 	}
59
+
60
+// 	// if clientType == utils.ClientMini {
61
+// 	// 	return s.authMini(gctx)
62
+// 	// }
63
+
64
+// 	return map[string]interface{}{
65
+// 		"code":  http.StatusBadRequest,
66
+// 		"error": errors.New("暂无该客户端的 API"),
67
+// 	}
68
+// }
69
+
70
+// // NewToken 设置 TOKEN
71
+// // 15 分钟后过期
72
+// func (s *SysServ) NewToken(batch string) string {
73
+// 	var token *utils.JWTToken
74
+// 	exp := time.Now().Local().Add(15 * time.Second)
75
+
76
+// 	if s.ctx.Get("userMap") != nil {
77
+// 		userMap := s.ctx.Get("userMap").(model.TaUserMapping)
78
+
79
+// 		token = &utils.JWTToken{
80
+// 			Guest:   false,
81
+// 			ID:      userMap.Openid,
82
+// 			Expire:  exp,
83
+// 			BatchNo: batch,
84
+// 		}
85
+// 	} else if s.ctx.Get("user") != nil {
86
+// 		user := s.ctx.Get("user").(model.SysUser)
87
+
88
+// 		token = &utils.JWTToken{
89
+// 			Guest:   false,
90
+// 			ID:      user.UserId,
91
+// 			Expire:  exp,
92
+// 			BatchNo: batch,
93
+// 		}
94
+// 	} else {
95
+// 		token = &utils.JWTToken{
96
+// 			Guest:  true,
97
+// 			Expire: exp,
98
+// 		}
99
+// 	}
100
+
101
+// 	tokenEncodeStr, err := utils.CreateToken(token.ToMap())
102
+// 	if err != nil {
103
+// 		utils.LogError("系统生成 Token 失败: " + err.Error())
104
+// 		return ""
105
+// 	}
106
+
107
+// 	// 入库
108
+// 	if !token.Guest {
109
+// 		if err := models.InsertToken(tokenEncodeStr, token.ID, batch, exp); err != nil {
110
+// 			utils.LogError("入库 Token 失败: " + err.Error())
111
+// 			return tokenEncodeStr
112
+// 		}
113
+// 	}
114
+
115
+// 	return tokenEncodeStr
116
+// }
117
+
118
+// // authPCAdmin
119
+// // 管理端 API 校验
120
+// func (s *SysServ) authPCAdmin(gctx *context.Context) map[string]interface{} {
121
+// 	if !s.needAuth(gctx) {
122
+// 		return nil
123
+// 	}
124
+
125
+// 	// 获取 token
126
+// 	token, err := s.getToken(gctx)
127
+// 	if err != nil {
128
+// 		// token 报错一律视为需要重新登录
129
+// 		return map[string]interface{}{
130
+// 			"code":  http.StatusUnauthorized,
131
+// 			"error": err,
132
+// 		}
133
+// 	}
134
+
135
+// 	if token.ID == "" || token.Guest == true {
136
+// 		return map[string]interface{}{
137
+// 			"code":  http.StatusUnauthorized,
138
+// 			"error": errors.New("用户未登录"),
139
+// 		}
140
+// 	}
141
+
142
+// 	if err := s.SetUserProfile(token.ID); err != nil {
143
+// 		return map[string]interface{}{
144
+// 			"code":  http.StatusInternalServerError,
145
+// 			"error": err,
146
+// 		}
147
+// 	}
148
+
149
+// 	return nil
150
+// }
151
+
152
+// func (s *SysServ) authWechat(gctx *context.Context) map[string]interface{} {
153
+// 	var wxUser *utils.WechatUser
154
+// 	var openID string
155
+
156
+// 	if beego.BConfig.RunMode == "dev" {
157
+// 		openID = "OPENID"
158
+// 	} else {
159
+// 		// 初始化微信配置
160
+// 		if err := s.initWechatClient(s.org.OrgId); err != nil {
161
+// 			utils.LogError("初始化微信服务失败: " + err.Error())
162
+
163
+// 			return map[string]interface{}{
164
+// 				"code":  http.StatusInternalServerError,
165
+// 				"error": errors.New("初始化微信服务失败"),
166
+// 			}
167
+// 		}
168
+
169
+// 		// 微信 code
170
+// 		code := gctx.Input.Query("code")
171
+
172
+// 		// 获取 token
173
+// 		token, err := s.getToken(gctx)
174
+// 		if err != nil {
175
+// 			tokenStr := s.ctx.Get("token").(string)
176
+// 			if tokenStr != "" {
177
+// 				// token 报错一律视为需要重新登录
178
+// 				return map[string]interface{}{
179
+// 					"code":  http.StatusUnauthorized,
180
+// 					"error": err,
181
+// 					"message": map[string]interface{}{
182
+// 						"appid": utils.GetWxAppID(s.org.OrgId),
183
+// 					},
184
+// 				}
185
+// 			}
186
+// 		}
187
+
188
+// 		// 未登录 或 未验证
189
+// 		if token == nil || token.ID == "" {
190
+// 			if code == "" {
191
+// 				return map[string]interface{}{
192
+// 					"code":  http.StatusUnauthorized,
193
+// 					"error": errors.New("请授权微信用户登录"),
194
+// 					"message": map[string]interface{}{
195
+// 						"appid": utils.GetWxAppID(s.org.OrgId),
196
+// 					},
197
+// 				}
198
+// 			}
199
+
200
+// 			// 微信用户信息
201
+// 			var err error
202
+// 			wxUser, err = s.wechartSignIn(gctx, code)
203
+// 			if err != nil {
204
+// 				return map[string]interface{}{
205
+// 					"code":  http.StatusInternalServerError,
206
+// 					"error": err,
207
+// 				}
208
+// 			}
209
+
210
+// 			if wxUser == nil {
211
+// 				return map[string]interface{}{
212
+// 					"code":  http.StatusInternalServerError,
213
+// 					"error": errors.New("请先关注公众号"),
214
+// 				}
215
+// 			}
216
+
217
+// 			utils.LogError("获取到微信人员: ", wxUser)
218
+
219
+// 			openID = wxUser.OpenID
220
+// 		} else {
221
+// 			openID = token.ID
222
+// 		}
223
+// 	}
224
+
225
+// 	// 查询数据库是否存在已有映射
226
+// 	userMapList, err := models.GetUserMappingByOpenID(openID)
227
+// 	if err != nil {
228
+// 		utils.LogError("校验人员失败: " + err.Error())
229
+// 		return map[string]interface{}{
230
+// 			"code":  http.StatusInternalServerError,
231
+// 			"error": errors.New("校验人员失败"),
232
+// 		}
233
+// 	}
234
+
235
+// 	var userMapping *model.TaUserMapping
236
+// 	for _, ump := range userMapList {
237
+// 		if openID == ump.Openid && models.ACCMAP_WECHAT == ump.AccountType {
238
+// 			userMapping = &ump
239
+// 		}
240
+// 	}
241
+
242
+// 	// 如果尚无人员映射信息, 代表人员初次使用本系统
243
+// 	if userMapping == nil {
244
+// 		// 如果没有微信用户信息, 代表产生了未知异常
245
+// 		if wxUser == nil || wxUser.OpenID == "" {
246
+// 			return map[string]interface{}{
247
+// 				"code":  http.StatusInternalServerError,
248
+// 				"error": errors.New("系统异常, 请清空缓存后重试"),
249
+// 			}
250
+// 		}
251
+
252
+// 		wxInfoJSON, err := json.Marshal(wxUser)
253
+// 		if err != nil {
254
+// 			utils.LogError("转换微信json信息失败: " + err.Error())
255
+// 			return map[string]interface{}{
256
+// 				"code":  http.StatusInternalServerError,
257
+// 				"error": errors.New("微信信息异常"),
258
+// 			}
259
+// 		}
260
+
261
+// 		userMapping = &model.TaUserMapping{
262
+// 			AccountType: models.ACCMAP_WECHAT,
263
+// 			Openid:      openID,
264
+// 			Uuid:        wxUser.UnionID,
265
+// 			AccountInfo: string(wxInfoJSON),
266
+// 		}
267
+// 	}
268
+
269
+// 	// 防止JSON解析失败
270
+// 	if userMapping.AccountInfo == "" {
271
+// 		userMapping.AccountInfo = "{}"
272
+// 	}
273
+
274
+// 	// 更新映射信息, 没有的话则插入
275
+// 	err = models.EditUserMapping(userMapping)
276
+// 	if err != nil {
277
+// 		utils.LogError("保存用户映射信息失败: " + err.Error())
278
+// 		return map[string]interface{}{
279
+// 			"code":  http.StatusInternalServerError,
280
+// 			"error": errors.New("更新用户信息失败"),
281
+// 		}
282
+// 	}
283
+// 	s.ctx.Set("userMap", *userMapping)
284
+
285
+// 	// if !s.needAuth(gctx) {
286
+// 	// 	return nil
287
+// 	// }
288
+
289
+// 	var cust *model.TaCustomer
290
+
291
+// 	// 如果只有映射, 但是没有人员信息
292
+// 	// 则新增人员
293
+// 	if userMapping.UserId == "" {
294
+// 		cust, err = s.saveNewCustomer(wxUser, userMapping)
295
+// 		if err != nil {
296
+// 			return map[string]interface{}{
297
+// 				"code":  http.StatusInternalServerError,
298
+// 				"error": err,
299
+// 			}
300
+// 		}
301
+// 	} else {
302
+// 		cust, err = models.GetCustomerByID(userMapping.UserId)
303
+// 		if err != nil {
304
+// 			utils.LogError("查询用户信息失败: " + err.Error())
305
+// 			return map[string]interface{}{
306
+// 				"code":  http.StatusInternalServerError,
307
+// 				"error": err,
308
+// 			}
309
+// 		}
310
+// 	}
311
+// 	s.ctx.Set("customer", *cust)
312
+
313
+// 	if cust.UserId != "" {
314
+// 		if err := s.SetUserProfile(cust.UserId); err != nil {
315
+// 			return map[string]interface{}{
316
+// 				"code":  http.StatusInternalServerError,
317
+// 				"error": err,
318
+// 			}
319
+// 		}
320
+// 	}
321
+
322
+// 	return nil
323
+// }
324
+
325
+// // 小程序端暂时无人员或者其他业务要求
326
+// func (s *SysServ) authMini(gctx *context.Context) map[string]interface{} {
327
+// 	if err := s.initMiniClient(s.org.OrgId); err != nil {
328
+// 		utils.LogError("初始化小程序服务失败: " + err.Error())
329
+
330
+// 		return map[string]interface{}{
331
+// 			"code":  http.StatusInternalServerError,
332
+// 			"error": errors.New("初始化小程序服务失败"),
333
+// 		}
334
+// 	}
335
+
336
+// 	return nil
337
+// }
338
+
339
+// // wechartSignIn 使用 code 微信登录
340
+// func (s *SysServ) wechartSignIn(gctx *context.Context, code string) (*utils.WechatUser, error) {
341
+// 	// 获取 微信信息
342
+// 	// 可能出现的情况是 openid 获取到了, 但是详情没有获取到
343
+// 	wxUserMap, err := utils.WxClientFor(s.org.OrgId).GetUserInfo(code)
344
+// 	if err != nil {
345
+// 		utils.LogError("获取微信信息失败: " + err.Error())
346
+
347
+// 		if wxUserMap == nil {
348
+// 			return nil, errors.New("获取微信信息失败")
349
+// 		}
350
+// 	}
351
+
352
+// 	return utils.MapToWechatUser(wxUserMap), nil
353
+// }
354
+
355
+// func (s *SysServ) getToken(gctx *context.Context) (*utils.JWTToken, error) {
356
+// 	tokenEnStr := gctx.Input.Query("token")
357
+// 	if tokenEnStr == "" {
358
+// 		tokenRaw := gctx.Input.Header(utils.TokenHeader)
359
+// 		if tokenRaw == "" {
360
+// 			return new(utils.JWTToken), nil
361
+// 		}
362
+
363
+// 		tokenEnStr = strings.Trim(strings.TrimLeft(tokenRaw, utils.TokenSchema), " ")
364
+// 	} else {
365
+// 		tokenEnStr = strings.Trim(strings.TrimLeft(tokenEnStr, utils.TokenSchema), " ")
366
+// 	}
367
+
368
+// 	s.ctx.Set("token", tokenEnStr)
369
+
370
+// 	token, err := utils.PareseToken(tokenEnStr)
371
+// 	if err != nil {
372
+// 		utils.LogError("解析 Token 失败: " + err.Error())
373
+// 		return nil, errors.New("解析Token失败或已过期")
374
+// 	}
375
+
376
+// 	// 校验 token
377
+// 	tk, err := models.GetToken(tokenEnStr)
378
+// 	if err != nil {
379
+// 		utils.LogError("查询 Token 失败: " + err.Error())
380
+// 		return nil, errors.New("校验Token失败或已过期")
381
+// 	}
382
+
383
+// 	if tk.Status == models.STATUS_DEL {
384
+// 		return nil, errors.New("超时 或者 Token 已过期")
385
+// 	}
386
+
387
+// 	s.ctx.Set("token-batch", tk.BatchNo)
388
+// 	return utils.MapToJWTToken(token), nil
389
+// }
390
+
391
+// // UpdateTokenExpire 更新 token 为过期
392
+// // 如果发生错误, 此处选择忽略
393
+// func (s *SysServ) UpdateTokenExpire(token, uid string) {
394
+// 	if err := models.UpdateTokenExpire(token, uid); err != nil {
395
+// 		utils.LogError("更新 Token 过期失败: " + err.Error())
396
+// 	}
397
+// }
398
+
399
+// func (s *SysServ) needAuth(gctx *context.Context) bool {
400
+// 	route := gctx.Input.URL()
401
+// 	apiPrefix := beego.AppConfig.String("api::prefix")
402
+// 	guestAPI := beego.AppConfig.String("api::guest")
403
+
404
+// 	if strings.Index(route, apiPrefix+strings.Split(guestAPI, ":")[0]) > -1 {
405
+// 		return false
406
+// 	}
407
+
408
+// 	return true
409
+// }
410
+
411
+// // SetUserProfile 设置用户信息
412
+// func (s *SysServ) SetUserProfile(id string) error {
413
+// 	user, err := models.GetPureUserInfo(id)
414
+// 	if err != nil {
415
+// 		return utils.LogError("获取用户基本信息失败: " + err.Error())
416
+// 	}
417
+// 	s.ctx.Set("user", *user)
418
+
419
+// 	cases, err := models.GetUserCase(id)
420
+// 	if err != nil {
421
+// 		return utils.LogError("获取用户案场信息失败: " + err.Error())
422
+// 	}
423
+// 	s.ctx.Set("cases", cases)
424
+
425
+// 	found := false
426
+// 	for _, cs := range cases {
427
+// 		if cs.IsBelong == models.BOOL_TRUE {
428
+// 			found = true
429
+// 			s.ctx.Set("currentCase", cs)
430
+// 		}
431
+// 	}
432
+
433
+// 	if !found {
434
+// 		utils.LogError("用户没有设置默认案场")
435
+// 	}
436
+
437
+// 	return nil
438
+// }
439
+
440
+// // saveNewCustomer 新增用户
441
+// func (s *SysServ) saveNewCustomer(wxUser *utils.WechatUser, userMap *model.TaUserMapping) (*model.TaCustomer, error) {
442
+// 	cust := model.TaCustomer{
443
+// 		CustomerName: wxUser.NickName,
444
+// 		Name:         wxUser.NickName,
445
+// 		Sex:          int(wxUser.Sex),
446
+// 		Headimgurl:   wxUser.HeadImgURL,
447
+// 		OrgId:        s.org.OrgId,
448
+// 	}
449
+
450
+// 	if err := models.SaveCustomer(&cust); err != nil {
451
+// 		utils.LogError("更新客户信息失败: " + err.Error())
452
+// 		return nil, errors.New("更新客户信息失败")
453
+// 	}
454
+
455
+// 	account := new(model.TaCustomerAccount)
456
+// 	account.CustomerId = cust.CustomerId
457
+// 	account.CustomerName = cust.CustomerName
458
+// 	account.OrgId = cust.OrgId
459
+// 	account.Amount = "0"
460
+// 	account.Points = "0"
461
+// 	account.PayedAmount = "0"
462
+// 	account.PayedPoints = "0"
463
+
464
+// 	if err := models.SaveAccount(account); err != nil {
465
+// 		utils.LogError("插入账户信息失败: " + err.Error())
466
+// 		return nil, errors.New("更新客户信息失败")
467
+// 	}
468
+
469
+// 	// 更新映射表信息
470
+// 	userMap.UserId = cust.CustomerId
471
+// 	if err := models.UpdateUserMapping(userMap, []string{"user_id"}); err != nil {
472
+// 		utils.LogError("更新用户映射信息失败:" + err.Error())
473
+// 		return nil, errors.New("映射用户信息失败")
474
+// 	}
475
+
476
+// 	return &cust, nil
477
+// }
478
+
479
+// // initWechatClient 初始化微信客户端
480
+// func (s *SysServ) initWechatClient(orgID string) error {
481
+// 	cert, err := models.GetWeChatConfig(orgID, models.WECHAT_WX)
482
+// 	if err != nil {
483
+// 		utils.LogError("获取微信配置失败: " + err.Error())
484
+// 		return errors.New("获取微信配置失败")
485
+// 	}
486
+
487
+// 	if cert == nil {
488
+// 		return errors.New("未找到微信配置")
489
+// 	}
490
+
491
+// 	utils.WxClientSingleton(orgID, cert)
492
+// 	return nil
493
+// }
494
+
495
+// func (s *SysServ) initMiniClient(orgID string) error {
496
+// 	cert, err := models.GetWeChatConfig(orgID, models.WECHAT_MINI)
497
+// 	if err != nil {
498
+// 		utils.LogError("获取小程序配置失败: " + err.Error())
499
+// 		return errors.New("获取小程序配置失败")
500
+// 	}
501
+
502
+// 	if cert == nil {
503
+// 		return errors.New("未找到小程序配置")
504
+// 	}
505
+
506
+// 	utils.MiniClientSingleton(orgID, cert)
507
+// 	return nil
508
+// }

+ 19
- 0
test.http View File

@@ -0,0 +1,19 @@
1
+@domain = localhost:8080
2
+@guestAPI = api/guest
3
+@phone = 17714208769
4
+
5
+@contentForm = application/x-www-form-urlencoded
6
+@contentJSON = application/json
7
+# 验证码
8
+
9
+GET http://{{domain}}/{{guestAPI}}/captcha?phone={{phone}}
10
+
11
+
12
+###
13
+POST http://dev.ycjcjy.com/api-v2/api/guest/wxsignup
14
+content-type: {{contentForm}}
15
+
16
+phone={{phone}}
17
+&captcha=648633
18
+&case=1
19
+&sales=0

+ 46
- 0
tests/cases_test.go View File

@@ -0,0 +1,46 @@
1
+package tests
2
+
3
+import (
4
+	"net/http"
5
+	"net/url"
6
+	"wechat-conf/controllers"
7
+	"testing"
8
+
9
+	. "github.com/smartystreets/goconvey/convey"
10
+)
11
+
12
+func TestGetCmsCaseList(t *testing.T) {
13
+
14
+	Convey("获取案场列表", t, func() {
15
+		Convey("正常业务", func() {
16
+			params := url.Values{}
17
+			params.Add("orgid", "1")
18
+
19
+			result := &controllers.JSONMessage{}
20
+
21
+			code, _, err := NewRequestMock().Request(http.MethodGet, "/api/guest/MQ/cms/case", params, result)
22
+
23
+			// http 常规判断, 可以省略
24
+			So(code, ShouldEqual, http.StatusOK)
25
+			So(err, ShouldBeNil)
26
+
27
+			// 业务返回判断
28
+			So(result.Code, ShouldEqual, http.StatusOK)
29
+			So(result.Result, ShouldNotBeEmpty)
30
+		})
31
+
32
+		Convey("机构不传报错", func() {
33
+			result := &controllers.JSONMessage{}
34
+
35
+			code, _, err := NewRequestMock().Request(http.MethodGet, "/api/guest/MQ/cms/case", nil, result)
36
+
37
+			// http 常规判断, 可以省略
38
+			So(code, ShouldEqual, http.StatusOK)
39
+			So(err, ShouldBeNil)
40
+
41
+			// 业务返回判断
42
+			So(result.Code, ShouldNotEqual, http.StatusOK)
43
+		})
44
+
45
+	})
46
+}

+ 74
- 0
tests/courseorder_test.go View File

@@ -0,0 +1,74 @@
1
+package tests
2
+
3
+import (
4
+	"net/http"
5
+	"net/url"
6
+	"wechat-conf/controllers"
7
+	"wechat-conf/utils"
8
+	"testing"
9
+	"time"
10
+
11
+	"github.com/astaxie/beego"
12
+
13
+	. "github.com/smartystreets/goconvey/convey"
14
+)
15
+
16
+func TestPreCourseOrder(t *testing.T) {
17
+	Convey("课程下单", t, func() {
18
+
19
+		Convey("正常下单", func() {
20
+			params := url.Values{}
21
+			params.Add("info", `{"CourseId":"ecc55f91-52b4-4e9b-89a8-b9b3362730f9","CaseId":"10","Price":"2.00"}`)
22
+			result := &controllers.JSONMessage{}
23
+			api := "/api/wechat/MQ/order/course"
24
+
25
+			token := &utils.JWTToken{
26
+				Guest:   false,
27
+				ID:      "oMOpz0kgTrasoAA3G70R7phomn1g", // openid
28
+				Expire:  time.Now().Local().Add(24 * 30 * time.Hour),
29
+				BatchNo: "",
30
+			}
31
+
32
+			code, _, err := NewRequestMock().
33
+				AddToken(token.ToMap()).
34
+				AddWechatUA().
35
+				Request(http.MethodPost, api, params, result)
36
+
37
+			beego.Trace(result)
38
+
39
+			So(code, ShouldEqual, http.StatusOK)
40
+			So(err, ShouldBeNil)
41
+
42
+			// 业务返回判断
43
+			So(result.Code, ShouldEqual, http.StatusOK)
44
+			So(result.Result, ShouldNotBeEmpty)
45
+
46
+			orderID := result.Result.(map[string]interface{})["OrdersId"].(string)
47
+
48
+			Convey("下单确认", func() {
49
+				result := &controllers.JSONMessage{}
50
+				api := "/api/wechat/MQ/order/course/" + orderID
51
+
52
+				token := &utils.JWTToken{
53
+					Guest:   false,
54
+					ID:      "oMOpz0kgTrasoAA3G70R7phomn1g", // openid
55
+					Expire:  time.Now().Local().Add(24 * 30 * time.Hour),
56
+					BatchNo: "",
57
+				}
58
+
59
+				code, _, err := NewRequestMock().
60
+					AddToken(token.ToMap()).
61
+					AddWechatUA().
62
+					Request(http.MethodPut, api, nil, result)
63
+
64
+				beego.Trace(result)
65
+
66
+				So(code, ShouldEqual, http.StatusOK)
67
+				So(err, ShouldBeNil)
68
+
69
+				// 业务返回判断
70
+				So(result.Code, ShouldEqual, http.StatusOK)
71
+			})
72
+		})
73
+	})
74
+}

+ 70
- 0
tests/goodsorder_test.go View File

@@ -0,0 +1,70 @@
1
+package tests
2
+
3
+import (
4
+	"net/http"
5
+	"net/url"
6
+	"wechat-conf/controllers"
7
+	"wechat-conf/utils"
8
+	"testing"
9
+	"time"
10
+
11
+	. "github.com/smartystreets/goconvey/convey"
12
+)
13
+
14
+func TestPreGoodsOrder(t *testing.T) {
15
+	Convey("商品下单", t, func() {
16
+
17
+		Convey("正常下单", func() {
18
+			params := url.Values{}
19
+			params.Add("info", `{"CaseId":"1","AreaId":"12","AreaName":"吧台","TableId":"28","TableNo":"城咖啡水吧台","Amount":"23","OrdersNum":1,"Remark":"","OrgId":"1","UserName":"奥利奥","PayType":"coupon"}`)
20
+			params.Add("detail", `[{"GoodsId":"61","GoodsName":"美式","SpecId":"40","SpecName":"去冰","Number":1,"Price":"23"}]`)
21
+
22
+			result := &controllers.JSONMessage{}
23
+			api := "/api/wechat/MQ/order/goods"
24
+
25
+			token := &utils.JWTToken{
26
+				Guest:   false,
27
+				ID:      "oMOpz0kgTrasoAA3G70R7phomn1g", // openid
28
+				Expire:  time.Now().Local().Add(24 * 30 * time.Hour),
29
+				BatchNo: "",
30
+			}
31
+
32
+			code, _, err := NewRequestMock().
33
+				AddToken(token.ToMap()).
34
+				AddWechatUA().
35
+				Request(http.MethodPost, api, params, result)
36
+
37
+			So(code, ShouldEqual, http.StatusOK)
38
+			So(err, ShouldBeNil)
39
+
40
+			// 业务返回判断
41
+			So(result.Code, ShouldEqual, http.StatusOK)
42
+			So(result.Result, ShouldNotBeEmpty)
43
+
44
+			orderID := result.Result.(map[string]interface{})["OrdersId"].(string)
45
+
46
+			Convey("下单确认", func() {
47
+				result := &controllers.JSONMessage{}
48
+				api := "/api/wechat/MQ/order/goods/" + orderID
49
+
50
+				token := &utils.JWTToken{
51
+					Guest:   false,
52
+					ID:      "oMOpz0kgTrasoAA3G70R7phomn1g", // openid
53
+					Expire:  time.Now().Local().Add(24 * 30 * time.Hour),
54
+					BatchNo: "",
55
+				}
56
+
57
+				code, _, err := NewRequestMock().
58
+					AddToken(token.ToMap()).
59
+					AddWechatUA().
60
+					Request(http.MethodPut, api, nil, result)
61
+
62
+				So(code, ShouldEqual, http.StatusOK)
63
+				So(err, ShouldBeNil)
64
+
65
+				// 业务返回判断
66
+				So(result.Code, ShouldEqual, http.StatusOK)
67
+			})
68
+		})
69
+	})
70
+}

+ 27
- 0
tests/tests.go View File

@@ -0,0 +1,27 @@
1
+package tests
2
+
3
+/**
4
+*
5
+* 使用的测试框架为  http://labix.org/gocheck
6
+*
7
+**/
8
+
9
+import (
10
+	"wechat-conf/bootstrap"
11
+	"wechat-conf/utils"
12
+	"testing"
13
+
14
+	"github.com/astaxie/beego"
15
+	. "github.com/smartystreets/goconvey/convey"
16
+)
17
+
18
+func TestHelloWorld(t *testing.T) {
19
+	Convey("Hello go tester !", t, func() {
20
+		So(2, ShouldEqual, 2)
21
+	})
22
+}
23
+
24
+func init() {
25
+	bootstrap.SystemInit()
26
+	beego.TestBeegoInit(utils.GetAppRoot())
27
+}

+ 231
- 0
tests/utils.go View File

@@ -0,0 +1,231 @@
1
+package tests
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/json"
6
+	"errors"
7
+	"fmt"
8
+	"io"
9
+	"io/ioutil"
10
+	"mime/multipart"
11
+	"net/http"
12
+	"net/http/httptest"
13
+	"net/url"
14
+	"os"
15
+	"wechat-conf/utils"
16
+	"strconv"
17
+	"strings"
18
+	"time"
19
+
20
+	"github.com/astaxie/beego"
21
+)
22
+
23
+// RequestMock 模拟请求
24
+type RequestMock struct {
25
+	Headers map[string]string
26
+	Host    string
27
+}
28
+
29
+// NewRequestMock new inst
30
+func NewRequestMock() *RequestMock {
31
+	return &RequestMock{
32
+		Headers: make(map[string]string),
33
+	}
34
+}
35
+
36
+// SetHeaders 设置自定义请求头
37
+func (t *RequestMock) SetHeaders(headers map[string]string) *RequestMock {
38
+	if headers != nil {
39
+		for k, v := range headers {
40
+			t.Headers[k] = v
41
+		}
42
+	}
43
+
44
+	return t
45
+}
46
+
47
+// AddWechatUA 添加微信UA
48
+func (t *RequestMock) AddWechatUA() *RequestMock {
49
+	t.Headers["User-Agent"] = "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12A365 MicroMessenger/5.4.1 NetType/WIFI"
50
+	return t
51
+}
52
+
53
+// AddToken 添加Token
54
+func (t *RequestMock) AddToken(dt map[string]interface{}) *RequestMock {
55
+	token, _ := utils.CreateToken(dt)
56
+	t.Headers[utils.TokenHeader] = utils.TokenSchema + " " + token
57
+	return t
58
+}
59
+
60
+// SetHost 设置 host 包含 port
61
+func (t *RequestMock) SetHost(host string) *RequestMock {
62
+	t.Host = host
63
+	return t
64
+}
65
+
66
+// Request 请求
67
+// 暂时只支持 params 是 url.Values 及 map[string]interface{} 两种类型
68
+// url.Values 将被理解为 content-type="application/x-www-form-urlencoded"
69
+// map[string]interface{} 将被理解为 content-type="multipart/form-data"
70
+func (t *RequestMock) Request(meth, addr string, params interface{}, result interface{}) (code int, body []byte, err error) {
71
+	var r *http.Request
72
+	defer t.SetHeaders(nil)
73
+
74
+	code = 0
75
+	body = nil
76
+	err = nil
77
+
78
+	if t.Host != "" {
79
+		addr = t.Host + addr
80
+	} else {
81
+		addr = "http://127.0.0.1:8080" + addr
82
+	}
83
+
84
+	switch meth {
85
+
86
+	// get 请求, 只支持 params 为 url.Values 的参数
87
+	case http.MethodGet:
88
+		if params != nil {
89
+			if dt, ok := params.(url.Values); ok {
90
+				searchStr := dt.Encode()
91
+				if strings.Index(addr, "?") > -1 {
92
+					searchStr = "&" + searchStr
93
+				} else {
94
+					searchStr = "?" + searchStr
95
+				}
96
+				r, _ = http.NewRequest(meth, addr+searchStr, nil)
97
+			}
98
+		} else {
99
+			r, _ = http.NewRequest(meth, addr, nil)
100
+		}
101
+
102
+	// post, put, delete
103
+	case http.MethodPost, http.MethodPut, http.MethodDelete:
104
+		rb, ct, e := GetBody(params)
105
+		if e != nil {
106
+			err = e
107
+			return
108
+		}
109
+
110
+		r, _ = http.NewRequest(meth, addr, rb)
111
+		if ct != "" {
112
+			r.Header.Set("Content-Type", ct)
113
+		}
114
+
115
+	// 其他的本系统没有使用
116
+	default:
117
+		err = errors.New("不支持的请求类型")
118
+		return
119
+	}
120
+
121
+	// 自定义的 header 头
122
+	if t.Headers != nil {
123
+		for k, v := range t.Headers {
124
+			r.Header.Set(k, v)
125
+		}
126
+	}
127
+
128
+	w := httptest.NewRecorder()
129
+	beego.BeeApp.Handlers.ServeHTTP(w, r)
130
+
131
+	code = w.Code
132
+	body, _ = ioutil.ReadAll(w.Result().Body)
133
+
134
+	beego.Trace("testing", meth+" - "+addr)
135
+
136
+	if result != nil {
137
+		err = json.Unmarshal(body, result)
138
+		return
139
+	}
140
+
141
+	return
142
+}
143
+
144
+func GetBody(params interface{}) (body io.Reader, contentType string, err error) {
145
+	body = nil
146
+	contentType = ""
147
+	err = nil
148
+
149
+	if params == nil {
150
+		return
151
+	}
152
+
153
+	t := fmt.Sprintf("%T", params)
154
+	switch t {
155
+
156
+	// 被会解析为 application/x-www-form-urlencoded
157
+	case "url.Values":
158
+		data, _ := params.(url.Values)
159
+		body = strings.NewReader(data.Encode())
160
+		contentType = "application/x-www-form-urlencoded"
161
+		return
162
+
163
+	// 被会解析为 multipart/form-data
164
+	case "map[string]interface {}":
165
+		var b bytes.Buffer
166
+		var fw io.Writer
167
+
168
+		w := multipart.NewWriter(&b)
169
+		data, _ := params.(map[string]interface{})
170
+
171
+		// 遍历字段
172
+		for k, v := range data {
173
+			switch x := v.(type) {
174
+
175
+			// 文件
176
+			case *os.File:
177
+				if fw, err = w.CreateFormFile(k, x.Name()); err != nil {
178
+					return
179
+				}
180
+
181
+				if _, err = io.Copy(fw, x); err != nil {
182
+					return
183
+				}
184
+
185
+			// 字符串
186
+			case string:
187
+				err = w.WriteField(k, x)
188
+				if err != nil {
189
+					return
190
+				}
191
+
192
+			// 整数, 暂不支持 int 各种原始类型, 比如 int32, int64 等
193
+			case int:
194
+				dt := strconv.Itoa(x)
195
+				err = w.WriteField(k, dt)
196
+				if err != nil {
197
+					return
198
+				}
199
+
200
+			// 小数, 暂时不支持其他类型的浮点数
201
+			case float64:
202
+				dt := strconv.FormatFloat(x, 'f', -1, 64)
203
+				err = w.WriteField(k, dt)
204
+				if err != nil {
205
+					return
206
+				}
207
+
208
+			// 时间
209
+			case time.Time:
210
+				dt := x.Format("2006-01-02 15:04:05")
211
+				err = w.WriteField(k, dt)
212
+				if err != nil {
213
+					return
214
+				}
215
+
216
+			// 其他
217
+			default:
218
+				err = fmt.Errorf("暂时不支持 key "+k+" 对应的数据类型 %T", x)
219
+				return
220
+			}
221
+		}
222
+
223
+		body = bytes.NewReader(b.Bytes())
224
+		contentType = w.FormDataContentType()
225
+		return
226
+
227
+	default:
228
+		err = fmt.Errorf("暂时不支持的参数类型 " + t)
229
+		return
230
+	}
231
+}

+ 104
- 0
utils/aliyun.go View File

@@ -0,0 +1,104 @@
1
+package utils
2
+
3
+import (
4
+	"bytes"
5
+	"encoding/base64"
6
+	"net/http"
7
+	"strings"
8
+	"time"
9
+
10
+	"github.com/yl10/kit/guid"
11
+
12
+	"github.com/aliyun/aliyun-oss-go-sdk/oss"
13
+)
14
+
15
+// GetOssClient 获取 OSS 客户端
16
+func GetOssClient(endpoint, accessKeyID, accessKeySecret string) (*oss.Client, error) {
17
+	return oss.New(endpoint, accessKeyID, accessKeySecret)
18
+}
19
+
20
+// UploadFileToBucket 简单上传文件
21
+func UploadFileToBucket(cli *oss.Client, req *http.Request, bucket, fromFront string, nameSuffix ...string) (string, error) {
22
+	bkt, err := cli.Bucket(bucket)
23
+	if err != nil {
24
+		return "", err
25
+	}
26
+
27
+	file, fh, err := req.FormFile(fromFront)
28
+	if err != nil {
29
+		return "", err
30
+	}
31
+	defer file.Close()
32
+
33
+	fname := fh.Filename
34
+	if len(nameSuffix) > 0 && nameSuffix[0] != "" {
35
+		fArr := strings.Split(fname, ".")
36
+		fArr[0] = fArr[0] + nameSuffix[0]
37
+		fname = strings.Join(fArr, ".")
38
+	}
39
+
40
+	err = bkt.PutObject(fname, file)
41
+	if err != nil {
42
+		return "", err
43
+	}
44
+
45
+	return strings.Join([]string{
46
+		"http://",
47
+		bucket + ".",
48
+		cli.Config.Endpoint + "/",
49
+		fname,
50
+	}, ""), nil
51
+}
52
+
53
+// UploadStringToBucket 上传base64图片
54
+func UploadStringToBucket(cli *oss.Client, bucket, fromStr string) (string, error) {
55
+	bkt, err := cli.Bucket(bucket)
56
+	if err != nil {
57
+		return "", err
58
+	}
59
+
60
+	imgName := guid.NewGUIDString() + "-" + time.Now().Local().Format("20060102150405")
61
+	imgExt := ".jpg"
62
+	switch getMIMEFromBase64(fromStr) {
63
+	case "image/gif":
64
+		imgExt = ".gif"
65
+	case "image/bmp":
66
+		imgExt = ".bmp"
67
+	case "image/png", "image/x-png":
68
+		imgExt = ".png"
69
+	case "image/pipeg":
70
+		imgExt = ".jfif"
71
+	case "image/webp":
72
+		imgExt = ".webp"
73
+	}
74
+
75
+	fName := imgName + imgExt
76
+
77
+	imgData, err := base64.StdEncoding.DecodeString(strings.Split(fromStr, "base64,")[1])
78
+	if err != nil {
79
+		return "", err
80
+	}
81
+
82
+	err = bkt.PutObject(fName, bytes.NewBuffer(imgData))
83
+	if err != nil {
84
+		return "", err
85
+	}
86
+
87
+	return strings.Join([]string{
88
+		"http://",
89
+		bucket + ".",
90
+		cli.Config.Endpoint + "/",
91
+		fName,
92
+	}, ""), nil
93
+}
94
+
95
+// utils
96
+// getMIMEFromBase64 获取 base64 字串中的 mime
97
+func getMIMEFromBase64(str string) string {
98
+	g := strings.Split(str, ";")
99
+	if len(g) < 2 {
100
+		return ""
101
+	}
102
+
103
+	return strings.ToLower(strings.TrimLeft(g[0], "data:"))
104
+}

+ 15
- 0
utils/configer.go View File

@@ -0,0 +1,15 @@
1
+package utils
2
+
3
+import (
4
+	"github.com/astaxie/beego/config"
5
+)
6
+
7
+// GetConfiger 获取配置
8
+func GetConfiger(file string) (config.Configer, error) {
9
+	conf, err := config.NewConfig("ini", file)
10
+	if err != nil {
11
+		return nil, LogError("读取配置文件失败", err.Error())
12
+	}
13
+
14
+	return conf, nil
15
+}

+ 59
- 0
utils/context.go View File

@@ -0,0 +1,59 @@
1
+package utils
2
+
3
+import (
4
+	"github.com/go-xorm/xorm"
5
+)
6
+
7
+// Context 上下文
8
+// 只限制在每次 Request 内
9
+type Context struct {
10
+	DB  *xorm.Session
11
+	box map[string]interface{}
12
+}
13
+
14
+// NewContext 初始化上下文
15
+func NewContext(engine *xorm.Engine, opt map[string]interface{}) *Context {
16
+	return &Context{
17
+		DB:  engine.NewSession(),
18
+		box: opt,
19
+	}
20
+}
21
+
22
+// Get 获取
23
+func (c *Context) Get(key string, def ...interface{}) interface{} {
24
+	if c.box != nil {
25
+		if opt, has := c.box[key]; has {
26
+			return opt
27
+		}
28
+	}
29
+
30
+	if len(def) > 0 {
31
+		return def[0]
32
+	}
33
+
34
+	return nil
35
+}
36
+
37
+// Set 设置
38
+func (c *Context) Set(key string, val interface{}) {
39
+	if c.box == nil {
40
+		c.box = make(map[string]interface{})
41
+	}
42
+
43
+	c.box[key] = val
44
+}
45
+
46
+// Ready Request 级别初始化
47
+func (c *Context) Ready() error {
48
+	// 开启事务
49
+	if err := c.DB.Begin(); err != nil {
50
+		return err
51
+	}
52
+
53
+	return nil
54
+}
55
+
56
+// Destroy 析构函数
57
+func (c *Context) Destroy() {
58
+	c.DB.Close()
59
+}

+ 10
- 0
utils/guid.go View File

@@ -0,0 +1,10 @@
1
+package utils
2
+
3
+import (
4
+	"github.com/pborman/uuid"
5
+)
6
+
7
+// GetGUID 获取GUID
8
+func GetGUID() string {
9
+	return uuid.New()
10
+}

+ 88
- 0
utils/jwt.go View File

@@ -0,0 +1,88 @@
1
+package utils
2
+
3
+import (
4
+	"errors"
5
+	"time"
6
+
7
+	jwt "github.com/dgrijalva/jwt-go"
8
+)
9
+
10
+const (
11
+	TokenHeader = "Authorization"
12
+	TokenSchema = "Bearer"
13
+)
14
+
15
+var tokenSignedKey = []byte(`Yansen is so handsome!`)
16
+
17
+// CreateToken 获取token
18
+func CreateToken(info map[string]interface{}) (string, error) {
19
+	claims := jwt.MapClaims(info)
20
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
21
+	return token.SignedString(tokenSignedKey)
22
+}
23
+
24
+// PareseToken 解析 token
25
+func PareseToken(token string) (map[string]interface{}, error) {
26
+	tk, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
27
+		return tokenSignedKey, nil
28
+	})
29
+
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+
34
+	if claims, ok := tk.Claims.(jwt.MapClaims); ok && tk.Valid {
35
+		info := map[string]interface{}(claims)
36
+		return info, nil
37
+	}
38
+
39
+	return nil, errors.New("Token 解析 未知错误")
40
+}
41
+
42
+// JWTToken token 内容
43
+type JWTToken struct {
44
+	Guest    bool
45
+	ID       string
46
+	Password string
47
+	BatchNo  string
48
+	Expire   time.Time
49
+}
50
+
51
+// ToMap 转 map
52
+func (t *JWTToken) ToMap() map[string]interface{} {
53
+	return map[string]interface{}{
54
+		"guest":    t.Guest,
55
+		"user":     t.ID,
56
+		"password": t.Password,
57
+		"batchno":  t.BatchNo,
58
+		"exp":      t.Expire.Format("2006-01-02 15:04:05"),
59
+	}
60
+}
61
+
62
+// MapToJWTToken map 映射 Token
63
+func MapToJWTToken(data map[string]interface{}) *JWTToken {
64
+	token := JWTToken{}
65
+
66
+	if data["guest"] != nil {
67
+		token.Guest = data["guest"].(bool)
68
+	}
69
+
70
+	if data["user"] != nil {
71
+		token.ID = data["user"].(string)
72
+	}
73
+
74
+	if data["password"] != nil {
75
+		token.Password = data["password"].(string)
76
+	}
77
+
78
+	if data["batchno"] != nil {
79
+		token.BatchNo = data["batchno"].(string)
80
+	}
81
+
82
+	if data["exp"] != nil {
83
+		exp, _ := time.Parse("2006-01-02 15:04:05", data["exp"].(string))
84
+		token.Expire = exp
85
+	}
86
+
87
+	return &token
88
+}

+ 105
- 0
utils/log.go View File

@@ -0,0 +1,105 @@
1
+package utils
2
+
3
+import (
4
+	"errors"
5
+	"fmt"
6
+	"runtime"
7
+
8
+	"github.com/astaxie/beego/config"
9
+	"github.com/astaxie/beego/logs"
10
+)
11
+
12
+var instances = make(map[string]*logs.BeeLogger)
13
+
14
+// NewLog 构造日志对象
15
+func NewLog(tp ...string) *logs.BeeLogger {
16
+	logType := "common"
17
+	if len(tp) > 0 {
18
+		if tp[0] != "" {
19
+			logType = tp[0]
20
+		}
21
+	}
22
+
23
+	if v, ok := instances[logType]; ok {
24
+		return v
25
+	}
26
+
27
+	log := logs.NewLogger()
28
+	instances[logType] = log
29
+
30
+	// 读取配置文件
31
+	conf, err := config.NewConfig("ini", GetAppRoot()+"/conf/log.conf")
32
+	if err != nil {
33
+		log.SetLogger(logs.AdapterConsole)
34
+		return log
35
+	}
36
+
37
+	// 读文件存储位置
38
+	filename := conf.String(logType + "::filename")
39
+	// 输出级别
40
+	level := conf.String(logType + "::level")
41
+	if level == "" {
42
+		level = "error"
43
+	}
44
+
45
+	if filename == "" {
46
+		filename = "./" + logType + ".log"
47
+	}
48
+
49
+	log.Async()
50
+	log.SetLogger(logs.AdapterFile, `{"filename":"`+filename+`"}`)
51
+	return log
52
+}
53
+
54
+// LogError 错误日志
55
+func LogError(v ...interface{}) error {
56
+	log := instances["common"]
57
+
58
+	preMsg := ""
59
+	_, file, line, ok := runtime.Caller(1)
60
+	if ok {
61
+		preMsg = fmt.Sprintf("file: %s    line=%d : ", file, line)
62
+	}
63
+
64
+	log.Error(preMsg, v...)
65
+
66
+	if len(v) > 0 {
67
+		firstV := v[0]
68
+
69
+		switch err := firstV.(type) {
70
+		case string:
71
+			return errors.New(err)
72
+		case error:
73
+			return err
74
+		default:
75
+			msg := "Unknown error type"
76
+			return errors.New(msg)
77
+		}
78
+	}
79
+
80
+	return nil
81
+}
82
+
83
+// LogInfo Info 日志
84
+func LogInfo(v ...interface{}) {
85
+	log := instances["common"]
86
+
87
+	preMsg := ""
88
+	_, file, line, ok := runtime.Caller(1)
89
+	if ok {
90
+		preMsg = fmt.Sprintf("file: %s    line=%d : ", file, line)
91
+	}
92
+
93
+	log.Error(preMsg, v...)
94
+}
95
+
96
+// GetDefaultLogger 获取默认 logger
97
+func GetDefaultLogger() *logs.BeeLogger {
98
+	log := instances["common"]
99
+	log.SetLogFuncCallDepth(4)
100
+	return log
101
+}
102
+
103
+func LogInit() {
104
+	NewLog()
105
+}

+ 51
- 0
utils/pagenavi.go View File

@@ -0,0 +1,51 @@
1
+package utils
2
+
3
+import (
4
+	"strconv"
5
+)
6
+
7
+type PageNaviEngine struct {
8
+	ctx *Context
9
+}
10
+
11
+func NewPageNaviEngine(ctx *Context) *PageNaviEngine {
12
+	return &PageNaviEngine{ctx: ctx}
13
+}
14
+
15
+// GetPageList 获取列表
16
+func (t *PageNaviEngine) GetPageList(target interface{}, sql string, limit []int, sqlArgs ...interface{}) (int64, error) {
17
+	countSQL := "select count(*) as cnt from (" + sql + ") as virtualTable"
18
+	countSQLorArgs := []interface{}{countSQL}
19
+	for _, arg := range sqlArgs {
20
+		countSQLorArgs = append(countSQLorArgs, arg)
21
+	}
22
+
23
+	cntRes, err := t.ctx.DB.Query(countSQLorArgs...)
24
+	if err != nil {
25
+		return 0, err
26
+	}
27
+
28
+	cntStr := string(cntRes[0]["cnt"])
29
+	cnt, _ := strconv.ParseInt(cntStr, 10, 64)
30
+
31
+	newSQL := t.PackLimitToSQL(sql, limit)
32
+
33
+	err = t.ctx.DB.SQL(newSQL, sqlArgs...).Find(target)
34
+	if err != nil {
35
+		return 0, err
36
+	}
37
+
38
+	return cnt, nil
39
+}
40
+
41
+// PackLimitToSQL 添加 limit 语句到 sql 最后
42
+func (t *PageNaviEngine) PackLimitToSQL(sql string, limit []int) string {
43
+	switch len(limit) {
44
+	case 1:
45
+		return sql + " LIMIT " + strconv.Itoa(limit[0])
46
+	case 2:
47
+		return sql + " LIMIT " + strconv.Itoa(limit[0]) + " OFFSET " + strconv.Itoa(limit[1])
48
+	default:
49
+		return sql
50
+	}
51
+}

+ 194
- 0
utils/utils.go View File

@@ -0,0 +1,194 @@
1
+package utils
2
+
3
+import (
4
+	"encoding/base64"
5
+	"math/rand"
6
+	"net/http"
7
+	"os"
8
+	"path/filepath"
9
+	"runtime"
10
+	"strconv"
11
+	"strings"
12
+	"time"
13
+)
14
+
15
+// 客户端类型
16
+const (
17
+	ClientAdmin  = "client-admin"
18
+	ClientWechat = "client-wechat"
19
+	ClientMini   = "client-mini"
20
+)
21
+
22
+// 一些 UA 列表
23
+const (
24
+	UAWechat      = "micromessenger"
25
+	UAMiniProgram = "miniprogram"
26
+)
27
+
28
+// GetClientType 获取客户端类型
29
+// 默认返回是 管理端
30
+func GetClientType(r *http.Request) string {
31
+	ua := strings.ToLower(r.Header.Get("User-Agent"))
32
+
33
+	if strings.Index(ua, UAWechat) > -1 {
34
+		// if strings.Index(ua, UAMiniProgram) > -1 {
35
+		// 	return ClientMini
36
+		// }
37
+
38
+		return ClientWechat
39
+	}
40
+
41
+	return ClientAdmin
42
+}
43
+
44
+// StrSliceIndexOf slice indexof
45
+func StrSliceIndexOf(s []string, t string) int64 {
46
+	if s == nil || len(s) == 0 {
47
+		return -1
48
+	}
49
+
50
+	for k, v := range s {
51
+		if v == t {
52
+			return int64(k)
53
+		}
54
+	}
55
+
56
+	return -1
57
+}
58
+
59
+// GUIID2IntString guid 转 int 字符串
60
+func GUIID2IntString(id string) []string {
61
+	if id == "" {
62
+		return nil
63
+	}
64
+
65
+	res := make([]string, 0)
66
+	for _, part := range strings.Split(id, "-") {
67
+		sum := 0
68
+		for _, r := range []rune(part) {
69
+			sum += int(r)
70
+		}
71
+
72
+		res = append(res, strconv.Itoa(sum))
73
+	}
74
+
75
+	return res
76
+}
77
+
78
+// EncodeBase64TrimTail base64 加密, 去除末尾 =
79
+func EncodeBase64TrimTail(src string) string {
80
+	if src == "" {
81
+		return ""
82
+	}
83
+
84
+	enStr := base64.StdEncoding.EncodeToString([]byte(src))
85
+
86
+	return strings.TrimRight(enStr, "=")
87
+}
88
+
89
+// DecodeBase64NoTail base64解密, 位数不够的, 自动补全
90
+func DecodeBase64NoTail(src string) string {
91
+	if src == "" {
92
+		return ""
93
+	}
94
+
95
+	pd := 4 - len(src)%4
96
+	if pd > 0 && pd != 4 {
97
+		src += strings.Repeat("=", pd)
98
+	}
99
+
100
+	res, err := base64.StdEncoding.DecodeString(src)
101
+	if err != nil {
102
+		LogError("解密 Base64 字串失败: " + err.Error())
103
+		return ""
104
+	}
105
+
106
+	return string(res)
107
+}
108
+
109
+// GetPageNaviLimit 获取 mysql limit 限定
110
+func GetPageNaviLimit(params ...int) []int {
111
+	page := 1
112
+	pagesize := 10
113
+
114
+	if params != nil {
115
+		pl := len(params)
116
+
117
+		if pl > 0 {
118
+			page = params[0]
119
+			if page < 1 {
120
+				page = 1
121
+			}
122
+		}
123
+
124
+		if pl > 1 {
125
+			pagesize = params[1]
126
+		}
127
+	}
128
+
129
+	return []int{
130
+		pagesize,
131
+		(page - 1) * pagesize,
132
+	}
133
+}
134
+
135
+// GenerateQRCode 生成二维码数字
136
+func GenerateQRCode() string {
137
+	var temp1 int = rand.Intn(9)*87 + 11
138
+	var temp2 int = rand.Intn(9)*89 + 13
139
+	nano := time.Now().UnixNano()
140
+	var nanostr string = strconv.FormatInt(nano, 10)
141
+	var temp3 string = nanostr[11:]
142
+	var temp4 string = "6"
143
+	var temp5 int = rand.Intn(9)*7 + 17
144
+	var code string = "66" + strconv.Itoa(temp1) + temp4 + strconv.Itoa(temp2) + temp3 + strconv.Itoa(temp5)
145
+	return code
146
+}
147
+
148
+// GetFiveSeconds 获取 5 秒钟的时间
149
+func GetFiveSeconds(t time.Time) string {
150
+	str := t.Format("20060102150405")
151
+	strLen := len(str)
152
+
153
+	lastNum, _ := strconv.Atoi(str[strLen-1:])
154
+	if lastNum < 5 {
155
+		lastNum = 0
156
+	} else {
157
+		lastNum = 5
158
+	}
159
+
160
+	return str[:strLen-1] + strconv.Itoa(lastNum)
161
+}
162
+
163
+// 当前运行环境
164
+var processEnv string
165
+var appRoot string
166
+
167
+// GetAppRoot 获取系统根目录
168
+func GetAppRoot() string {
169
+	if appRoot != "" {
170
+		return appRoot
171
+	}
172
+
173
+	if processEnv == "" {
174
+		for _, arg := range os.Args {
175
+			if strings.Index(arg, "-test.run") > -1 {
176
+				processEnv = "test"
177
+				break
178
+			}
179
+		}
180
+
181
+		if processEnv == "" {
182
+			processEnv = "production"
183
+		}
184
+	}
185
+
186
+	if processEnv == "test" {
187
+		_, file, _, _ := runtime.Caller(1)
188
+		appRoot, _ = filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))
189
+	} else {
190
+		appRoot, _ = filepath.Abs(filepath.Dir(os.Args[0]))
191
+	}
192
+
193
+	return appRoot
194
+}

+ 118
- 0
utils/utils_test.go View File

@@ -0,0 +1,118 @@
1
+package utils_test
2
+
3
+import (
4
+	"wechat-conf/utils"
5
+	"strings"
6
+	"testing"
7
+	"time"
8
+)
9
+
10
+func TestStrSliceIndexOf(t *testing.T) {
11
+	cases := []map[string]interface{}{
12
+		map[string]interface{}{
13
+			"case":     []string{"a", "b", "c"},
14
+			"expected": int64(1),
15
+		},
16
+		map[string]interface{}{
17
+			"case":     []string{"aa", "bb", "cc"},
18
+			"expected": int64(-1),
19
+		},
20
+	}
21
+
22
+	for _, cs := range cases {
23
+		td := cs["case"].([]string)
24
+		ex := cs["expected"].(int64)
25
+
26
+		res := utils.StrSliceIndexOf(td, "b")
27
+
28
+		if ex != res {
29
+			t.Fatalf("TestStrSliceIndexOf case fail : %v", res)
30
+		}
31
+	}
32
+}
33
+
34
+func TestGUIID2IntString(t *testing.T) {
35
+	cases := []map[string]string{
36
+		map[string]string{
37
+			"case":     "3b2da085-8881-4029-9a3d-6d33caf870d0",
38
+			"expected": "553-217-207-305-861",
39
+		},
40
+	}
41
+
42
+	for _, cs := range cases {
43
+		res := utils.GUIID2IntString(cs["case"])
44
+		if strings.Join(res, "-") != cs["expected"] {
45
+			t.Fatalf("TestGUIID2IntString fail")
46
+		}
47
+	}
48
+
49
+}
50
+
51
+func TestEncodeBase64TrimTail(t *testing.T) {
52
+	cases := []map[string]string{
53
+		map[string]string{
54
+			"case":     "1",
55
+			"expected": "MQ",
56
+		},
57
+		map[string]string{
58
+			"case":     "111",
59
+			"expected": "MTEx",
60
+		},
61
+	}
62
+
63
+	for _, cs := range cases {
64
+		res := utils.EncodeBase64TrimTail(cs["case"])
65
+		if res != cs["expected"] {
66
+			t.Fatalf("TestEncodeBase64TrimTail fail")
67
+		}
68
+	}
69
+}
70
+
71
+func TestDecodeBase64NoTail(t *testing.T) {
72
+	cases := []map[string]string{
73
+		map[string]string{
74
+			"case":     "MQ",
75
+			"expected": "1",
76
+		},
77
+		map[string]string{
78
+			"case":     "MTEx",
79
+			"expected": "111",
80
+		},
81
+	}
82
+
83
+	for _, cs := range cases {
84
+		res := utils.DecodeBase64NoTail(cs["case"])
85
+		if res != cs["expected"] {
86
+			t.Fatalf("TestDecodeBase64NoTail fail : %s", cs["case"])
87
+		}
88
+	}
89
+}
90
+
91
+func TestGetFiveSeconds(t *testing.T) {
92
+	cases := []map[string]string{
93
+		map[string]string{
94
+			"case":     "2018-10-07 13:21:40",
95
+			"expected": "20181007132140",
96
+		},
97
+		map[string]string{
98
+			"case":     "2018-10-07 13:21:42",
99
+			"expected": "20181007132140",
100
+		},
101
+		map[string]string{
102
+			"case":     "2018-10-07 13:21:45",
103
+			"expected": "20181007132145",
104
+		},
105
+		map[string]string{
106
+			"case":     "2018-10-07 13:21:48",
107
+			"expected": "20181007132145",
108
+		},
109
+	}
110
+
111
+	for _, cs := range cases {
112
+		dt, _ := time.Parse("2006-01-02 15:04:05", cs["case"])
113
+		res := utils.GetFiveSeconds(dt)
114
+		if res != cs["expected"] {
115
+			t.Fatalf("TestGetFiveSeconds fail : %s", cs["case"])
116
+		}
117
+	}
118
+}

+ 116
- 0
utils/wechat.go View File

@@ -0,0 +1,116 @@
1
+package utils
2
+
3
+import (
4
+	"github.com/zjxpcyc/wechat/mini"
5
+	"github.com/zjxpcyc/wechat/wx"
6
+)
7
+
8
+var wxClients map[string]*wx.Client
9
+var miniClients map[string]*mini.Client
10
+
11
+type WechatUser struct {
12
+	OpenID     string  `json:"openid"`
13
+	NickName   string  `json:"nickname"`
14
+	Sex        float64 `json:"sex"`
15
+	Province   string  `json:"province"`
16
+	City       string  `json:"city"`
17
+	Country    string  `json:"country"`
18
+	HeadImgURL string  `json:"headimgurl"`
19
+	UnionID    string  `json:"unionid"`
20
+}
21
+
22
+// WxClientSingleton 初始化
23
+func WxClientSingleton(org string, cert map[string]string) {
24
+	if wxClients == nil {
25
+		wxClients = map[string]*wx.Client{
26
+			org: wx.NewClient(cert),
27
+		}
28
+	} else {
29
+		if _, ok := wxClients[org]; !ok {
30
+			wxClients[org] = wx.NewClient(cert)
31
+		}
32
+	}
33
+}
34
+
35
+// MiniClientSingleton 初始化
36
+func MiniClientSingleton(org string, cert map[string]string) {
37
+	if miniClients == nil {
38
+		miniClients = map[string]*mini.Client{
39
+			org: mini.NewClient(cert),
40
+		}
41
+	} else {
42
+		if _, ok := miniClients[org]; !ok {
43
+			miniClients[org] = mini.NewClient(cert)
44
+		}
45
+	}
46
+}
47
+
48
+// WxClientFor 微信客户端
49
+func WxClientFor(org string) *wx.Client {
50
+	return wxClients[org]
51
+}
52
+
53
+// MiniClientFor 小程序客户端
54
+func MiniClientFor(org string) *mini.Client {
55
+	return miniClients[org]
56
+}
57
+
58
+// GetWxAppID 获取微信ID
59
+func GetWxAppID(org string) string {
60
+	return wxClients[org].GetAppID()
61
+}
62
+
63
+func WechatInit() {
64
+	logger := GetDefaultLogger()
65
+	wx.SetLogInst(logger)
66
+	mini.SetLogInst(logger)
67
+}
68
+
69
+// MapToWechatUser 映射微信人员
70
+func MapToWechatUser(data map[string]interface{}) *WechatUser {
71
+	subscribe, has := data["subscribe"]
72
+	if has {
73
+		if subscribe == nil {
74
+			return nil
75
+		}
76
+
77
+		sub := subscribe.(float64)
78
+		if sub == 0 {
79
+			return nil
80
+		}
81
+	}
82
+
83
+	user := WechatUser{
84
+		OpenID: data["openid"].(string),
85
+	}
86
+
87
+	if data["sex"] != nil {
88
+		user.Sex = data["sex"].(float64)
89
+	}
90
+
91
+	if data["nickname"] != nil {
92
+		user.NickName = data["nickname"].(string)
93
+	}
94
+
95
+	if data["province"] != nil {
96
+		user.Province = data["province"].(string)
97
+	}
98
+
99
+	if data["city"] != nil {
100
+		user.City = data["city"].(string)
101
+	}
102
+
103
+	if data["country"] != nil {
104
+		user.Country = data["country"].(string)
105
+	}
106
+
107
+	if data["headimgurl"] != nil {
108
+		user.HeadImgURL = data["headimgurl"].(string)
109
+	}
110
+
111
+	if data["unionid"] != nil {
112
+		user.UnionID = data["unionid"].(string)
113
+	}
114
+
115
+	return &user
116
+}