Browse Source

0608 登录接口和jwt

Qing 11 tháng trước cách đây
mục cha
commit
632bb5b212

+ 19 - 0
novel-demo/pom.xml

@@ -16,6 +16,7 @@
     <description>novel-demo</description>
     <properties>
         <java.version>17</java.version>
+        <jjwt.version>0.11.5</jjwt.version>
     </properties>
     <dependencies>
         <dependency>
@@ -97,6 +98,24 @@
         </dependency>
 
 
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>${jjwt.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>${jjwt.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>${jjwt.version}</version>
+            <scope>runtime</scope>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 38 - 0
novel-demo/src/main/java/com/sf/config/TokenInterceptor.java

@@ -0,0 +1,38 @@
+package com.sf.config;
+
+import com.sf.util.JwtUtils;
+import com.sf.util.UserHolder;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+// 拦截器  要实现HandlerInterceptor接口
+// 要作为spring容器的组件
+@Component
+public class TokenInterceptor implements HandlerInterceptor {
+
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    // controller执行之前
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        String token = request.getHeader("Authorization");
+        if (token != null) {
+            Long uid = jwtUtils.parseToken(token, "front");
+            System.out.println("用拦截器解析uid: " + uid);
+            UserHolder.setUserId(uid);
+        }
+        return HandlerInterceptor.super.preHandle(request, response, handler);
+    }
+
+    // controller执行之后
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        System.out.println("controller执行完成");
+        UserHolder.clearUserId();
+        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
+    }
+}

+ 20 - 0
novel-demo/src/main/java/com/sf/config/WebConfig.java

@@ -0,0 +1,20 @@
+package com.sf.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+// web相关的配置 需要实现WebMvcConfigurer接口
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    @Autowired
+    private TokenInterceptor tokenInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+//        WebMvcConfigurer.super.addInterceptors(registry);
+        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
+    }
+}

+ 3 - 0
novel-demo/src/main/java/com/sf/controller/BookChapterController.java

@@ -3,6 +3,7 @@ package com.sf.controller;
 import com.sf.dto.RestResp;
 import com.sf.dto.resp.BookChapterRespDto;
 import com.sf.service.IBookChapterService;
