前言: 在開發當中,經常會驗證用戶登錄狀態和獲取用戶信息。如果每次都手動調用用戶信息查詢介面,會非常的繁瑣,而且代碼冗餘。為了提高開發效率,因此就有了今天這篇文章。 思路: 用戶請求我們的方法會攜帶一個Token,通過Filter過濾器將會員信息查出來並放到request請求參數中。接著在Cotro ...
前言:
- 在開發當中,經常會驗證用戶登錄狀態和獲取用戶信息。如果每次都手動調用用戶信息查詢介面,會非常的繁瑣,而且代碼冗餘。為了提高開發效率,因此就有了今天這篇文章。
思路:
- 用戶請求我們的方法會攜帶一個Token,通過Filter過濾器將會員信息查出來並放到request請求參數中。接著在Cotroller層的請求方法中接收一個MemberDeatails類型的參數,就能直接獲得會員信息了。
詳細步驟:
1. Gradle引入需要的Jar包:
compile "com.fasterxml.jackson.core:jackson-databind:2.8.10"
2. 定義一個Login註解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {
String value() default "";
}
3. 定義一個MemberDetails.class,用於封裝用戶信息
public class MemberDetails {
private String memberId;
private String memberName;
private String memberNickname;
private String memberPhone;
private String memberEmail;
}
5. 定義一個會員介面類
/**
* @Author: XiongFeng
* @Description: 會員介面
* @Date: Created in 19:40 2018/4/10
*/
public interface MemberService {
/** 根據TokenId獲取用戶信息 */
MemberDto getMemberByToken(String token);
}
6. 定義一個會員介面實現類,在裡面寫上用戶信息獲取方法
@Service
public class MemberServiceImpl implements MemberService {
@Override
public MemberDetails getMemberDetailsByToken(String token) {
if (StringUtils.isBlank(token)) return null;
if (!"123".equals(token)) return null;
MemberDetails memberDetails = new MemberDetails();
memberDetails.setMemberId("123");
memberDetails.setMemberName("哈哈123");
memberDetails.setMemberEmail("[email protected]");
memberDetails.setMemberNickname("Seifon");
memberDetails.setMemberPhone("13100001111");
return memberDetails;
}
}
7. 定義一個Request請求包裝類
- 通過繼承HttpServletRequestWrapper類,重寫它裡面的多個方法,對前端傳過來的參數進行重新封裝。因為在Filter,雖然可以通過request.getParameterMap()拿到一個含有參數的map,但是不能直接對request裡面東西進行修改操作,一旦重新修改,就會報錯。後來我發現j2ee已經給我們提供瞭解決的辦法,使用HttpServletRequestWrapper類來解決向request添加額外參數的功能。於是我對HttpServletRequest進行重新包裝,在裡面重新定義一個map,將以前的參數put進去,並將我們需要添加的參數放進去,達到我們想要的效果。
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
/**
* @Author: XiongFeng
* @Description: 對Request請求重新包裝
* @Date: Created in 11:17 2018/4/13
*/
public class ParameterRequestWrapper extends HttpServletRequestWrapper {
private Map<String , String[]> params = new HashMap<String, String[]>();
@SuppressWarnings("unchecked")
public ParameterRequestWrapper(HttpServletRequest request) {
// 將request交給父類,以便於調用對應方法的時候,將其輸出,其實父親類的實現方式和第一種new的方式類似
super(request);
//將參數表,賦予給當前的Map以便於持有request中的參數
this.params.putAll(request.getParameterMap());
}
//重載一個構造方法
public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) {
this(request);
addAllParameters(extendParams);//這裡將擴展參數寫入參數表
}
/**
* 覆寫獲取key的方法
*/
@Override
public Enumeration getParameterNames() {
Vector names = new Vector(params.keySet());
return names.elements();
}
/**
* 覆寫獲取值value的方法
*/
@Override
public String getParameter(String name) {
Object v = params.get(name);
if (v == null) {
return null;
} else if (v instanceof String[]) {
String[] strArr = (String[]) v;
if (strArr.length > 0) {
return strArr[0];
} else {
return null;
}
} else if (v instanceof String) {
return (String) v;
} else {
return v.toString();
}
}
@Override
public String[] getParameterValues(String name) {
Object v = params.get(name);
if (v == null) {
return null;
} else if (v instanceof String[]) {
return (String[]) v;
} else if (v instanceof String) {
return new String[] { (String) v };
} else {
return new String[] { v.toString() };
}
}
public void addAllParameters(Map<String , Object>otherParams) {//增加多個參數
for(Map.Entry<String , Object>entry : otherParams.entrySet()) {
addParameter(entry.getKey() , entry.getValue());
}
}
public void addParameter(String name , Object value) {//增加參數
if(value != null) {
if(value instanceof String[]) {
params.put(name , (String[])value);
}else if(value instanceof String) {
params.put(name , new String[] {(String)value});
}else {
params.put(name , new String[] {String.valueOf(value)});
}
}
}
}
8. 定義一個過濾器
- 在這個過濾裡面,主要校驗Token是否有效以及將會員信息添加到request。首先,從Request請求頭中拿到前端傳過來的Token,並使用Token調用會員信息獲取介面,得到用戶的資料,然後將用戶信息put到ParameterMap中,這個ParameterMap是我們通過ParameterRequestWrapper重新包裝的一個map,因此可以在裡面添加會員的參數,然後將新的request傳遞出去。
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: XiongFeng
* @Description: 會員登錄信息過濾器
* @Date: Created in 11:17 2018/4/13
*/
@Component
@WebFilter(urlPatterns = "/*")
public class MemberFilter implements Filter {
MemberService memberService = new MemberServiceImpl();
ObjectMapper objectMapper = new ObjectMapper();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String tokenId = req.getHeader("X-Authorization");
if (tokenId == null || "".equals(tokenId) || tokenId.isEmpty()) {
chain.doFilter(request, response);
return;
}
MemberDetails memberDetails = memberService.getMemberDetailsByToken(tokenId);
if (memberDetails == null) {
try {
respFail(response);
return;
} catch (Exception e) {
throw new ServletException();
}
}
Map<String, String[]> parameterMap = new HashMap<String, String[]>(request.getParameterMap());
parameterMap.put("memberId", new String[]{memberDetails.getMemberId()});
parameterMap.put("memberName", new String[]{memberDetails.getMemberName()});
parameterMap.put("memberEmail", new String[]{memberDetails.getMemberEmail()});
parameterMap.put("memberNickname", new String[]{memberDetails.getMemberNickname()});
parameterMap.put("memberPhone", new String[]{memberDetails.getMemberPhone()});
chain.doFilter(new ParameterRequestWrapper((HttpServletRequest) req, parameterMap), response);
}
/** 返回失敗結果Json數據 */
private void respFail(ServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("status", 500);
map.put("message", "登錄失效,請登錄");
map.put("data", null);
String s = objectMapper.writeValueAsString(map);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(s);
}
@Override
public void destroy() {
}
}
9. 定義一個SpringMVC攔截器
- 在這個攔截器裡面,主要驗證Controller方法中是否需要MemberDetails和是否標了@Login註解。首先,從HandlerMethod中獲取所有入參,看有沒有需要MemberDetails參數,如果有,就從HttpServletRequest中拿memberId,如果不存在說明沒有登錄,存在就通過。然後HandlerMethod獲取@Login註解,判斷是否存在,如果存在,就看有沒有memberId,沒有就不通過。
package cn.seifon.paymodle.interceptor;
import cn.seifon.paymodle.annotations.Login;
import cn.seifon.paymodle.dto.MemberDetails;
import cn.seifon.paymodle.service.manager.member.MemberService;
import cn.seifon.paymodle.service.manager.member.impl.MemberServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: XiongFeng
* @Description: 會員登錄信息攔截器
* @Date: Created in 11:17 2018/4/13
*/
public class MemberInterceptor extends HandlerInterceptorAdapter {
ObjectMapper objectMapper = new ObjectMapper();
MemberService memberService = new MemberServiceImpl();
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod method = (HandlerMethod) handler;
String[] memberIds = request.getParameterValues("memberId");
MethodParameter[] methodParameters = method.getMethodParameters();
//判斷方法類是否有MemberDetails入參
if (methodParameters.length > 0) {
for (MethodParameter methodParameter : methodParameters) {
Type genericParameterType = methodParameter.getGenericParameterType();
String typeName = genericParameterType.getTypeName();
if (!typeName.equals(MemberDetails.class.getTypeName())) continue;
if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用戶信息就返回失敗
break;
}
}
//判斷是否有Login註解
Login login = method.getMethodAnnotation(Login.class);
if (login == null) return true;
if (memberIds == null || memberIds.length <= 0) return this.respFail(response); //如果找不到用戶信息就返回失敗
return true;
}
/** 返回失敗結果Json數據 */
private boolean respFail(HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("status", 500);
map.put("message", "登錄失效,請登錄");
map.put("data", null);
String s = objectMapper.writeValueAsString(map);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(s);
return false;
}
}
10. 將攔截器註冊到WebMvcConfigurer中
@Configuration
public class MyWebAppConfigurer
extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns 用於添加攔截規則
registry.addInterceptor(new MemberInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
11. 定義會員Controller
- 經過一個過濾器和一個攔截器,request請求終於來到了我們Controller層。這時候,我們只需要在方法裡面寫入MemberDetails memberDetails 就OK了,不用做任何操作,我們就可以獲取會員信息了,是不是炒雞方便!另外還可以在方法上標@Login註解。
@RestController
public class MemberController {
@RequestMapping("/token")
@Login
public Map<String, Object> getUser(MemberDetails memberDetails) {
//User user = userManager.selectByPrimaryKey(id);
Map<String, Object> map = new LinkedHashMap<>();
map.put("status", 200);
map.put("message", "請求成功");
map.put("data", memberDetails);
return map;
}
}
運行結果:
遇到的坑:
當時我嘗試過把會員參數放到session域中的Attribute,也嘗試過在Model里setAttribute。後來發現這是行不通的,在filter中直接使用request.setAttribute()是無效的。放在Modle也是可行,但是Controller裡面的方法需要加@ModelAttribute("...")才能得到用戶信息,很不方便。唯有通過request.getParameterMap() put()進去,才是最方便的。
一開始我沒想到用過濾器,因此我就嘗試在攔截器里,直接通過ParameterRequestWrapper對request包裝,後來發現不管我怎麼弄都不成功。當時非常絕望,後來想了想會不會是攔截器不支持重新包裝request,於是我就通過filter去做,沒想到成功了。這時,我想既然用到了filter,那乾脆直接在filter裡面獲取@Login註解和獲取方法參數得了,後來發現filter裡面拿不到方法的信息,哭。後來想到一個辦法,可以通過先filter,後攔截器。於是就成功了!
後記:
- 這篇文章只是記錄了我的一點小小經驗,如果有什麼不對的地方或者有更好的方法,請大家在評論里留言指正!
參考文章:http://www.importnew.com/19023.html