自定義註解使用實現登入AOP
阿新 • • 發佈:2018-12-09
分散式商城redis應用,實現單點登入和購物車功能
單點登入+自定義註解+AOP
技術點:
redis+jsonp+cookie實現分散式系統內部的單點登入
抽取公共的js
$(function () { $.ajax({ //url進行遠端訪問,跨域限制@CrossOriginal無法攜帶cookie所以只能通過jsonp實現 url:'http://localhost:8085/sso/isLogin', // 後臺返回的是一個js函式 dataType:'jsonp', method:'post' }); }); //直接呼叫取出data即可 function isLogin(data) { //將json字串轉成json物件 var json = eval('(' + data + ')'); if(data != null){ $("#pid").html("您好"+json.username+",歡迎來到<b><a href=\"/\">ShopCZ商城</a></b> <a href=\"http://localhost:8085/sso/logout\" >登出</a>"); }else{ $("#pid").html("[<a href=\"javascript:toLogin()\" >登入</a>][<a >註冊</a>]"); } } /** * 在跳轉前獲取頁面url * location.href可以直接取值 * encodeURI()可以對位址列的中文進行轉碼 * replace解決拼接多個引數的問題 */ function toLogin(){ //獲取本地url var localUrl = location.href; //對url進行編碼 localUrl = encodeURI(localUrl); //&拼接需要保證為一個引數需要手動編碼 localUrl = localUrl.replace("&","%26"); location.href = "http://localhost:8085/sso/toLogin?returnUrl=" + localUrl; }
redis+cookie實現登入
/** * redis+jsonp+cookie實現分散式系統內部的單點登入 * 如果是多個系統無法通過cookie實現 * @Author 許恆亮 * @Time 2018/11/27 11:17 * @Version 1.0 */ Controller @RequestMapping("/sso") public class SSOController { @Reference private IUserService userService; @Autowired private RedisTemplate redisTemplate; /** * returnUrl是跳轉到登入頁面前的頁面url,帶引數 * @param returnUrl * @param model * @return */ @RequestMapping("/toLogin") public String toLogin(String returnUrl,Model model){ model.addAttribute("returnUrl",returnUrl); return "login"; } /** * 登入功能 * 知識點: * 1.ResultData在Service層對不同的結果設定不同的狀態碼自定義規則和錯誤資訊 * 2.returnUrl在跳轉到登入頁面的時候將本地的url通過位址列帶引數的方式帶到登入頁面 * 然後作為隱藏域引數新增到登入後的頁面跳轉redirect+returnUrl實現對不同頁面登入響應不同的結果 * 同時對位址列進行編碼,js解決中文亂碼問題,encodeUri(returnUrl引數) * 如果帶有多個引數&需要手動進行替換 replace("&","%26")只有這樣才能將url整個作為一個引數進行傳遞後跳轉頁面 * @param username * @param password * @param model * @param response * @param returnUrl * @return */ @RequestMapping("/login") public String login(String username, String password, Model model, HttpServletResponse response,String returnUrl){ //登陸 ResultData<User> resultData = userService.login(username, password); switch (resultData.getCode()){ case 200: //登陸成功 if(returnUrl == null || "".equals(returnUrl)){ returnUrl = "http://localhost:8082"; } //將使用者資訊放到redis中 redisTemplate.opsForValue().set(Constant.LOGIN_TOKEN,resultData.getData()); //將token寫到客戶端cookie中 Cookie cookie = new Cookie("login_token",Constant.LOGIN_TOKEN); cookie.setMaxAge(60 * 60 * 24 * 7);//設定過期時間 cookie.setPath("/");//設定cookie的有效路徑 // cookie.setDomain();//設定cookie的有效域名,可以寫二級域名,例如jd.com // cookie.setHttpOnly();//設定cookie是否能被script等指令碼訪問 // cookie.setSecure();//設定cookie是否只支援https response.addCookie(cookie); model.addAttribute("user",resultData.getData()); return "redirect:" + returnUrl; default: //登入失敗 model.addAttribute("error",resultData.getMessage()); return "login"; } } /** * 驗證是否登陸成功 * 只有jsonp才能帶cookie,所以不能使用 @CrossOrigin * @param token * @return */ @RequestMapping("/isLogin") @ResponseBody public String checkLogin(@CookieValue(value = Constant.LOGIN_TOKEN,required = false) String token){ User user = null; if(token != null){ user = (User)redisTemplate.opsForValue().get(token); } return user != null ? "isLogin('" + new Gson().toJson(user) + "')" : "isLogin(null)"; } /** * 登出功能 * 需要注意:把redis快取中的資料情況還有cookie設為時間為0 * @param token * @param response * @return */ @RequestMapping("/logout") public String logout(@CookieValue(value = Constant.LOGIN_TOKEN,required = false) String token, HttpServletResponse response){ if(token != null){ //清空redis redisTemplate.delete(token); //刪除cookie Cookie cookie = new Cookie(Constant.LOGIN_TOKEN, null); cookie.setMaxAge(0); //cookie可以重名,只要path不同,這時候是不同的cookie只通過名字是不能覆蓋的 cookie.setPath("/"); response.addCookie(cookie); } return "login"; } }
購物車實現
技術點:
AOP+自定義註解+redis+cookie
controller
@Controller @RequestMapping("/cart") public class CartController { @IsLogin//預設不需要強制登入 @RequestMapping("/addCart") public String addCart(Cart cart, User user){ System.out.println(cart); System.out.println(user); return "success"; } }
自定義註解
/**
*
* 自定義註解
* 註解的宣告:public @interface 註解名稱
*
* 元註解:標記註解的註解
* @Documented:表示該註解會被javadoc命令寫入api文件中
* @Target:註解的標記位置
* ElementType.ANNOTATION_TYPE:該註解可以標記別的註解
* ElementType.CONSTRUCTOR:標註到構造方法
* ElementType.FIELD:標註到成員屬性
* ElementType.LOCAL_VARIABLE:標註到區域性變數
* ElementType.METHOD:標註到方法上
* ElementType.PACKAGE:標註到包上
* ElementType.PARAMETER:標註到方法形參上
* ElementType.TYPE:標註到類、介面、列舉類上
* @Retention:註解的作用範圍
* RetentionPolicy.SOURCE:註解的有效範圍只在原始碼中,編譯後就被丟棄
* RetentionPolicy.CLASS:註解有效範圍在編譯檔案中,執行時丟棄
* RetentionPolicy.RUNTIME:註解在執行時仍然有效,這個範圍的註解可以通過反射獲取
*
* 註解內的方法宣告:
* 型別 方法名() [defualt 預設值];
*
* 注意:
* 如果一個屬性沒有設定default預設值,在標記這個註解時,必須給定該屬性值
* 如果一個屬性的名字為value,則在賦值時可以省略屬性名。當如果需要賦值兩個以上的屬性,則value不能省略
* 如果一個屬性的型別是陣列型別,則應該用{}賦值,如果只要給一個值,{}可以省略
*/
/**
* aop 實現
* @Author 許恆亮
* @Time 2018/11/27 16:12
* @Version 1.0
*/
@Target(ElementType.METHOD)//作用範圍,方法上
@Retention(RetentionPolicy.RUNTIME)//執行時
public @interface IsLogin {
boolean value() default false;//是否強制需要登入
}
切面Aspect
/**
* @Author 許恆亮
* @Time 2018/11/27 16:17
* @Version 1.0
*/
@Aspect
public class LoginAspect {
@Autowired
private RedisTemplate redisTemplate;
/**
* 環繞增強
* 判斷controller的方法上有沒有@IsLogin註解
* 如果有就對其進行增強
* 增強效果
* 1/給方法的形參列表User user 注入值
* 如果登入了就注入user
* 如果沒登入就注入null
* 2.如果IsLogin的value=false表示不強制跳轉到登入頁面
* 3.如果IsLogin的value=true表示強制跳轉到登入頁面,一旦發現cookie中沒有值直接跳轉不允許執行目標方法
*/
@Around("execution(* *..*Controller.*(..)) && @annotation(IsLogin)")
//表示式代表所有路徑下面的xxxController類中的方法帶@IsLogin註解的
public Object isLogin(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//獲得request請求
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//通過reuqest獲得cookie
Cookie[] cookies = request.getCookies();
//迴圈Cookie
String token = null;
if(cookies != null){
for (int i = 0; i < cookies.length; i++) {
//找到令牌對應的Cookie
if(cookies[i].getName().equals(Constant.LOGIN_TOKEN)){
//找到Key
token = cookies[i].getValue();
break;
}
}
}
User user = null;
if(token != null && !"".equals(token)){
//通過key去redis中找到使用者資訊
//有可能已經登入了
user = (User) redisTemplate.opsForValue().get(token);
}
if(user == null){
//沒有登入---獲得註解上的屬性 判斷返回情況 true就直接跳轉到登入頁面
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();//獲得方法物件
IsLogin isLoginAnnotation = method.getAnnotation(IsLogin.class);//isLogin註解物件
boolean flag = isLoginAnnotation.value();//獲得isLogin註解的Value值
if(flag){//true直接跳轉到登入頁面
//因為登入後需要帶returnUrl跳轉到之前的頁面,所以需要通過request物件獲得url
StringBuffer requestURL = request.getRequestURL();
//帶引數
requestURL.append(request.getQueryString());
//解決中文亂碼問題
String url = URLEncoder.encode(requestURL.toString(), "utf-8");
//同樣解決&帶引數當做一個引數的問題
url = url.replace("&","%26");
return "redirect:http://localhost:8085/sso/toLogin?returnUrl=" + url;
}
//false繼續執行目標方法
}
//表示已經登入或者不需要登入就可以繼續操作
//獲得形參列表
Object[] args = proceedingJoinPoint.getArgs();
if(args != null){
for (int i = 0; i < args.length; i++) {
if(args[i].getClass() == User.class){
//找到形參列表上的User user,並將從redis查到的user新增到形參列表
args[i] = user;
break;
}
}
}
//執行目標方法 --- 帶指定的形參列表
Object result = proceedingJoinPoint.proceed(args);
//執行目標方法後的方法返回值就是目標方法的返回值
return result;
}
}
註冊切面
@Bean//註冊切面
public LoginAspect loginAspect(){
return new LoginAspect();
}