+import com.sf.util.UserHolder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -30,6 +31,8 @@ public class BookChapterController {
     // @RequestParam注解 用于从请求参数中获取参数
     @GetMapping("/api/front/book/chapter/list")
     public RestResp<List<BookChapterRespDto>> listChapters(@RequestParam("bookId") Long bookId){
+        Long userId = UserHolder.getUserId();
+        System.out.println("UserHolder uid: " + userId);
         // 根据传进来的参数 bookId 去查询对应书籍的章节信息
         List<BookChapterRespDto> chapterRespDtos = bookChapterService.getBookChapterByBookId(bookId);
         return RestResp.ok(chapterRespDtos);

+ 20 - 2
novel-demo/src/main/java/com/sf/controller/UserInfoController.java

@@ -1,8 +1,10 @@
 package com.sf.controller;
 
 import com.sf.dto.RestResp;
+import com.sf.dto.req.UserLoginReqDto;
 import com.sf.dto.req.UserRegisterReqDto;
 import com.sf.dto.resp.ImgVerifyCodeRespDto;
+import com.sf.dto.resp.UserLoginRespDto;
 import com.sf.dto.resp.UserRegisterRespDto;
 import com.sf.service.IUserInfoService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -32,19 +34,35 @@ public class UserInfoController {
         return RestResp.ok(imgVerifyCodeRespDto);
     }
 
+    // @RequestBody 代表接受的请求参数  是json结构的对象
+    // 和@ResponseBody 相对应
     @PostMapping("/api/front/user/register")
     public RestResp<UserRegisterRespDto> register(@RequestBody UserRegisterReqDto userRegisterReqDto) {
         UserRegisterRespDto respDto = userInfoService.register(userRegisterReqDto);
         if (respDto.getType() == 1) {
             // 返回错误的信息
-            return RestResp.fail("00001","验证码错误!");
+            return RestResp.fail("00001", "验证码错误!");
         }
 
         if (respDto.getType() == 2) {
             // 返回错误的信息
-            return RestResp.fail("00002","用户名重复了!");
+            return RestResp.fail("00002", "用户名重复了!");
         }
 
         return RestResp.ok(respDto);
     }
+
+    // http://127.0.0.1:8888/api/front/user/login
+    @PostMapping("/api/front/user/login")
+    public RestResp<UserLoginRespDto> login(@RequestBody UserLoginReqDto userLoginReqDto) {
+        UserLoginRespDto userLoginRespDto = userInfoService.login(userLoginReqDto);
+        if (userLoginRespDto.getType() == 1) {
+            return RestResp.fail("00003", "用户名不存在!");
+        }
+        if (userLoginRespDto.getType() == 2) {
+            return RestResp.fail("00004", "密码错误");
+        }
+        return RestResp.ok(userLoginRespDto);
+    }
+
 }

+ 17 - 0
novel-demo/src/main/java/com/sf/dto/req/UserLoginReqDto.java

@@ -0,0 +1,17 @@
+package com.sf.dto.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+// 用户登录接口的请求dto
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserLoginReqDto {
+
+    private String username;
+    private String password;
+}

+ 3 - 0
novel-demo/src/main/java/com/sf/dto/req/UserRegisterReqDto.java

@@ -14,6 +14,9 @@ import lombok.Data;
 @Data
 public class UserRegisterReqDto {
 
+    //  @Schema 是对字段的解释说明
+    //  @NotBlank  是字段的非空校验
+    //  @Pattern  通过表达式来识别符合手机号的格式
     @Schema(description = "手机号", required = true)
     @NotBlank(message="手机号不能为空!")
     @Pattern(regexp="^1[3|4|5|6|7|8|9][0-9]{9}$",message="手机号格式不正确!")

+ 19 - 0
novel-demo/src/main/java/com/sf/dto/resp/UserLoginRespDto.java

@@ -0,0 +1,19 @@
+package com.sf.dto.resp;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+// 用户登录接口的响应dto
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserLoginRespDto {
+
+    private Long uid;
+    private String nickName;
+    private String token;
+    private Integer type;
+}

+ 4 - 0
novel-demo/src/main/java/com/sf/service/IUserInfoService.java

@@ -1,7 +1,9 @@
 package com.sf.service;
 
+import com.sf.dto.req.UserLoginReqDto;
 import com.sf.dto.req.UserRegisterReqDto;
 import com.sf.dto.resp.ImgVerifyCodeRespDto;
+import com.sf.dto.resp.UserLoginRespDto;
 import com.sf.dto.resp.UserRegisterRespDto;
 import com.sf.entity.UserInfo;
 import com.baomidou.mybatisplus.extension.service.IService;
@@ -20,4 +22,6 @@ public interface IUserInfoService extends IService<UserInfo> {
 
     // req -> request 请求  resp -> response 响应
     UserRegisterRespDto register(UserRegisterReqDto userRegisterReqDto);
+
+    UserLoginRespDto login(UserLoginReqDto userLoginReqDto);
 }

+ 43 - 2
novel-demo/src/main/java/com/sf/service/impl/UserInfoServiceImpl.java

@@ -2,14 +2,17 @@ package com.sf.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.sf.dto.req.UserLoginReqDto;
 import com.sf.dto.req.UserRegisterReqDto;
 import com.sf.dto.resp.ImgVerifyCodeRespDto;
+import com.sf.dto.resp.UserLoginRespDto;
 import com.sf.dto.resp.UserRegisterRespDto;
 import com.sf.entity.UserInfo;
 import com.sf.mapper.UserInfoMapper;
 import com.sf.service.IUserInfoService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.sf.util.CaptchaUtils;
+import com.sf.util.JwtUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.util.Pair;
 import org.springframework.stereotype.Service;
@@ -35,6 +38,9 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
     @Autowired
     private UserInfoMapper userInfoMapper;
 
+    @Autowired
+    private JwtUtils jwtUtils;
+
 
     @Override
     public ImgVerifyCodeRespDto getImgVerifyCode() {
@@ -84,7 +90,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
                 .nickName(reqDto.getUsername())
                 .salt("0")
                 .accountBalance(0L)
-                .status((byte)0)
+                .status((byte) 0)
                 .createTime(LocalDateTime.now())  // 把当前时间存储进去
                 .updateTime(LocalDateTime.now())
                 .build();
@@ -93,7 +99,42 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         respDto.setType(0);
         // 在存入数据库后 获取生成的id
         respDto.setUid(userInfo.getId());
-        respDto.setToken("token_" + userInfo.getId());
+//        respDto.setToken("token_" + userInfo.getId());
+        respDto.setToken(jwtUtils.generateToken(userInfo.getId(),"front"));
         return respDto;
     }
+
+    @Override
+    public UserLoginRespDto login(UserLoginReqDto userLoginReqDto) {
+        // 判断用户名和密码是否正确
+        // select * from user_info where username='' and password = ''
+        // 可以一起查询  只返回用户名或密码错误
+        // 也可以分开处理  返回用户名错误 还是密码错误
+        // select * from user_info where username=''
+
+        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserInfo::getUsername, userLoginReqDto.getUsername());
+        // 如果有一条 使用selectOne
+        // 如果有多条 使用selectList
+        // 如果只想查询个数 使用selectCount
+        UserInfo userInfo = userInfoMapper.selectOne(queryWrapper);
+        if (userInfo == null) {
+            // 用户名不存在
+            return UserLoginRespDto.builder().type(1).build();
+        }
+
+        String pwd = DigestUtils.md5DigestAsHex(userLoginReqDto.getPassword().getBytes(StandardCharsets.UTF_8));
+        if (!userInfo.getPassword().equals(pwd)) {
+            // 密码错误
+            return UserLoginRespDto.builder().type(2).build();
+        }
+
+        // 登录成功
+//        String token = "token_" + userInfo.getId();
+        String token = jwtUtils.generateToken(userInfo.getId(),"front");
+        return UserLoginRespDto.builder().type(0)
+                .uid(userInfo.getId())
+                .nickName(userInfo.getNickName())
+                .token(token).build();
+    }
 }

