目錄

登錄接口説明:LoginController

一、獲取圖形驗證碼接口

controller層:

service層:

二、登錄接口

controller層:

service層:

mapper層:

三、JwtUtil創建

四、獲取登陸用户個人信息接口

controller層:

(重要)因為我們解析token的方法是在攔截器裏調用,所以如果我們每一次獲取登錄者的時候,要獲取token進行解析,連續調用兩次parseToken()方法就不太合理了。所以為避免重複解析,通常會在攔截器將Token解析完畢後,將結果保存至ThreadLocal中,這樣一來,我們便可以在整個請求的處理流程中進行訪問了。

修改攔截器:

service層:


登錄接口説明:LoginController

@Tag(name = "後台管理系統登錄管理")
@RestController
@RequestMapping("/admin")
public class LoginController {

    @Autowired
    private LoginService service;
}

一、獲取圖形驗證碼接口

接口名稱:getCaptcha

請求方式:Get

請求路徑:/admin/login/captcha

請求參數:無

返回類型:CaptchaVo

我們先查看CaptchaVo有哪些屬性:

@Data
@Schema(description = "圖像驗證碼")
@AllArgsConstructor
public class CaptchaVo {

    @Schema(description="驗證碼圖片信息")
    private String image;

    @Schema(description="驗證碼key")
    private String key;
}

需要獲取驗證碼圖片的信息和存儲在redis裏的key。那麼怎麼生成驗證碼呢?我們需要使用開源的驗證碼生成工具EasyCaptcha。

導入maven依賴:

<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
</dependency>

controller層:

@Operation(summary = "獲取圖形驗證碼")
    @GetMapping("login/captcha")
    public Result<CaptchaVo> getCaptcha() {
        CaptchaVo result= service.getCaptcha();
        return Result.ok(result);
    }

service層:

public CaptchaVo getCaptcha() {
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);  //分別設置驗證碼區域的長、寬、以及驗證碼長度。
        String code = specCaptcha.text().toLowerCase();  //把驗證碼轉成小寫
        String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID(); //遵循命名規範

        stringRedisTemplate.opsForValue().set(key,code,RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS);  //把key和驗證碼值存入redis
        return new CaptchaVo(specCaptcha.toBase64(),key);
    }

本項目Reids中的key需遵循以下命名規範:項目名:功能模塊名:其他,例如admin:login:123456


二、登錄接口

接口名稱:login

請求方式:Post

請求路徑:/admin/login

請求參數:@RequestBody LoginVo loginVo

返回類型:Result<String>

controller層:

@Operation(summary = "登錄")
    @PostMapping("login")
    public Result<String> login(@RequestBody LoginVo loginVo) {
        String jwt=service.login(loginVo);
        return Result.ok(jwt);
    }

loginVo用來封裝前端登錄界面用户提交的用户名、密碼、驗證碼以及驗證碼在redis的key

@Data
@Schema(description = "後台管理系統登錄信息")
public class LoginVo {

    @Schema(description="用户名")
    private String username;

    @Schema(description="密碼")
    private String password;

    @Schema(description="驗證碼key")
    private String captchaKey;

    @Schema(description="驗證碼code")
    private String captchaCode;
}

service層:

public String login(LoginVo loginVo) {
        if (loginVo.getCaptchaCode()==null){  //判斷輸入的驗證碼是否為空
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);
        }

        String code = stringRedisTemplate.opsForValue().get(loginVo.getCaptchaKey());  //從redis中獲取code
        if (code==null){  //判斷查詢的code是否為空
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);
        }
        if (!code.equals(loginVo.getCaptchaCode().toLowerCase())){  //如果code與輸入的code的小寫相等
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);
        }
        SystemUser systemUser = systemUserMapper.selectOneByUsername(loginVo.getUsername());  //通過用户名查詢用户
        if (systemUser==null){
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST_ERROR);
        }
        if (systemUser.getStatus()== BaseStatus.DISABLE){
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);
        }
        if (!systemUser.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword()))){
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);
        }
        return JwtUtil.createToken(systemUser.getId(),systemUser.getUsername());  //返回token
    }

mapper層:

自定義通過用户名字查詢用户的方法。

