Browse Source

merge yansen

zjxpcyc 6 years ago
parent
commit
5214ddee72
14 changed files with 3740 additions and 242 deletions
  1. 1
    1
      conf/app.conf
  2. 36
    177
      controllers/auth.go
  3. 9
    1
      controllers/base.go
  4. 0
    1
      controllers/context.go
  5. 23
    0
      controllers/file.go
  6. 4
    3
      controllers/user/user.go
  7. 3077
    0
      log/common.log
  8. 10
    0
      models/model/sys_token_log.go
  9. 40
    0
      models/sys.go
  10. 11
    24
      service/customer/customer.go
  11. 357
    35
      service/sys.go
  12. 68
    0
      utils/excel.go
  13. 48
    0
      utils/jwt.go
  14. 56
    0
      utils/wechat.go

+ 1
- 1
conf/app.conf View File

@@ -4,7 +4,7 @@ runmode = dev
4 4
 autorender = false
5 5
 copyrequestbody = true
6 6
 EnableDocs = true
7
-sessionon = true
7
+sessionon = false
8 8
 excelpath = ./
9 9
 clienturl = http://dev.ycjcjy.com/c-v2
10 10
 

+ 36
- 177
controllers/auth.go View File

@@ -1,205 +1,64 @@
1 1
 package controllers
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"net/http"
6
-	"spaceofcheng/services/models/model"
7 5
 	"spaceofcheng/services/service"
8
-	"spaceofcheng/services/utils"
9
-	"strings"
10
-
11
-	"github.com/astaxie/beego"
12 6
 )
13 7
 
14 8
 // Authenticate 权限验证