+ 38 - 0
novel-demo/src/main/java/com/sf/util/JwtUtils.java

@@ -0,0 +1,38 @@
+package com.sf.util;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+// 声明为组件
+@Component
+public class JwtUtils {
+    // @Value 获取配置文件中的值
+    @Value("${novel.jwt.secret}")
+    private String secret;
+
+    public String generateToken(Long uid, String systemKey) {
+        // systemKey是当前系统的标识
+        String token = Jwts.builder()
+                .setHeaderParam("systemKeyHeader", systemKey)
+                .setSubject(uid.toString())
+                .signWith(Keys.hmacShaKeyFor(secret.getBytes())).compact();
+        return token;
+    }
+
+
+    public Long parseToken(String token, String systemKey) {
+        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(secret.getBytes()))
+                .build().parseClaimsJws(token);
+        // 验证是否是当前系统加密的token
+        if (systemKey.equals(claimsJws.getHeader().get("systemKeyHeader"))) {
+            return Long.parseLong(claimsJws.getBody().getSubject());
+        }
+        return null;
+    }
+
+}

+ 20 - 0
novel-demo/src/main/java/com/sf/util/UserHolder.java

@@ -0,0 +1,20 @@
+package com.sf.util;
+
+// 用户上下文
+public class UserHolder {
+
+    // 不同的线程存储不同的数据
+    private static final ThreadLocal<Long> userHolder = new ThreadLocal<Long>();
+
+    public static void setUserId(Long userId) {
+        userHolder.set(userId);
+    }
+
+    public static Long getUserId() {
+        return userHolder.get();
+    }
+
+    public static void clearUserId() {
+        userHolder.remove();
+    }
+}

+ 2 - 0
novel-demo/src/main/resources/application.yml

@@ -5,6 +5,8 @@ novel:
     allow-origins:
       - http://localhost:1024
       # - http://localhost:8080
+  jwt:
+    secret: E66559580A1ADF48CDD928516062F12E
 server:
   port: 8888
 # server.port=8888