<select id="selectOneByUsername" resultType="com.atguigu.lease.model.entity.SystemUser">
        select id,
               username,
               password,
               name,
               type,
               phone,
               avatar_url,
               additional_info,
               post_id,
               status
        from system_user
        where is_deleted = 0
          and username = #{username}
    </select>

三、JwtUtil創建

JwtUtil是用來生成token來識別登錄的用户是否合法,如果合法則從數據庫中查詢用户信息,並響應給前端。而且後續前端想要請求後端接口也需要解析攜帶的token。

導入依賴:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <scope>runtime</scope>
</dependency>
public class JwtUtil {

    private static long tokenExpiration = 60 * 60 * 1000L;
    private static SecretKey tokenSignKey = Keys.hmacShaKeyFor("M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ".getBytes());

    /**
     * 創建token方法
     * @param userId
     * @param username
     * @return
     */
    public static String createToken(Long userId, String username) {
        String token = Jwts.builder().
                setSubject("LOGIN_USER").
                setExpiration(new Date(System.currentTimeMillis() + 3600000)).
                claim("userId", userId).
                claim("username", username).
                signWith(tokenSignKey, SignatureAlgorithm.HS256).
                compact();
        return token;
    }

    /**
     * 解析token方法
     * @param token
     * @return
     */
    public static Claims parseToken(String token){
        if (token==null){
            throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
        }

        try {
            JwtParser jwtParser = Jwts.parser().setSigningKey(tokenSignKey)
                    .build();
            Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return claims;
        }catch (ExpiredJwtException e){
            throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);
        }catch (JwtException e){
            throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);
        }

    }

四、獲取登陸用户個人信息接口

接口名稱:info

請求方式:Get

請求路徑:/admin/info

請求參數:無

返回類型:Result<SystemUserInfoVo>

controller層:

返回的類型為VO對象,需要自己定義查詢方法。

(重要)因為我們解析token的方法是在攔截器裏調用,所以如果我們每一次獲取登錄者的時候,要獲取token進行解析,連續調用兩次parseToken()方法就不太合理了。所以為避免重複解析,通常會在攔截器將Token解析完畢後,將結果保存至ThreadLocal中,這樣一來,我們便可以在整個請求的處理流程中進行訪問了。

ThreadLocal的主要作用是為每個使用它的線程提供一個獨立的變量副本,使每個線程都可以操作自己的變量,而不會互相干擾,其用法如下圖所示。

畢業設計-springboot+vue公租房租賃管理系統_wx64786b01a48a2的技術博客_#JWT

每個ThreadLocal對象都擁有set、get、remove方法,我們只需要編寫邏輯調用即可自定義存儲、獲取、清理本地線程對象的方法。

public class LoginUserHolder {
    public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();  //創建本地線程對象

    public static void setLoginUser(LoginUser loginUser) {
        threadLocal.set(loginUser);   //把登錄用户信息存儲進本地線程對象
    }

    public static LoginUser getLoginUser() {
        return threadLocal.get();  //獲取登錄用户信息
    }

    public static void clear() {
        threadLocal.remove();  //清理存儲的用户信息
    }
}
@Operation(summary = "獲取登錄用户個人信息")
    @GetMapping("info")
    public Result<SystemUserInfoVo> info() {
        Long userId = LoginUserHolder.getLoginUser().getUserId();
        SystemUserInfoVo systemUserInfoVo=service.getLoginUserInfoById(userId);
        return Result.ok(systemUserInfoVo);
    }

修改攔截器:

@Component
public class AuthenticationIntercepter implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("access-token");
        Claims claims = JwtUtil.parseToken(token);
        Long userId = claims.get("userId", Long.class);
        String username = claims.get("username", String.class);
        LoginUserHolder.setLoginUser(new LoginUser(userId, username));  //封裝登錄對象存儲進本地線程對象
        return true;
    }



    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LoginUserHolder.clear();  //清理本地線程方法
    }
}

service層:

public SystemUserInfoVo getLoginUserInfoById(Long userId) {
        SystemUser systemUser = systemUserMapper.selectById(userId);
        SystemUserInfoVo systemUserInfoVo = new SystemUserInfoVo();
        systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());
        systemUserInfoVo.setName(systemUser.getName());
        return systemUserInfoVo;
    }