9
+// 其中 token 的处理方式是
10
+// 1、获取 request 中 token
11
+// 2、放入 Context 中
12
+// 3、校验 token
13
+// 4、结束后,设置过期并从 Context 中删除
14
+// 5、生成新的 token, 并放入 Context 中
15 15
 func (c *BaseController) authenticate() {
16
-
17
-	// 默认 service
18 16
 	serv := service.NewSysServ(c.Context)
19 17
 
20
-	orgID := c.GetString(":org")
21
-	if orgID == "" {
22
-		c.ResponseError(errors.New("接口地址访问不正确"))
23
-	}
24
-	serv.SetOrgByID(orgID)
25
-
26
-	// 客户端类型
27
-	clientType := utils.GetClientType(c.Ctx.Request)
28
-
29
-	switch clientType {
30
-	case utils.ClientAdmin:
31
-		c.authPCAdmin(serv)
32
-	case utils.ClientWechat:
33
-		c.authWechat(serv)
34
-	default:
35
-		c.ResponseError(
36
-			errors.New("暂不支持的 API 场景"),
37
-			http.StatusBadRequest,
38
-		)
39
-	}
40
-}
41
-
42
-// authPCAdmin PC管理端
43
-func (c *BaseController) authPCAdmin(serv *service.SysServ) {
44
-	if !c.needAuth() {
45
-		return
46
-	}
47
-
48
-	// 用户ID
49
-	userID := ""
50
-	userIDRaw := c.GetSession(SNUserID)
51
-	if userIDRaw != nil {
52
-		userID = userIDRaw.(string)
53
-	}
54
-
55
-	if userID == "" {
56
-		c.ResponseError(
57
-			errors.New("用户未登录"),
58
-			http.StatusUnauthorized,
59
-		)
60
-	}
61
-
62
-	if err := serv.SetUserProfile(userID); err != nil {
63
-		// utils.LogError(err.Error())
64
-
65
-		c.ResponseError(
66
-			utils.LogError(err.Error()),
67
-			http.StatusInternalServerError,
68
-		)
69
-	}
70
-
71
-	// 设置 Session
72
-	c.SetSession(SNUserID, userID)
73
-}
74
-
75
-// authWechat 微信端
76
-func (c *BaseController) authWechat(serv *service.SysServ) {
77
-	custID := ""
78
-	custIDRaw := c.GetSession(SNCustID)
79
-	if custIDRaw != nil {
80
-		custID = custIDRaw.(string)
81
-	}
18
+	// 鉴权 - 并初始化上下文
19
+	res := serv.AuthAndInitCtx(c.Ctx)
82 20
 
83
-	// 机构
84
-	org := c.Context.Get("org").(model.SysOrg)
85
-
86
-	// 微信配置
87
-	wxConf, err := serv.GetWeChatConfig(org.OrgId)
88
-	if err != nil {
89
-		utils.LogError("查询微信配置失败: " + err.Error())
90
-		c.ResponseError(
91
-			errors.New("没有找到微信相关配置"),
92
-			http.StatusBadRequest,
93
-		)
94
-	}
95
-	utils.WxClientSingleton(org.OrgId, wxConf)
96
-
97
-	// 用户微信信息
98
-	var wxDetail map[string]interface{}
99
-
100
-	detailRaw := c.GetSession("wechat_user")
101
-	if detailRaw != nil {
102
-		wxDetail = detailRaw.(map[string]interface{})
103
-	} else {
104
-		// DEV MODE
105
-		if c.RunMode == "dev" {
106
-			wxDetail = map[string]interface{}{
107
-				"openid":     "OPENID",
108
-				"nickname":   "NICKNAME",
109
-				"sex":        "1",
110
-				"province":   "PROVINCE",
111
-				"city":       "CITY",
112
-				"country":    "COUNTRY",
113
-				"headimgurl": "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
114
-				"unionid":    "o6_bmasdasdsad6_2sgVt7hMZOPfL",
115
-			}
116
-		} else {
117
-			wxDetail = c.getWechatDetail(org.OrgId, serv)
21
+	if res != nil {
22
+		code := http.StatusOK
23
+		if res["code"] != nil {
24
+			code = res["code"].(int)
118 25
 		}
119 26
 
120
-		c.SetSession("wechat_user", wxDetail)
121
-	}
27
+		if code != http.StatusOK {
28
+			err := res["error"].(error)
29
+			data := map[string]interface{}{}
122 30
 
123
-	c.Context.Set("wxInfo", wxDetail)
31
+			if res["message"] != nil {
32
+				data = res["message"].(map[string]interface{})
33
+			}
124 34
 
125
-	// 用户映射检查
126
-	userMap, err := serv.CheckWechatUserMapping(wxDetail)
127
-	if err != nil {
128
-		c.ResponseError(
129
-			utils.LogError("获取人员映射账户失败: "+err.Error()),
130
-			http.StatusInternalServerError,
131
-		)
132
-	}
133
-	c.Context.Set("userMap", *userMap)
35
+			// 设置旧 token 过期
36
+			c.SetTokenExipre()
134 37
 
135
-	if !c.needAuth() {
136
-		return
137
-	}
138
-	// 未登录或者绑定, 返回 406
139
-	if custID == "" && userMap.UserId == "" {
140
-		// 新增用户信息
141
-		cust, err := serv.SaveNewCustomer(wxDetail, userMap)
142
-		if err != nil {
143
-			c.ResponseError(err)
38
+			c.ResponseData(data, err, code)
144 39
 		}
145
-		custID = cust.CustomerId
146
-		// c.ResponseError(
147
-		// 	errors.New("用户未登录或绑定"),
148
-		// 	http.StatusNotAcceptable,
149
-		// )
150 40
 	}
151 41
 
152
-	if custID == "" {
153
-		custID = userMap.UserId
154
-	}
155
-	if custID != "" {
156
-		if err := serv.SetCustomer(custID); err != nil {
157
-			utils.LogError(err.Error())
42
+	// 设置旧 token 过期
43
+	c.SetTokenExipre()
158 44
 
159
-			c.ResponseError(
160
-				errors.New("内部错误, 请重试"),
161
-				http.StatusInternalServerError,
162
-			)
163
-		}
164
-
165
-		// 设置 Session
166
-		c.SetSession(SNCustID, custID)
167
-	}
45
+	// 生成新 token
46
+	c.CreateNewToken()
168 47
 }
169 48
 
170
-func (c *BaseController) needAuth() bool {
171
-	route := c.Ctx.Input.URL()
172
-	apiPrefix := beego.AppConfig.String("api::prefix")
173
-	guestAPI := beego.AppConfig.String("api::guest")
174
-
175
-	if strings.Index(route, apiPrefix+strings.Split(guestAPI, ":")[0]) > -1 {
176
-		return false
49
+// SetTokenExipre 设置 token 过期
50
+func (c *BaseController) SetTokenExipre() {
51
+	token := c.Context.Get("token")
52
+	if token != nil {
53
+		serv := service.NewSysServ(c.Context)
54
+		serv.UpdateTokenExpire(token.(string))
177 55
 	}
178 56
 
179
-	return true
57
+	c.Context.Set("token", "")
180 58
 }
181 59
 
182
-// getWechatDetail 获取微信个人信息详情
183
-func (c *BaseController) getWechatDetail(org string, serv *service.SysServ) map[string]interface{} {
184
-	// 微信 code
185
-	code := c.GetString("code")
186
-	if code == "" {
187
-		c.ResponseData(
188
-			map[string]interface{}{
189
-				"appid": utils.GetWxAppID(org),
190
-			},
191
-			errors.New("获取微信信息失败"),
192
-			http.StatusUnauthorized,
193
-		)
194
-	}
195
-
196
-	usr, err := utils.WxClientFor(org).GetUserInfo(code)
197
-	if err != nil {
198
-		c.ResponseError(
199
-			utils.LogError("获取微信个人信息失败: "+err.Error()),
200
-			http.StatusInternalServerError,
201
-		)
202
-	}
203
-
204
-	return usr
60
+// CreateNewToken 新 token
61
+func (c *BaseController) CreateNewToken() {
62
+	serv := service.NewSysServ(c.Context)
63
+	c.Context.Set("token", serv.NewToken())
205 64
 }

+ 9
- 1
controllers/base.go View File

@@ -62,7 +62,15 @@ func (c *BaseController) ResponseData(data interface{}, msg interface{}, code in
62 62
 	}
63 63
 
64 64
 	c.Data["json"] = JSONMessage{status, sendMessage, data}
65
-	c.Ctx.Output.Header("Access-Control-Expose-Headers", "X-Token")
65
+	c.Ctx.Output.Header("Access-Control-Expose-Headers", utils.TokenHeader)
66
+
67
+	token := c.Context.Get("token")
68
+	if token != nil {
69
+		tk := token.(string)
70
+		if tk != "" {
71
+			c.Ctx.Output.Header(utils.TokenHeader, utils.TokenSchema+" "+tk)
72
+		}
73
+	}
66 74
 
67 75
 	c.crosPolicy()
68 76
 	c.ServeJSON()

+ 0
- 1
controllers/context.go View File

@@ -12,7 +12,6 @@ import (
12 12
 * user					SysUser 									用户基本信息
13 13
 * customer			TaCustomer 								会员基本信息
14 14
 * userMap				TaUserMapping 						用户/会员 映射第三方账户
15
-* wxInfo				map[string]interface{} 		微信信息
16 15
 * cases					[]SysUserCase 						用户所有案场信息
17 16
 * currentCase		SysUserCase 							当前案场
18 17
 * org						SysOrg 										用户当前组织

+ 23
- 0
controllers/file.go View File

@@ -1,12 +1,14 @@
1 1
 package controllers
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"errors"
5 6
 	"spaceofcheng/services/utils"
6 7
 	"strconv"
7 8
 	"time"
8 9
 
9 10
 	"net/http"
11
+	"net/url"
10 12
 )
11 13
 
12 14
 // FileUpload 文件上传
@@ -46,3 +48,24 @@ func (c *BaseController) uploadFileToOSS(field string) (string, error) {
46 48
 
47 49
 	return fileURL, nil
48 50
 }
51
+
52
+// SaveToExcel 保存文件到 excel
53
+func (c *BaseController) SaveToExcel(fn string, data interface{}, f func(interface{}) []interface{}) {
54
+	var buf bytes.Buffer
55
+	if err := utils.NewTinyXLSXEngine().SetTransFunc(f).SetData(data).Write(&buf); err != nil {
56
+		utils.LogError("写 xlsx buffer 失败: " + err.Error())
57
+		c.ResponseError(errors.New("生成 excel 异常, 请重试"))
58
+	}
59
+
60
+	c.Ctx.Output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fn))
61
+	c.Ctx.Output.Header("Content-Description", "File Transfer")
62
+	c.Ctx.Output.ContentType(".xlsx")
63
+	c.Ctx.Output.Header("Content-Transfer-Encoding", "binary")
64
+	c.Ctx.Output.Header("Expires", "0")
65
+	c.Ctx.Output.Header("Cache-Control", "must-revalidate")
66
+	c.Ctx.Output.Header("Pragma", "public")
67
+
68
+	r := bytes.NewReader(buf.Bytes())
69
+	http.ServeContent(c.Ctx.ResponseWriter, c.Ctx.Request, fn, time.Now().Local(), r)
70
+	c.StopRun()
71
+}

+ 4
- 3
controllers/user/user.go View File

@@ -182,8 +182,9 @@ func (c *UserController) SignIn() {
182 182
 		)
183 183
 	}
184 184
 
185
-	// 成功之后, 设置 session
186
-	c.SetSession(controllers.SNUserID, user.UserId)
185
+	// 成功之后, 生成新 token
186
+	c.Context.Set("user", *user)
187
+	c.CreateNewToken()
187 188
 
188 189
 	if token == "" && doRemember != 0 {
189 190
 		var err error
@@ -205,7 +206,7 @@ func (c *UserController) SignIn() {
205 206
 
206 207
 // SignOut 用户登出
207 208
 func (c *UserController) SignOut() {
208
-	c.DestroySession()
209
+	c.SetTokenExipre()
209 210
 	c.ResponseJSON("ok")
210 211
 }
211 212
 

+ 3077
- 0
log/common.log
File diff suppressed because it is too large
View File


+ 10
- 0
models/model/sys_token_log.go View File

@@ -0,0 +1,10 @@
1
+package model
2
+
3
+import "time"
4
+
5
+type SysTokenLog struct {
6
+	TokenId    int       `xorm:"not null pk autoincr INT(11)"`
7
+	Token      string    `xorm:"TEXT"`
8
+	Status     int       `xorm:"SMALLINT(6)"`
9
+	CreateDate time.Time `xorm:"DATETIME"`
10
+}

+ 40
- 0
models/sys.go View File

@@ -4,6 +4,7 @@ import (
4 4
 	"errors"
5 5
 	"spaceofcheng/services/models/model"
6 6
 	"spaceofcheng/services/utils"
7
+	"time"
7 8
 
8 9
 	"github.com/yl10/kit/guid"
9 10
 
@@ -165,3 +166,42 @@ func (m *SysDAO) GetWeChatConfig(org string) (*model.SysWechatConf, error) {
165 166
 
166 167
 	return conf, nil
167 168
 }
169
+
170
+// InsertToken 插入 token 生成记录
171
+func (m *SysDAO) InsertToken(token string) error {
172
+	tk := model.SysTokenLog{
173
+		Token:      token,
174
+		Status:     STATUS_NORMAL,
175
+		CreateDate: time.Now().Local(),
176
+	}
177
+
178
+	if _, err := m.db.Insert(&tk); err != nil {
179
+		return err
180
+	}
181
+
182
+	return nil
183
+}
184
+
185
+// UpdateTokenExpire 设置 token 过期
186
+func (m *SysDAO) UpdateTokenExpire(token string) error {
187
+	tk := model.SysTokenLog{
188
+		Status: STATUS_DEL,
189
+	}
190
+
191
+	if _, err := m.db.Cols("status").Where("token=?", token).Update(&tk); err != nil {
192
+		return err
193
+	}
194
+
195
+	return nil
196
+}
197
+
198
+// GetToken 获取 token
199
+func (m *SysDAO) GetToken(token string) (*model.SysTokenLog, error) {
200
+	tk := model.SysTokenLog{}
201
+
202
+	if _, err := m.db.Where("token=?", token).Get(&tk); err != nil {
203
+		return nil, err
204
+	}
205
+
206
+	return &tk, nil
207
+}

+ 11
- 24
service/customer/customer.go View File

@@ -1,6 +1,7 @@
1 1
 package customer
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"errors"
5 6
 	"spaceofcheng/services/models"
6 7
 	"spaceofcheng/services/models/cases"
@@ -104,15 +105,14 @@ func (s *CustomerServ) BindWechatUser(userMap *model.TaUserMapping, phone string
104 105
 	}
105 106
 
106 107
 	if cust == nil {
107
-		wxInfoRaw := s.ctx.Get("wxInfo")
108
-		if wxInfoRaw == nil {
109
-			return nil, errors.New("请确定使用微信端登录")
108
+		wxInfo := utils.WechatUser{}
109
+		if err := json.Unmarshal([]byte(userMap.AccountInfo), &wxInfo); err != nil {
110
+			utils.LogError("解析用户微信映射信息失败: " + err.Error())
111
+			return nil, errors.New("系统数据异常")
110 112
 		}
111 113
 
112
-		wxInfo := wxInfoRaw.(map[string]interface{})
113 114
 		// 更新用户手机号码信息及userid信息
114
-		openid := wxInfo["openid"].(string)
115
-		customer, err := s.dao.GetCustWithWXByOpenID(openid)
115
+		customer, err := s.dao.GetCustWithWXByOpenID(wxInfo.OpenID)
116 116
 
117 117
 		newCust := model.TaCustomer{
118 118
 			CustomerId: customer.CustomerId,
@@ -128,11 +128,6 @@ func (s *CustomerServ) BindWechatUser(userMap *model.TaUserMapping, phone string
128 128
 			return nil, err
129 129
 		}
130 130
 
131
-		// newCust, err := s.SaveNewCustomer(wxInfo, "", "", user.Phone, user.UserId)
132
-		// if err != nil {
133
-		// 	utils.LogError(err.Error())
134
-		// 	return nil, err
135
-		// }
136 131
 		cust = &newCust
137 132
 	} else {
138 133
 		if cust.Status != models.STATUS_NORMAL {
@@ -168,16 +163,14 @@ func (s *CustomerServ) BindWechatCust(userMap *model.TaUserMapping, phone, caseI
168 163
 
169 164
 	// 用户不存在, 则新增
170 165
 	if cust == nil {
171
-		wxInfoRaw := s.ctx.Get("wxInfo")
172
-		if wxInfoRaw == nil {
173
-			return nil, errors.New("请确定使用微信端登录")
166
+		wxInfo := utils.WechatUser{}
167
+		if err := json.Unmarshal([]byte(userMap.AccountInfo), &wxInfo); err != nil {
168
+			utils.LogError("解析用户微信映射信息失败: " + err.Error())
169
+			return nil, errors.New("系统数据异常")
174 170
 		}
175 171
 
176
-		wxInfo := wxInfoRaw.(map[string]interface{})
177
-
178 172
 		// 更新用户手机号码信息及userid信息
179
-		openid := wxInfo["openid"].(string)
180
-		customer, err := s.dao.GetCustWithWXByOpenID(openid)
173
+		customer, err := s.dao.GetCustWithWXByOpenID(wxInfo.OpenID)
181 174
 		newCust := model.TaCustomer{
182 175
 			CustomerId:  customer.CustomerId,
183 176
 			Phone:       phone,
@@ -228,12 +221,6 @@ func (s *CustomerServ) BindWechatCust(userMap *model.TaUserMapping, phone, caseI
228 221
 			return nil, err
229 222
 		}
230 223
 
231
-		// newCust, err := s.SaveNewCustomer(wxInfo, caseID, userID, phone, "")
232
-		// if err != nil {
233
-		// 	utils.LogError(err.Error())
234
-		// 	return nil, err
235
-		// }
236
-
237 224
 		cust = &newCust
238 225
 	} else {
239 226
 		if cust.Status != models.STATUS_NORMAL {

+ 357
- 35
service/sys.go View File

@@ -3,12 +3,16 @@ package service
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"errors"
6
+	"net/http"
6 7
 	"spaceofcheng/services/models"
7 8
 	"spaceofcheng/services/models/customer"
8 9
 	"spaceofcheng/services/models/model"
9 10
 	"spaceofcheng/services/utils"
11
+	"strings"
12
+	"time"
10 13
 
11 14
 	"github.com/astaxie/beego"
15
+	"github.com/astaxie/beego/context"
12 16
 )
13 17
 
14 18
 const (
@@ -17,6 +21,7 @@ const (
17 21
 
18 22
 // SysServ 系统处理
19 23
 type SysServ struct {
24
+	org         model.SysOrg
20 25
 	ctx         *utils.Context
21 26
 	dao         *models.SysDAO
22 27
 	customerdao *customer.CustomerDAO
@@ -31,6 +36,338 @@ func NewSysServ(ctx *utils.Context) *SysServ {
31 36
 	}
32 37
 }
33 38
 
39
+// AuthAndInitCtx 鉴权
40
+// gctx 是 beego 框架中的 Context
41
+func (s *SysServ) AuthAndInitCtx(gctx *context.Context) map[string]interface{} {
42
+	// 确认机构
43
+	orgID := gctx.Input.Query(":org")
44
+	if orgID == "" {
45
+		return map[string]interface{}{
46
+			"code":  http.StatusBadRequest,
47
+			"error": errors.New("接口地址访问不正确"),
48
+		}
49
+	}
50
+	if err := s.SetOrgByID(orgID); err != nil {
51
+		return map[string]interface{}{
52
+			"code":  http.StatusInternalServerError,
53
+			"error": err,
54
+		}
55
+	}
56
+
57
+	// 获取 token
58
+	token, err := s.getToken(gctx)
59
+	if err != nil {
60
+		// token 报错一律视为需要重新登录
61
+		return map[string]interface{}{
62
+			"code":  http.StatusUnauthorized,
63
+			"error": err,
64
+		}
65
+	}
66
+
67
+	// 客户端类型
68
+	// 通过 UA 判断
69
+	clientType := utils.GetClientType(gctx.Request)
70
+
71
+	// pc 管理端
72
+	if clientType == utils.ClientAdmin {
73
+		return s.authPCAdmin(gctx, token)
74
+	}
75
+
76
+	// wechat 端
77
+	if clientType == utils.ClientWechat {
78
+		return s.authWechat(gctx, token)
79
+	}
80
+
81
+	return map[string]interface{}{
82
+		"code":  http.StatusBadRequest,
83
+		"error": errors.New("暂无该客户端的 API"),
84
+	}
85
+}
86
+
87
+// NewToken 设置 TOKEN
88
+// 15 分钟后过期
89
+func (s *SysServ) NewToken() string {
90
+	var token *utils.JWTToken
91
+	exp := time.Now().Local().Add(15 * time.Second)
92
+
93
+	if s.ctx.Get("userMap") != nil {
94
+		userMap := s.ctx.Get("userMap").(model.TaUserMapping)
95
+
96
+		token = &utils.JWTToken{
97
+			Guest:  false,
98
+			ID:     userMap.Openid,
99
+			Expire: exp,
100
+		}
101
+	} else if s.ctx.Get("user") != nil {
102
+		user := s.ctx.Get("user").(model.SysUser)
103
+
104
+		token = &utils.JWTToken{
105
+			Guest:  false,
106
+			ID:     user.UserId,
107
+			Expire: exp,
108
+		}
109
+	} else {
110
+		token = &utils.JWTToken{
111
+			Guest:  true,
112
+			Expire: exp,
113
+		}
114
+	}
115
+
116
+	tokenEncodeStr, err := utils.CreateToken(token.ToMap())
117
+	if err != nil {
118
+		utils.LogError("系统生成 Token 失败: " + err.Error())
119
+		return ""
120
+	}
121
+
122
+	// 入库
123
+	if !token.Guest {
124
+		if err := s.dao.InsertToken(tokenEncodeStr); err != nil {
125
+			utils.LogError("入库 Token 失败: " + err.Error())
126
+			return tokenEncodeStr
127
+		}
128
+	}
129
+
130
+	return tokenEncodeStr
131
+}
132
+
133
+// authPCAdmin
134
+// 管理端 API 校验
135
+func (s *SysServ) authPCAdmin(gctx *context.Context, token *utils.JWTToken) map[string]interface{} {
136
+	if !s.needAuth(gctx) {
137
+		return nil
138
+	}
139
+
140
+	if token.ID == "" || token.Guest == true {
141
+		return map[string]interface{}{
142
+			"code":  http.StatusUnauthorized,
143
+			"error": errors.New("用户未登录"),
144
+		}
145
+	}
146
+
147
+	if err := s.SetUserProfile(token.ID); err != nil {
148
+		return map[string]interface{}{
149
+			"code":  http.StatusInternalServerError,
150
+			"error": err,
151
+		}
152
+	}
153
+
154
+	return nil
155
+}
156
+
157
+func (s *SysServ) authWechat(gctx *context.Context, token *utils.JWTToken) map[string]interface{} {
158
+	// 微信 code
159
+	code := gctx.Input.Query("code")
160
+
161
+	var wxUser *utils.WechatUser
162
+	var openID string
163
+
164
+	// 未登录 或 未验证
165
+	if token.ID == "" {
166
+		// 需要微信验证
167
+		if code == "" {
168
+			return map[string]interface{}{
169
+				"code":  http.StatusUnauthorized,
170
+				"error": errors.New("请授权微信用户登录"),
171
+				"message": map[string]interface{}{
172
+					"appid": utils.GetWxAppID(s.org.OrgId),
173
+				},
174
+			}
175
+		}
176
+
177
+		// 微信用户信息
178
+		var err error
179
+		wxUser, err = s.wechartSignIn(gctx, code)
180
+		if err != nil {
181
+			return map[string]interface{}{
182
+				"code":  http.StatusInternalServerError,
183
+				"error": err,
184
+			}
185
+		}
186
+
187
+		if wxUser == nil {
188
+			return map[string]interface{}{
189
+				"code":  http.StatusInternalServerError,
190
+				"error": errors.New("请先关注公众号"),
191
+			}
192
+		}
193
+
194
+		openID = wxUser.OpenID
195
+	} else {
196
+		openID = token.ID
197
+	}
198
+
199
+	// 查询数据库是否存在已有映射
200
+	userMapList, err := s.dao.GetUserMappingByOpenID(openID)
201
+	if err != nil {
202
+		utils.LogError("校验人员失败: " + err.Error())
203
+		return map[string]interface{}{
204
+			"code":  http.StatusInternalServerError,
205
+			"error": errors.New("校验人员失败"),
206
+		}
207
+	}
208
+
209
+	var userMapping *model.TaUserMapping
210
+	for _, ump := range userMapList {
211
+		if openID == ump.Openid && models.ACCMAP_WECHAT == ump.AccountType {
212
+			userMapping = &ump
213
+		}
214
+	}
215
+
216
+	// 如果尚无人员映射信息, 代表人员初次使用本系统
217
+	if userMapping == nil {
218
+		// 如果没有微信用户信息, 代表产生了未知异常
219
+		if wxUser == nil || wxUser.UnionID == "" {
220
+			return map[string]interface{}{
221
+				"code":  http.StatusInternalServerError,
222
+				"error": errors.New("系统异常, 请清空缓存后重试"),
223
+			}
224
+		}
225
+
226
+		wxInfoJSON, err := json.Marshal(wxUser)
227
+		if err != nil {
228
+			utils.LogError("转换微信json信息失败: " + err.Error())
229
+			return map[string]interface{}{
230
+				"code":  http.StatusInternalServerError,
231
+				"error": errors.New("微信信息异常"),
232
+			}
233
+		}
234
+
235
+		userMapping = &model.TaUserMapping{
236
+			AccountType: models.ACCMAP_WECHAT,
237
+			Openid:      openID,
238
+			Uuid:        wxUser.UnionID,
239
+			AccountInfo: string(wxInfoJSON),
240
+		}
241
+	}
242
+
243
+	// 更新映射信息, 没有的话则插入
244
+	err = s.dao.UpdateUserMapping(userMapping)
245
+	if err != nil {
246
+		utils.LogError("保存用户映射信息失败: " + err.Error())
247
+		return map[string]interface{}{
248
+			"code":  http.StatusInternalServerError,
249
+			"error": errors.New("更新用户信息失败"),
250
+		}
251
+	}
252
+	s.ctx.Set("userMap", *userMapping)
253
+
254
+	if !s.needAuth(gctx) {
255
+		return nil
256
+	}
257
+
258
+	var cust *model.TaCustomer
259
+
260
+	// 如果只有映射, 但是没有人员信息
261
+	// 则新增人员
262
+	if userMapping.UserId == "" {
263
+		cust, err = s.saveNewCustomer(wxUser, userMapping)
264
+		if err != nil {
265
+			return map[string]interface{}{
266
+				"code":  http.StatusInternalServerError,
267
+				"error": err,
268
+			}
269
+		}
270
+	} else {
271
+		cust, err = s.customerdao.GetCustomerByID(userMapping.UserId)
272
+		if err != nil {
273
+			utils.LogError("查询用户信息失败: " + err.Error())
274
+			return map[string]interface{}{
275
+				"code":  http.StatusInternalServerError,
276
+				"error": err,
277
+			}
278
+		}
279
+	}
280
+	s.ctx.Set("customer", *cust)
281
+
282
+	if cust.UserId != "" {
283
+		if err := s.SetUserProfile(cust.UserId); err != nil {
284
+			return map[string]interface{}{
285
+				"code":  http.StatusInternalServerError,
286
+				"error": err,
287
+			}
288
+		}
289
+	}
290
+
291
+	return nil
292
+}
293
+
294
+// wechartSignIn 使用 code 微信登录
295
+func (s *SysServ) wechartSignIn(gctx *context.Context, code string) (*utils.WechatUser, error) {
296
+	if beego.BConfig.RunMode == "dev" {
297
+		return &utils.WechatUser{
298
+			OpenID: "OPENID",
299
+		}, nil
300
+	}
301
+
302
+	// 初始化微信配置
303
+	if err := s.initWechatClient(s.org.OrgId); err != nil {
304
+		utils.LogError("初始化微信客户端失败: " + err.Error())
305
+		return nil, errors.New("初始化微信客户端失败")
306
+	}
307
+
308
+	// 获取 微信信息
309
+	// 可能出现的情况是 openid 获取到了, 但是详情没有获取到
310
+	wxUserMap, err := utils.WxClientFor(s.org.OrgId).GetUserInfo(code)
311
+	if err != nil {
312
+		utils.LogError("获取微信信息失败: " + err.Error())
313
+
314
+		if wxUserMap == nil {
315
+			return nil, errors.New("获取微信信息失败")
316
+		}
317
+	}
318
+
319
+	return utils.MapToWechatUser(wxUserMap), nil
320
+}
321
+
322
+func (s *SysServ) getToken(gctx *context.Context) (*utils.JWTToken, error) {
323
+	tokenRaw := gctx.Input.Header(utils.TokenHeader)
324
+	if tokenRaw == "" {
325
+		return nil, nil
326
+	}
327
+
328
+	tokenEnStr := strings.Trim(strings.TrimLeft(tokenRaw, utils.TokenSchema), " ")
329
+	s.ctx.Set("token", tokenEnStr)
330
+
331
+	token, err := utils.PareseToken(tokenEnStr)
332
+	if err != nil {
333
+		utils.LogError("解析 Token 失败: " + err.Error())
334
+		return nil, errors.New("解析Token失败或已过期")
335
+	}
336
+
337
+	// 校验 token
338
+	tk, err := s.dao.GetToken(tokenEnStr)
339
+	if err != nil {
340
+		utils.LogError("查询 Token 失败: " + err.Error())
341
+		return nil, errors.New("校验Token失败或已过期")
342
+	}
343
+
344
+	if tk.Status == models.STATUS_DEL {
345
+		return nil, errors.New("超时 或者 Token 已过期")
346
+	}
347
+
348
+	return utils.MapToJWTToken(token), nil
349
+}
350
+
351
+// UpdateTokenExpire 更新 token 为过期
352
+// 如果发生错误, 此处选择忽略
353
+func (s *SysServ) UpdateTokenExpire(token string) {
354
+	if err := s.dao.UpdateTokenExpire(token); err != nil {
355
+		utils.LogError("更新 Token 过期失败: " + err.Error())
356
+	}
357
+}
358
+
359
+func (s *SysServ) needAuth(gctx *context.Context) bool {
360
+	route := gctx.Input.URL()
361
+	apiPrefix := beego.AppConfig.String("api::prefix")
362
+	guestAPI := beego.AppConfig.String("api::guest")
363
+
364
+	if strings.Index(route, apiPrefix+strings.Split(guestAPI, ":")[0]) > -1 {
365
+		return false
366
+	}
367
+
368
+	return true
369
+}
370
+
34 371
 // SetUserProfile 设置用户信息
35 372
 func (s *SysServ) SetUserProfile(id string) error {
36 373
 	user, err := s.dao.GetPureUserInfo(id)
@@ -60,32 +397,14 @@ func (s *SysServ) SetUserProfile(id string) error {
60 397
 	return nil
61 398
 }
62 399
 
63
-// SetCustomer 设置客户信息
64
-func (s *SysServ) SetCustomer(id string) error {
65
-	cust, err := s.dao.GetCustomer(id)
66
-	if err != nil {
67
-		return utils.LogError("获取客户基本信息失败: " + err.Error())
68
-	}
69
-
70
-	s.ctx.Set("customer", *cust)
71
-	return nil
72
-}
73
-
74
-// SaveNewCustomer 新增用户
75
-func (s *SysServ) SaveNewCustomer(wxInfo map[string]interface{}, userMap *model.TaUserMapping) (*model.TaCustomer, error) {
76
-	// 微信相关字段
77
-	nickyName := wxInfo["nickname"].(string)
78
-	sex := wxInfo["sex"].(float64)
79
-	headimgurl := wxInfo["headimgurl"].(string)
80
-
81
-	org := s.ctx.Get("org").(model.SysOrg)
82
-
400
+// saveNewCustomer 新增用户
401
+func (s *SysServ) saveNewCustomer(wxUser *utils.WechatUser, userMap *model.TaUserMapping) (*model.TaCustomer, error) {
83 402
 	cust := model.TaCustomer{
84
-		CustomerName: nickyName,
85
-		Name:         nickyName,
86
-		Sex:          int(sex),
87
-		Headimgurl:   headimgurl,
88
-		OrgId:        org.OrgId,
403
+		CustomerName: wxUser.NickName,
404
+		Name:         wxUser.NickName,
405
+		Sex:          int(wxUser.Sex),
406
+		Headimgurl:   wxUser.HeadImgURL,
407
+		OrgId:        s.org.OrgId,
89 408
 	}
90 409
 
91 410
 	if err := s.customerdao.SaveCustomer(&cust); err != nil {
@@ -130,7 +449,6 @@ func (s *SysServ) CheckWechatUserMapping(user map[string]interface{}) (*model.Ta
130 449
 
131 450
 	accountRaw, err := json.Marshal(user)
132 451
 	if err != nil {
133
-		beego.Error(err)
134 452
 		return nil, err
135 453
 	}
136 454
 	account := string(accountRaw)
@@ -146,7 +464,6 @@ func (s *SysServ) CheckWechatUserMapping(user map[string]interface{}) (*model.Ta
146 464
 	userMapList, err := s.dao.GetUserMappingByOpenID(openID)
147 465
 
148 466
 	if err != nil {
149
-		beego.Error(err)
150 467
 		return nil, err
151 468
 	}
152 469
 
@@ -172,24 +489,28 @@ func (s *SysServ) CheckWechatUserMapping(user map[string]interface{}) (*model.Ta
172 489
 	return &userMapping, nil
173 490
 }
174 491
 
175
-// GetWeChatConfig 获取微信配置
176
-func (s SysServ) GetWeChatConfig(org string) (map[string]string, error) {
177
-	conf, err := s.dao.GetWeChatConfig(org)
492
+// initWechatClient 初始化微信客户端
493
+func (s SysServ) initWechatClient(orgID string) error {
494
+	conf, err := s.dao.GetWeChatConfig(orgID)
178 495
 	if err != nil {
179
-		return nil, err
496
+		utils.LogError("获取微信配置失败: " + err.Error())
497
+		return errors.New("获取微信配置失败")
180 498
 	}
181 499
 
182
-	if conf.ConfId == "" {
183
-		return nil, errors.New("未找到微信配置")
500
+	if conf == nil || conf.ConfId == "" {
501
+		return errors.New("未找到微信配置")
184 502
 	}
185 503
 
186
-	return map[string]string{
504
+	cert := map[string]string{
187 505
 		"appid":  conf.Appid,
188 506
 		"secret": conf.Secret,
189 507
 		"token":  conf.Token,
190 508
 		"aeskey": conf.Aeskey,
191 509
 		"wxid":   conf.Wxid,
192
-	}, nil
510
+	}
511
+
512
+	utils.WxClientSingleton(orgID, cert)
513
+	return nil
193 514
 }
194 515
 
195 516
 // SetOrgByID 获取组织
@@ -203,6 +524,7 @@ func (s SysServ) SetOrgByID(orgID string) error {
203 524
 	}
204 525
 
205 526
 	s.ctx.Set("org", *org)
527
+	s.org = *org
206 528
 
207 529
 	return nil
208 530
 }

+ 68
- 0
utils/excel.go View File

@@ -0,0 +1,68 @@
1
+package utils
2
+
3
+import (
4
+	"io"
5
+	"reflect"
6
+
7
+	"github.com/tealeg/xlsx"
8
+)
9
+
10
+// TinyXLSXEngine 简版 xlsx 处理器
11
+type TinyXLSXEngine struct {
12
+	xlsxFile *xlsx.File
13
+	toSlice  func(interface{}) []interface{}
14
+}
15
+
16
+// NewTinyXLSXEngine init
17
+func NewTinyXLSXEngine() *TinyXLSXEngine {
18
+	return &TinyXLSXEngine{}
19
+}
20
+
21
+// SetTransFunc 设置转换函数
22
+func (t *TinyXLSXEngine) SetTransFunc(f func(interface{}) []interface{}) *TinyXLSXEngine {
23
+	t.toSlice = f
24
+	return t
25
+}
26
+
27
+// SetData 设置数据
28
+func (t *TinyXLSXEngine) SetData(data interface{}) *TinyXLSXEngine {
29
+	file := xlsx.NewFile()
30
+	t.xlsxFile = file
31
+
32
+	sheet, err := file.AddSheet("sheet1")
33
+	if err != nil {
34
+		LogError("创建 xlsx sheet 失败: " + err.Error())
35
+		return t
36
+	}
37
+
38
+	if t.toSlice == nil {
39
+		t.toSlice = func(dt interface{}) []interface{} {
40
+			return nil
41
+		}
42
+	}
43
+
44
+	v := reflect.ValueOf(data)
45
+	if v.Kind() == reflect.Ptr {
46
+		v.Elem()
47
+	}
48
+
49
+	if v.Kind() == reflect.Slice && v.IsValid() {
50
+		l := v.Len()
51
+		for i := 0; i < l; i++ {
52
+			rowData := v.Index(i).Interface()
53
+			cols := t.toSlice(rowData)
54
+
55
+			row := sheet.AddRow()
56
+			for _, col := range cols {
57
+				row.AddCell().SetValue(col)
58
+			}
59
+		}
60
+	}
61
+
62
+	return t
63
+}
64
+
65
+// Write 写数据
66
+func (t *TinyXLSXEngine) Write(w io.Writer) error {
67
+	return t.xlsxFile.Write(w)
68
+}

+ 48
- 0
utils/jwt.go View File

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

+ 56
- 0
utils/wechat.go View File

@@ -6,6 +6,17 @@ import (
6 6
 
7 7
 var wxClients map[string]*wx.Client
8 8
 
9
+type WechatUser struct {
10
+	OpenID     string  `json:"openid"`
11
+	NickName   string  `json:"nickname"`
12
+	Sex        float64 `json:"sex"`
13
+	Province   string  `json:"province"`
14
+	City       string  `json:"city"`
15
+	Country    string  `json:"country"`
16
+	HeadImgURL string  `json:"headimgurl"`
17
+	UnionID    string  `json:"unionid"`
18
+}
19
+
9 20
 // WxClientSingleton 初始化
10 21
 func WxClientSingleton(org string, cert map[string]string) {
11 22
 	if wxClients == nil {
@@ -32,3 +43,48 @@ func GetWxAppID(org string) string {
32 43
 func init() {
33 44
 	wx.SetLogInst(defaultLogger)
34 45
 }
46
+
47
+// MapToWechatUser 映射微信人员
48
+func MapToWechatUser(data map[string]interface{}) *WechatUser {
49
+	subscribe, has := data["subscribe"]
50
+	if has {
51
+		if subscribe == nil {
52
+			return nil
53
+		}
54
+
55
+		sub := subscribe.(float64)
56
+		if sub == 0 {
57
+			return nil
58
+		}
59
+	}
60
+
61
+	user := WechatUser{
62
+		OpenID: data["openid"].(string),
63
+	}
64
+
65
+	if data["sex"] != nil {
66
+		user.Sex = data["sex"].(float64)
67
+	}
68
+
69
+	if data["province"] != nil {
70
+		user.Province = data["province"].(string)
71
+	}
72
+
73
+	if data["city"] != nil {
74
+		user.City = data["city"].(string)
75
+	}
76
+
77
+	if data["country"] != nil {
78
+		user.Country = data["country"].(string)
79
+	}
80
+
81
+	if data["headimgurl"] != nil {
82
+		user.HeadImgURL = data["headimgurl"].(string)
83
+	}
84
+
85
+	if data["unionid"] != nil {
86
+		user.UnionID = data["unionid"].(string)
87
+	}
88
+
89
+	return &user
90
+}