1. 程式人生 > >自定義註解使用實現登入AOP

自定義註解使用實現登入AOP

分散式商城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();
    }