此文章只將思想,不提供具體完整實(shí)現(xiàn)(博主太懶,懶得整理),有疑問或想了解的可以私信或評(píng)論
背景
在傳統(tǒng)的java web 中小型項(xiàng)目中,一般使用session暫存會(huì)話信息,比如登錄者的身份信息等。此機(jī)制是借用http的cookie機(jī)制實(shí)現(xiàn),但是對(duì)于app來說每次請(qǐng)求都保存并共享cookie信息比較麻煩,并且傳統(tǒng)的session對(duì)集群并不友好,所以一般app后端服務(wù)都使用token來區(qū)分用戶登錄信息。
j2ee的session機(jī)制大家都很了解,使用非常方便,在傳統(tǒng)java web應(yīng)用中很好用,但是在互聯(lián)網(wǎng)項(xiàng)目中或用得到集群的一些項(xiàng)目就有些問題,比如序列化問題,同步的延時(shí)問題等等,所以我們需要一個(gè)使用起來類似session的卻能解決得了集群等問題的一個(gè)工具。
方案
我們使用cache機(jī)制來解決這個(gè)問題,比較流行的redis是個(gè)nosql內(nèi)存數(shù)據(jù)庫(kù),而且?guī)в衏ache的失效機(jī)制,很適合做會(huì)話數(shù)據(jù)的存儲(chǔ)。而token字符串需要在第一次請(qǐng)求時(shí)服務(wù)器返回給客戶端,客戶端以后每次請(qǐng)求都使用這個(gè)token標(biāo)識(shí)身份。為了對(duì)業(yè)務(wù)開發(fā)透明,我們把a(bǔ)pp的請(qǐng)求和響應(yīng)做的報(bào)文封裝,只需要對(duì)客戶端的http請(qǐng)求工具類做點(diǎn)手腳,對(duì)服務(wù)端的mvc框架做點(diǎn)手腳就可以了,客戶端的http工具類修改很簡(jiǎn)單,主要是服務(wù)端的協(xié)議封裝。
實(shí)現(xiàn)思路
一、制定請(qǐng)求響應(yīng)報(bào)文協(xié)議。
二、解析協(xié)議處理token字符串。
三、使用redis存儲(chǔ)管理token以及對(duì)應(yīng)的會(huì)話信息。
四、提供保存、獲取會(huì)話信息的API。
我們逐步講解下每一步的實(shí)現(xiàn)方案。
一、制定請(qǐng)求響應(yīng)報(bào)文協(xié)議。
既然要封裝報(bào)文協(xié)議,就需要考慮什么是公共字段,什么是業(yè)務(wù)字段,報(bào)文的數(shù)據(jù)結(jié)構(gòu)等。
請(qǐng)求的公共字段一般有token、版本、平臺(tái)、機(jī)型、imei、app來源等,其中token是我們這次的主角。
響應(yīng)的公共字段一般有token、結(jié)果狀態(tài)(success,fail)、結(jié)果碼(code)、結(jié)果信息等。
報(bào)文數(shù)據(jù)結(jié)構(gòu),我們選用json,原因是json普遍、可視化好、字節(jié)占用低。
請(qǐng)求報(bào)文如下,body中存放業(yè)務(wù)信息,比如登錄的用戶名和密碼等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
{ "token": "客戶端token", /**客戶端構(gòu)建版本號(hào)*/ "version": 11, /**客戶端平臺(tái)類型*/ "platform": "IOS", /**客戶端設(shè)備型號(hào)*/ "machineModel": "Iphone 6s", "imei": "客戶端串號(hào)(手機(jī))", /**真正的消息體,應(yīng)為map*/ "body": { "key1": "value1", "key2": { "key21": "value21" }, "key3": [ 1, ] } } |
響應(yīng)的報(bào)文
1
2
3
4
5
6
7
8
9
10
11
12
13
|
{ /**是否成功*/ "success": false, /**每個(gè)請(qǐng)求都會(huì)返回token,客戶端每次請(qǐng)求都應(yīng)使用最新的token*/ "token": "服務(wù)器為當(dāng)前請(qǐng)求選擇的token", /**失敗碼*/ "failCode": 1, /**業(yè)務(wù)消息或者失敗消息*/ "msg": "未知原因", /**返回的真實(shí)業(yè)務(wù)數(shù)據(jù),可為任意可序列化的對(duì)象*/ "body": null } } |
二、解析協(xié)議處理token字符串。
服務(wù)端的mvc框架我們選用的是SpringMVC框架,SpringMVC也比較普遍,不做描述。
暫且不提t(yī)oken的處理,先解決制定報(bào)文后怎么做參數(shù)傳遞。
因?yàn)檎?qǐng)求信息被做了封裝,所以要讓springmvc框架能正確注入我們?cè)贑ontroller需要的參數(shù),就需要對(duì)報(bào)文做解析和轉(zhuǎn)換。
要對(duì)請(qǐng)求信息做解析,我們需要自定義springmvc的參數(shù)轉(zhuǎn)換器,通過實(shí)現(xiàn)HandlerMethodArgumentResolver接口可以定義一個(gè)參數(shù)轉(zhuǎn)換器
RequestBodyResolver實(shí)現(xiàn)resolveArgument方法,對(duì)參數(shù)進(jìn)行注入,以下代碼為示例代碼,切勿拿來直用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String requestBodyStr = webRequest.getParameter(requestBodyParamName);//獲取請(qǐng)求報(bào)文,可以使用任意方式傳遞報(bào)文,只要在這獲取到就可以 if(StringUtils.isNotBlank(requestBodyStr)){ String paramName = parameter.getParameterName();//獲取Controller中參數(shù)名 Class<?> paramClass = parameter.getParameterType();//獲取Controller中參數(shù)類型 /* 通過json工具類解析報(bào)文 */ JsonNode jsonNode = objectMapper.readTree(requestBodyStr); if(paramClass.equals(ServiceRequest.class)){//ServiceRequest為請(qǐng)求報(bào)文對(duì)應(yīng)的VO ServiceRequest serviceRequest = objectMapper.readValue(jsonNode.traverse(),ServiceRequest.class); return serviceRequest;//返回這個(gè)object就是注入到參數(shù)中了,一定要對(duì)應(yīng)類型,否則異常不容易捕獲 } if(jsonNode!=null){//從報(bào)文中查找Controller中需要的參數(shù) JsonNode paramJsonNode = jsonNode.findValue(paramName); if(paramJsonNode!=null){ return objectMapper.readValue(paramJsonNode.traverse(), paramClass); } } } return null; } |
將自己定義的參數(shù)轉(zhuǎn)換器配置到SrpingMVC的配置文件中<mvc:argument-resolvers>
1
2
3
4
5
6
7
8
|
< mvc:argument-resolvers > <!-- 統(tǒng)一的請(qǐng)求信息處理,從ServiceRequest中取數(shù)據(jù) --> < bean id = "requestBodyResolver" class = "com.niuxz.resolver.RequestBodyResolver" > < property name = "objectMapper" >< bean class = "com.shoujinwang.utils.json.ObjectMapper" ></ bean ></ property > <!-- 配置請(qǐng)求中ServiceRequest對(duì)應(yīng)的字段名,默認(rèn)為requestBody --> < property name = "requestBodyParamName" >< value >requestBody</ value ></ property > </ bean > </ mvc:argument-resolvers > |
這樣就可以使報(bào)文中的參數(shù)能被springmvc正確識(shí)別了。
接下來我們要對(duì)token做處理了,我們需要添加一個(gè)SrpingMVC攔截器將每次請(qǐng)求都攔截下來,這屬于常用功能,不做細(xì)節(jié)描述
1
2
3
4
5
6
|
Matcher m1 =Pattern.compile("\"token\":\"(.*?)\"").matcher(requestBodyStr); if(m1.find()){ token = m1.group(1); } tokenMapPool.verifyToken(token);//對(duì)token做公共處理,驗(yàn)證 |
這樣就簡(jiǎn)單的獲取到了token了,可以做公共處理了。
三、使用redis存儲(chǔ)管理token以及對(duì)應(yīng)的會(huì)話信息。
其實(shí)就是寫一個(gè)redis的操作工具類,因?yàn)槭褂昧藄pring作為項(xiàng)目主框架,而且我們用到redis的功能并不多,所以直接使用spring提供的CacheManager功能
配置org.springframework.data.redis.cache.RedisCacheManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<!-- 緩存管理器 全局變量等可以用它存取--> < bean id = "cacheManager" class = "org.springframework.data.redis.cache.RedisCacheManager" > < constructor-arg > < ref bean = "redisTemplate" /> </ constructor-arg > < property name = "usePrefix" value = "true" /> < property name = "cachePrefix" > < bean class = "org.springframework.data.redis.cache.DefaultRedisCachePrefix" > < constructor-arg name = "delimiter" value = ":@WebServiceInterface" /> </ bean > </ property > < property name = "expires" > <!-- 緩存有效期 --> < map > < entry > < key >< value >tokenPoolCache</ value ></ key > <!-- tokenPool緩存名 --> < value >2592000</ value > <!-- 有效時(shí)間 --> </ entry > </ map > </ property > </ bean > |
四、提供保存、獲取會(huì)話信息的API。
通過以上前戲我們已經(jīng)把token處理的差不多了,接下來我們要實(shí)現(xiàn)token管理工作了
我們需要讓業(yè)務(wù)開發(fā)方便的保存獲取會(huì)話信息,還要使token是透明的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cache.Cache; import org.springframework.cache.Cache.ValueWrapper; import org.springframework.cache.CacheManager; /** * * 類 名: TokenMapPoolBean * 描 述: token以及相關(guān)信息調(diào)用處理類 * 修 改 記 錄: * @version V1.0 * @date 2016年4月22日 * @author NiuXZ * */ public class TokenMapPoolBean { private static final Log log = LogFactory.getLog(TokenMapPoolBean.class); /** 當(dāng)前請(qǐng)求對(duì)應(yīng)的token*/ private ThreadLocal< String > currentToken; private CacheManager cacheManager; private String cacheName; private TokenGenerator tokenGenerator; public TokenMapPoolBean(CacheManager cacheManager, String cacheName, TokenGenerator tokenGenerator) { this.cacheManager = cacheManager; this.cacheName = cacheName; this.tokenGenerator = tokenGenerator; currentToken = new ThreadLocal< String >(); } /** * 如果token合法就返回token,不合法就創(chuàng)建一個(gè)新的token并返回, * 將token放入ThreadLocal中 并初始化一個(gè)tokenMap * @param token * @return token */ public String verifyToken(String token) { // log.info("校驗(yàn)Token:\""+token+"\""); String verifyedToken = null; if (tokenGenerator.checkTokenFormat(token)) { // log.info("校驗(yàn)Token成功:\""+token+"\""); verifyedToken = token; } else { verifyedToken = newToken(); } currentToken.set(verifyedToken); Cache cache = cacheManager.getCache(cacheName); if (cache == null) { throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName); } ValueWrapper value = cache.get(verifyedToken); //token對(duì)應(yīng)的值為空,就創(chuàng)建一個(gè)新的tokenMap放入緩存中 if (value == null || value.get() == null) { verifyedToken = newToken(); currentToken.set(verifyedToken); Map< String , Object> tokenMap = new HashMap< String , Object>(); cache.put(verifyedToken, tokenMap); } return verifyedToken; } /** * 生成新的token * @return token */ private String newToken() { Cache cache = cacheManager.getCache(cacheName); if (cache == null) { throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName); } String newToken = null; int count = 0; do { count++; newToken = tokenGenerator.generatorToken(); } while (cache.get(newToken) != null); // log.info("創(chuàng)建Token成功:\""+newToken+"\" 嘗試生成:"+count+"次"); return newToken; } /** * 獲取當(dāng)前請(qǐng)求的tokenMap中對(duì)應(yīng)key的對(duì)象 * @param key * @return 當(dāng)前請(qǐng)求的tokenMap中對(duì)應(yīng)key的屬性,模擬session */ public Object getAttribute(String key) { Cache cache = cacheManager.getCache(cacheName); if (cache == null) { throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName); } ValueWrapper tokenMapWrapper = cache.get(currentToken.get()); Map< String , Object> tokenMap = null; if (tokenMapWrapper != null) { tokenMap = (Map< String , Object>) tokenMapWrapper.get(); } if (tokenMap == null) { verifyToken(currentToken.get()); tokenMapWrapper = cache.get(currentToken.get()); tokenMap = (Map< String , Object>) tokenMapWrapper.get(); } return tokenMap.get(key); } /** * 設(shè)置到當(dāng)前請(qǐng)求的tokenMap中,模擬session< br > * TODO:此種方式設(shè)置attribute有問題:< br > * 1、可能在同一token并發(fā)的情況下執(zhí)行cache.put(currentToken.get(),tokenMap);時(shí),< br > * tokenMap可能不是最新,會(huì)導(dǎo)致丟失數(shù)據(jù)。< br > * 2、每次都put整個(gè)tokenMap,數(shù)據(jù)量太大,需要優(yōu)化< br > * @param key value */ public void setAttribute(String key, Object value) { Cache cache = cacheManager.getCache(cacheName); if (cache == null) { throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName); } ValueWrapper tokenMapWrapper = cache.get(currentToken.get()); Map< String , Object> tokenMap = null; if (tokenMapWrapper != null) { tokenMap = (Map< String , Object>) tokenMapWrapper.get(); } if (tokenMap == null) { verifyToken(currentToken.get()); tokenMapWrapper = cache.get(currentToken.get()); tokenMap = (Map< String , Object>) tokenMapWrapper.get(); } log.info("TokenMap.put(key=" + key + ",value=" + value + ")"); tokenMap.put(key, value); cache.put(currentToken.get(), tokenMap); } /** * 獲取當(dāng)前線程綁定的用戶token * @return token */ public String getToken() { if (currentToken.get() == null) { //初始化一次token verifyToken(null); } return currentToken.get(); } /** * 刪除token以及tokenMap * @param token */ public void removeTokenMap(String token) { if (token == null) { return; } Cache cache = cacheManager.getCache(cacheName); if (cache == null) { throw new RuntimeException("獲取不到存放token的緩存池,chacheName:" + cacheName); } log.info("刪除Token:token=" + token); cache.evict(token); } public CacheManager getCacheManager() { return cacheManager; } public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } public String getCacheName() { return cacheName; } public void setCacheName(String cacheName) { this.cacheName = cacheName; } public TokenGenerator getTokenGenerator() { return tokenGenerator; } public void setTokenGenerator(TokenGenerator tokenGenerator) { this.tokenGenerator = tokenGenerator; } public void clear() { currentToken.remove(); } } |
這里用到了ThreadLocal變量是因?yàn)閟ervlet容器一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)線程,在一個(gè)請(qǐng)求的生命周期內(nèi)都是處于同一個(gè)線程中,而同時(shí)又有多個(gè)線程共享token管理器,所以需要這個(gè)線程本地變量來保存token字符串。
注意事項(xiàng):
1、verifyToken方法的調(diào)用,一定要在每次請(qǐng)求最開始調(diào)用。并且在請(qǐng)求結(jié)束后調(diào)用clear做清除,以免下次有未知異常導(dǎo)致verifyToken未被執(zhí)行,卻在返回時(shí)從ThreadLocal里取出token返回。(這個(gè)bug困擾我好幾天,公司n個(gè)開發(fā)檢查代碼也沒找到,最后我經(jīng)過測(cè)試發(fā)現(xiàn)是在發(fā)生404的時(shí)候沒有進(jìn)入攔截器,所以就沒有調(diào)用verifyToken方法,導(dǎo)致返回的異常信息中的token為上一次請(qǐng)求的token,導(dǎo)致詭異的串號(hào)問題。嗯,記我一大鍋)。
2、客戶端一定要在封裝http工具的時(shí)候把每次token保存下來,并用于下一次請(qǐng)求。公司ios開發(fā)請(qǐng)的外包,但是外包沒按要求做,在未登錄時(shí),不保存token,每次傳遞的都是null,導(dǎo)致每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)token,服務(wù)器創(chuàng)建了大量的無用token。
使用
使用方式也很簡(jiǎn)單,以下是封裝的登錄管理器,可以參考一下token管理器對(duì)于登陸管理器的應(yīng)用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cache.Cache; import org.springframework.cache.Cache.ValueWrapper; import org.springframework.cache.CacheManager; import com.niuxz.base.Constants; /** * * 類 名: LoginManager * 描 述: 登錄管理器 * 修 改 記 錄: * @version V1.0 * @date 2016年7月19日 * @author NiuXZ * */ public class LoginManager { private static final Log log = LogFactory.getLog(LoginManager.class); private CacheManager cacheManager; private String cacheName; private TokenMapPoolBean tokenMapPool; public LoginManager(CacheManager cacheManager, String cacheName, TokenMapPoolBean tokenMapPool) { this.cacheManager = cacheManager; this.cacheName = cacheName; this.tokenMapPool = tokenMapPool; } public void login(String userId) { log.info("用戶登錄:userId=" + userId); Cache cache = cacheManager.getCache(cacheName); ValueWrapper valueWrapper = cache.get(userId); String token = (String) (valueWrapper == null ? null : valueWrapper.get()); tokenMapPool.removeTokenMap(token);//退出之前登錄記錄 tokenMapPool.setAttribute(Constants.LOGGED_USER_ID, userId); cache.put(userId, tokenMapPool.getToken()); } public void logoutCurrent(String phoneTel) { String curUserId = getCurrentUserId(); log.info("用戶退出:userId=" + curUserId); tokenMapPool.removeTokenMap(tokenMapPool.getToken());//退出登錄 if (curUserId != null) { Cache cache = cacheManager.getCache(cacheName); cache.evict(curUserId); cache.evict(phoneTel); } } /** * 獲取當(dāng)前用戶的id * @return */ public String getCurrentUserId() { return (String) tokenMapPool.getAttribute(Constants.LOGGED_USER_ID); } public CacheManager getCacheManager() { return cacheManager; } public String getCacheName() { return cacheName; } public TokenMapPoolBean getTokenMapPool() { return tokenMapPool; } public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } public void setCacheName(String cacheName) { this.cacheName = cacheName; } public void setTokenMapPool(TokenMapPoolBean tokenMapPool) { this.tokenMapPool = tokenMapPool; } } |
下面是一段常見的發(fā)送短信驗(yàn)證碼接口,有的應(yīng)用也是用session存儲(chǔ)驗(yàn)證碼,我不建議用這種方式,存session弊端相當(dāng)大。大家看看就好,不是我寫的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public void sendValiCodeByPhoneNum(String phoneNum, String hintMsg, String logSuffix) { validatePhoneTimeSpace(); // 獲取6位隨機(jī)數(shù) String code = CodeUtil.getValidateCode(); log.info(code + "------->" + phoneNum); // 調(diào)用短信驗(yàn)證碼下發(fā)接口 RetStatus retStatus = msgSendUtils.sendSms(code + hintMsg, phoneNum); if (!retStatus.getIsOk()) { log.info(retStatus.toString()); throw new ThrowsToDataException(ServiceResponseCode.FAIL_INVALID_PARAMS, "手機(jī)驗(yàn)證碼獲取失敗,請(qǐng)稍后再試"); } // 重置session tokenMapPool.setAttribute(Constants.VALIDATE_PHONE, phoneNum); tokenMapPool.setAttribute(Constants.VALIDATE_PHONE_CODE, code.toString()); tokenMapPool.setAttribute(Constants.SEND_CODE_WRONGNU, 0); tokenMapPool.setAttribute(Constants.SEND_CODE_TIME, new Date().getTime()); log.info(logSuffix + phoneNum + "短信驗(yàn)證碼:" + code); } |
處理響應(yīng)
有的同學(xué)會(huì)問了 那么響應(yīng)的報(bào)文封裝呢?
1
2
3
4
5
6
7
|
@RequestMapping("record") @ResponseBody public ServiceResponse record(String message){ String userId = loginManager.getCurrentUserId(); messageBoardService.recordMessage(userId, message); return ServiceResponseBuilder.buildSuccess(null); } |
其中ServiceResponse是封裝的響應(yīng)報(bào)文VO,我們直接使用springmvc的@ResponseBody注解就好了。關(guān)鍵在于這個(gè)builder。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
import org.apache.commons.lang3.StringUtils; import com.niuxz.base.pojo.ServiceResponse; import com.niuxz.utils.spring.SpringContextUtil; import com.niuxz.web.server.token.TokenMapPoolBean; /** * * 類 名: ServiceResponseBuilder * * @version V1.0 * @date 2016年4月25日 * @author NiuXZ * */ public class ServiceResponseBuilder { /** * 構(gòu)建一個(gè)成功的響應(yīng)信息 * * @param body * @return 一個(gè)操作成功的 ServiceResponse */ public static ServiceResponse buildSuccess(Object body) { return new ServiceResponse( ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool")) .getToken(), "操作成功", body); } /** * 構(gòu)建一個(gè)成功的響應(yīng)信息 * * @param body * @return 一個(gè)操作成功的 ServiceResponse */ public static ServiceResponse buildSuccess(String token, Object body) { return new ServiceResponse(token, "操作成功", body); } /** * 構(gòu)建一個(gè)失敗的響應(yīng)信息 * * @param failCode * msg * @return 一個(gè)操作失敗的 ServiceResponse */ public static ServiceResponse buildFail(int failCode, String msg) { return buildFail(failCode, msg, null); } /** * 構(gòu)建一個(gè)失敗的響應(yīng)信息 * * @param failCode * msg body * @return 一個(gè)操作失敗的 ServiceResponse */ public static ServiceResponse buildFail(int failCode, String msg, Object body) { return new ServiceResponse( ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool")) .getToken(), failCode, StringUtils.isNotBlank(msg) ? msg : "操作失敗", body); } } |
由于使用的是靜態(tài)工具類的形式,不能通過spring注入tokenMapPool(token管理器)對(duì)象,則通過spring提供的api獲取。然后構(gòu)建響應(yīng)信息的時(shí)候直接調(diào)用tokenMapPool的getToken()方法,此方法會(huì)返回當(dāng)前線程綁定的token字符串。再次強(qiáng)調(diào)在請(qǐng)求結(jié)束后一定要手動(dòng)調(diào)用clear(我通過全局?jǐn)r截器調(diào)用)。
以上這篇模仿J2EE的session機(jī)制的App后端會(huì)話信息管理實(shí)例就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/niuxiaozu/archive/2017/11/23/7886600.html