zjxpcyc 6 years ago
parent
commit
c403fe1e00
11 changed files with 269 additions and 68 deletions
  1. 1
    1
      conf/app.conf
  2. 26
    42
      controllers/auth.go
  3. 9
    1
      controllers/base.go
  4. 22
    0
      controllers/file.go
  5. 4
    3
      controllers/user/user.go
  6. 12
    0
      log/common.log
  7. 10
    0
      models/model/sys_token_log.go
  8. 40
    0
      models/sys.go
  9. 74
    19
      service/sys.go
  10. 68
    0
      utils/excel.go
  11. 3
    2
      utils/jwt.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
 
10 10
 

+ 26
- 42
controllers/auth.go View File

@@ -1,24 +1,23 @@
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
-	"time"
10 6
 )
11 7
 
12 8
 // Authenticate 权限验证
9
+// 其中 token 的处理方式是
10
+// 1、获取 request 中 token
11
+// 2、放入 Context 中
12
+// 3、校验 token
13
+// 4、结束后,设置过期并从 Context 中删除
14
+// 5、生成新的 token, 并放入 Context 中
13 15
 func (c *BaseController) authenticate() {
14 16
 	serv := service.NewSysServ(c.Context)
15 17
 
16 18
 	// 鉴权 - 并初始化上下文
17 19
 	res := serv.AuthAndInitCtx(c.Ctx)
18 20
 
19
-	// 设置 TOKEN
20
-	c.setToken()
21
-
22 21
 	if res != nil {
23 22
 		code := http.StatusOK
24 23
 		if res["code"] != nil {
@@ -33,48 +32,33 @@ func (c *BaseController) authenticate() {
33 32
 				data = res["message"].(map[string]interface{})
34 33
 			}
35 34
 
35
+			// 设置旧 token 过期
36
+			c.SetTokenExipre()
37
+
36 38
 			c.ResponseData(data, err, code)
37 39
 		}
38 40
 	}
39
-}
40 41
 
41
-// setToken 设置 TOKEN
42
-// 15 分钟后过期
43
-func (c *BaseController) setToken() {
44
-	var token *utils.JWTToken
45
-	exp := time.Now().Local().Add(15 * time.Second)
42
+	// 设置旧 token 过期
43
+	c.SetTokenExipre()
46 44
 
47
-	if c.Context.Get("user") != nil {
48
-		user := c.Context.Get("user").(model.SysUser)
49
-
50
-		token = &utils.JWTToken{
51
-			Guest:  false,
52
-			ID:     user.UserId,
53
-			Expire: exp,
54
-		}
55
-	} else if c.Context.Get("userMap") != nil {
56
-		userMap := c.Context.Get("userMap").(model.TaUserMapping)
45
+	// 生成新 token
46
+	c.CreateNewToken()
47
+}
57 48
 
58
-		token = &utils.JWTToken{
59
-			Guest:  false,
60
-			ID:     userMap.Openid,
61
-			Expire: exp,
62
-		}
63
-	} else {
64
-		token = &utils.JWTToken{
65
-			Guest:  true,
66
-			Expire: exp,
67
-		}
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))
68 55
 	}
69 56
 
70
-	tokenEncode, err := utils.CreateToken(token.ToMap())
71
-	if err != nil {
72
-		utils.LogError("系统生成 Token 失败: " + err.Error())
73
-		c.ResponseError(
74
-			errors.New("系统异常, 请退出重试"),
75
-			http.StatusInternalServerError,
76
-		)
77
-	}
57
+	c.Context.Set("token", "")
58
+}
78 59
 
79
-	c.Ctx.Output.Header(utils.TokenHeader, utils.TokenSchema+" "+tokenEncode)
60
+// CreateNewToken 新 token
61
+func (c *BaseController) CreateNewToken() {
62
+	serv := service.NewSysServ(c.Context)
63
+	c.Context.Set("token", serv.NewToken())
80 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()

+ 22
- 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,23 @@ 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
+}

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

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

+ 12
- 0
log/common.log View File

@@ -3060,3 +3060,15 @@
3060 3060
 2018/09/11 17:57:59 [E] 用户没有设置默认案场
3061 3061
 2018/09/11 17:58:51 [E] 用户没有设置默认案场
3062 3062
 2018/09/11 18:00:05 [E] 用户没有设置默认案场
3063
+2018/09/13 09:57:29 [E] 入库 Token 失败: Error 1054: Unknown column 'auto_increment' in 'field list'
3064
+2018/09/13 09:57:29 [E] 入库 Token 失败: Error 1054: Unknown column 'auto_increment' in 'field list'
3065
+2018/09/13 10:02:12 [E] 查询 Token 失败: Error 1054: Unknown column 'auto_increment' in 'field list'
3066
+2018/09/13 10:03:18 [E] 查询 Token 失败: Error 1054: Unknown column 'AUTO_INCREMENT' in 'field list'
3067
+2018/09/13 10:14:14 [E] 用户没有设置默认案场
3068
+2018/09/13 10:19:48 [E] 用户没有设置默认案场
3069
+2018/09/13 10:21:24 [E] 用户没有设置默认案场
3070
+2018/09/13 10:22:19 [E] 用户没有设置默认案场
3071
+2018/09/13 10:22:23 [E] 用户没有设置默认案场
3072
+2018/09/13 10:23:43 [E] 用户没有设置默认案场
3073
+2018/09/13 10:24:25 [E] 用户没有设置默认案场
3074
+2018/09/13 10:49:12 [E] 用户没有设置默认案场

+ 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
+}

+ 74
- 19
service/sys.go View File

@@ -9,6 +9,7 @@ import (
9 9
 	"spaceofcheng/services/models/model"
10 10
 	"spaceofcheng/services/utils"
11 11
 	"strings"
12
+	"time"
12 13
 
13 14
 	"github.com/astaxie/beego"
14 15
 	"github.com/astaxie/beego/context"
@@ -56,14 +57,12 @@ func (s *SysServ) AuthAndInitCtx(gctx *context.Context) map[string]interface{} {
56 57
 	// 获取 token
57 58
 	token, err := s.getToken(gctx)
58 59
 	if err != nil {
60
+		// token 报错一律视为需要重新登录
59 61
 		return map[string]interface{}{
60
-			"code":  http.StatusInternalServerError,
62
+			"code":  http.StatusUnauthorized,
61 63
 			"error": err,
62 64
 		}
63 65
 	}
64
-	if token != nil {
65
-		s.ctx.Set("token", *token)
66
-	}
67 66
 
68 67
 	// 客户端类型
69 68
 	// 通过 UA 判断
@@ -71,12 +70,12 @@ func (s *SysServ) AuthAndInitCtx(gctx *context.Context) map[string]interface{} {
71 70
 
72 71
 	// pc 管理端
73 72
 	if clientType == utils.ClientAdmin {
74
-		return s.authPCAdmin(gctx)
73
+		return s.authPCAdmin(gctx, token)
75 74
 	}
76 75
 
77 76
 	// wechat 端
78 77
 	if clientType == utils.ClientWechat {
79
-		return s.authWechat(gctx)
78
+		return s.authWechat(gctx, token)
80 79
 	}
81 80
 
82 81
 	return map[string]interface{}{
@@ -85,18 +84,59 @@ func (s *SysServ) AuthAndInitCtx(gctx *context.Context) map[string]interface{} {
85 84
 	}
86 85
 }
87 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
+
88 133
 // authPCAdmin
89 134
 // 管理端 API 校验
90
-func (s *SysServ) authPCAdmin(gctx *context.Context) map[string]interface{} {
135
+func (s *SysServ) authPCAdmin(gctx *context.Context, token *utils.JWTToken) map[string]interface{} {
91 136
 	if !s.needAuth(gctx) {
92 137
 		return nil
93 138
 	}
94 139
 
95
-	token := utils.JWTToken{}
96
-	if s.ctx.Get("token") != nil {
97
-		token = s.ctx.Get("token").(utils.JWTToken)
98
-	}
99
-
100 140
 	if token.ID == "" || token.Guest == true {
101 141
 		return map[string]interface{}{
102 142
 			"code":  http.StatusUnauthorized,
@@ -114,15 +154,10 @@ func (s *SysServ) authPCAdmin(gctx *context.Context) map[string]interface{} {
114 154
 	return nil
115 155
 }
116 156
 
117
-func (s *SysServ) authWechat(gctx *context.Context) map[string]interface{} {
157
+func (s *SysServ) authWechat(gctx *context.Context, token *utils.JWTToken) map[string]interface{} {
118 158
 	// 微信 code
119 159
 	code := gctx.Input.Query("code")
120 160
 
121
-	token := utils.JWTToken{}
122
-	if s.ctx.Get("token") != nil {
123
-		token = s.ctx.Get("token").(utils.JWTToken)
124
-	}
125
-
126 161
 	var wxUser *utils.WechatUser
127 162
 	var openID string
128 163
 
@@ -291,16 +326,36 @@ func (s *SysServ) getToken(gctx *context.Context) (*utils.JWTToken, error) {
291 326
 	}
292 327
 
293 328
 	tokenEnStr := strings.Trim(strings.TrimLeft(tokenRaw, utils.TokenSchema), " ")
329
+	s.ctx.Set("token", tokenEnStr)
294 330
 
295 331
 	token, err := utils.PareseToken(tokenEnStr)
296 332
 	if err != nil {
297 333
 		utils.LogError("解析 Token 失败: " + err.Error())
298
-		return nil, errors.New("解析 Token 失败")
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 已过期")
299 346
 	}
300 347
 
301 348
 	return utils.MapToJWTToken(token), nil
302 349
 }
303 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
+
304 359
 func (s *SysServ) needAuth(gctx *context.Context) bool {
305 360
 	route := gctx.Input.URL()
306 361
 	apiPrefix := beego.AppConfig.String("api::prefix")

+ 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
+}

+ 3
- 2
utils/jwt.go View File

@@ -53,7 +53,7 @@ func (t *JWTToken) ToMap() map[string]interface{} {
53 53
 		"guest":    t.Guest,
54 54
 		"user":     t.ID,
55 55
 		"password": t.Password,
56
-		"exp":      t.Expire,
56
+		"exp":      t.Expire.Format("2006-01-02 15:04:05"),
57 57
 	}
58 58
 }
59 59
 
@@ -74,7 +74,8 @@ func MapToJWTToken(data map[string]interface{}) *JWTToken {
74 74
 	}
75 75
 
76 76
 	if data["exp"] != nil {
77
-		token.Expire = data["exp"].(time.Time)
77
+		exp, _ := time.Parse("2006-01-02 15:04:05", data["exp"].(string))
78
+		token.Expire = exp
78 79
 	}
79 80
 
80 81
 	return &token