Skip to content

一、项目实战

1、环境搭建

准备:

  1. 依赖(web、mybatis、mysql驱动)
  2. 配置application.yml的mybatis配置信息

2、数据库创建

sql

sql
-- 创建数据库
create database big_event;

-- 使用数据库
use big_event;

-- 用户表
create table user (
                      id int unsigned primary key auto_increment comment 'ID',
                      username varchar(20) not null unique comment '用户名',
                      password varchar(32)  comment '密码',
                      nickname varchar(10)  default '' comment '昵称',
                      email varchar(128) default '' comment '邮箱',
                      user_pic varchar(128) default '' comment '头像',
                      create_time datetime not null comment '创建时间',
                      update_time datetime not null comment '修改时间'
) comment '用户表';

-- 分类表
create table category(
                         id int unsigned primary key auto_increment comment 'ID',
                         category_name varchar(32) not null comment '分类名称',
                         category_alias varchar(32) not null comment '分类别名',
                         create_user int unsigned not null comment '创建人ID',
                         create_time datetime not null comment '创建时间',
                         update_time datetime not null comment '修改时间',
                         constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束
);

-- 文章表
create table article(
                        id int unsigned primary key auto_increment comment 'ID',
                        title varchar(30) not null comment '文章标题',
                        content varchar(10000) not null comment '文章内容',
                        cover_img varchar(128) not null  comment '文章封面',
                        state varchar(3) default '草稿' comment '文章状态: 只能是[已发布] 或者 [草稿]',
                        category_id int unsigned comment '文章分类ID',
                        create_user int unsigned not null comment '创建人ID',
                        create_time datetime not null comment '创建时间',
                        update_time datetime not null comment '修改时间',
                        constraint fk_article_category foreign key (category_id) references category(id),-- 外键约束
                        constraint fk_article_user foreign key (create_user) references user(id) -- 外键约束
)

3、请求参数校验

1、引入validation

xml
<!--		vaildation参数校验-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>

2、加入注解

@Validated和@Pattern

java
@RequestMapping("/users")
@RestController
@Validated
public class UserController {
    @Autowired
    private UserService userService;

    //    用户注册
    @PostMapping("/register")
    public Result register(@Pattern(regexp = "^\\s{5,16}$") String username, @Pattern(regexp = "^\\s{5,16}$") String password) {
//        先判断是否存在此用户
        User user = userService.findByUsername(username);
//        注册用户
        if (user == null) {
            userService.register(username, password);
            return Result.success();
        } else {
//            返回错误信息
            return Result.error("用户名已存在");
        }
    }
}

3、设置全局异常

java
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e){
        e.printStackTrace();
        return Result.error(StringUtils.hasLength(e.getMessage())?e.getMessage():"操作失败");
    }
}

4、JWT的引入

jwt的版本一定要一致

xml
<!--		java-jwt依赖-->
		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>4.4.0</version>
		</dependency>

1、生成token

java
    @Test
    public void TestCreateJwt(){
        Map<String,Object> claims=new HashMap<>();
        claims.put("id",1);
        claims.put("username","黄达全");

        String token = JWT.create()
                .withClaim("user", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256("huangdaquan"));

        System.out.println(token);
    }

结果

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6Ium7hOi-vuWFqCJ9LCJleHAiOjE3MDExMzkzNTl9.dJRvMnQ1pe9JYdgF6t5nxmwl7EoP8qP4kpZMa2wBmGY

2、校验token

java
    @Test
    public void TestParseToken(){
        String token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6Ium7hOi-vuWFqCJ9LCJleHAiOjE3MDExMzg3NDF9.Mcyx4V2GibjMk0FrtU1lJX0--02JrdGmsvZXVoweQH8";

        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("huangdaquan")).build();

        DecodedJWT verify = jwtVerifier.verify(token);

        Map<String, Claim> claims = verify.getClaims();

        Claim user = claims.get("user");

        System.out.println(user);
    }

结果

json
{"id":1,"username":"黄达全"}

5、拦截器的使用

1、单个使用(不推荐)

controller类

java
@RequestMapping("/articles")
@RestController
public class ArticleController {
//    获取所有文章数据
    @GetMapping
    public Result getAll(@RequestHeader(name = "Authorization") String token, HttpServletResponse response){
        try {
            Map<String, Object> parseToken = JwtUtil.parseToken(token);
            return Result.success("获取数据");
        } catch (Exception e) {
            response.setStatus(401);
            return Result.error("未登录");
        }
    }
}

2、注册拦截器

1、创建interceptors -> LoginInterceptor 编写拦截器

java
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        请求头解析出token
        String token = request.getHeader("Authorization");
//        验证token
        try {
            Map<String, Object> parseToken = JwtUtil.parseToken(token);
            return true;
        } catch (Exception e) {
            response.setStatus(401);
            return false;
        }
    }
}

2、创建config -> WebCogfig 注册拦截器

java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}

6、请求响应忽略某属性(如密码)

@JsonIgnore注解,这是一种用于在Java对象序列化为JSON格式时控制某些字段不被包括在序列化结果中的注解。

java
    @JsonIgnore
    private String password;//密码

7、驼峰命名映射不到数据库(mybatis)

问题:createTime和updateTime为null,映射不出数据库的数据

图片

需要修改applicatiion.yml文件的配置

设置MyBatis是否要将数据库字段的下划线命名风格自动映射为Java对象的驼峰命名风格。

将数据库字段的下划线命名风格自动映射为Java对象的驼峰命名

yaml
mybatis:
  configuration:
    map-underscore-to-camel-case: true

8、ThreadLocal优化拦截器

使用ThreadLocal保存用户关键信息,减少重复的获取用户信息代码

1、在请求拦截器里,保存token

java
        try {
            Map<String, Object> parseToken = JwtUtil.parseToken(token);
            ThreadLocalUtil.set(parseToken);
            return true;
        } catch (Exception e) {
            response.setStatus(401);
            return false;
        }

2、在需要用户信息来进行请求的接口

java
    @GetMapping("/userInfo")
    public Result<User> getUserInfo(){
        Map<String,Object> parseToken=ThreadLocalUtil.get();
        String username=(String)parseToken.get("username");
        User user = userService.findByUsername(username);
        return Result.success(user);
    }

3、请求结束后,移除ThreadLocal

使用响应拦截器

java
 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
     ThreadLocalUtil.remove();
 }

这样子减少了重复从请求头获取token的代码

9、自定义校验注解

例如:需要给state(上架状态)设置校验规则,需要自定义

1、anno -> State 创建一个@interface State 注解

java
import com.hdq.validation.StateValidation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;


@Documented
@Constraint(validatedBy = {StateValidation.class})
@Retention(RUNTIME)
@Target({FIELD})

public @interface State {
    String message() default "state的值只能是'已发布'或'草稿'";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

2、validiation -> StateValidation 为注解State设置校验规则

java
import com.hdq.anno.State;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class StateValidation implements ConstraintValidator<State,String> {
    /**
     *
     * @param value 校验的数据
     * @param context
     * @return
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        System.out.println(value);
        if(value==null) return false;
        if(value.equals("已发布")||value.equals("草稿")) return true;
        return false;
    }
}

3、添加@Validate

java
    @PostMapping
    public Result insert(@RequestBody @Validated Article article){
        articleService.insert(article);
        return Result.success();
    }

10、使用pageHelper实现分页查询功能

1、引入pageHelper

xml
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.4.6</version>
</dependency>

2、准备一个实体类PageBean

PageBean用来存放分页查询的结果

java
package com.hdq.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

//分页返回结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean <T>{
    private Long total;//总条数
    private List<T> items;//当前页数据集合
}

3、编写controller层

java
   @GetMapping("/page")
//    分页查询文章
    public Result<PageBean<Article>> getPageList(
            Integer current,
            Integer size,
            @RequestParam(required = false) Integer categoryId,
            @RequestParam(required = false) String state
    ){

        try {
            PageBean<Article> pb=articleService.getPageList(current,size,categoryId,state);
            return Result.success(pb);
        } catch (Exception e) {
            return Result.error("查询失败");
        }
    }

4、编写service -> impl 的分页功能

java
@Override
    public PageBean<Article> getPageList(Integer current, Integer size, Integer categoryId, String state) {
//        数据存放容器
        PageBean<Article> pb=new PageBean<>();
//        分页查询
        PageHelper.startPage(current,size);

//        获取user_id
        Map<String,Object> claims=ThreadLocalUtil.get();
        Integer id=(Integer) claims.get("id");

//        传递参数mapper,返回数据
        List<Article> as=articleMapper.getPageList(id,categoryId,state);
        
//        强转
        Page<Article> p=(Page<Article>) as;
        
        pb.setTotal(p.getTotal());
        pb.setItems(p.getResult());
        return pb;
    }

5、mapper

java
List<Article> getPageList(Integer id, Integer categoryId, String state);

6、映射到resource -> ...对应的目录下的文件

xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hdq.mapper.ArticleMapper">
    <select id="getPageList" resultType="com.hdq.pojo.Article">
        select * from article
        <where>
            <if test="categoryId!=null">
                category_id=#{categoryId}
            </if>
            <if test="state!=null">
                and state=#{state}
            </if>
            and create_user=#{id}
        </where>
    </select>
</mapper>

11、本地存储复制文件

1、新建FileUploadController类

java
   @PostMapping("/upload")
    public Result<String> upload(MultipartFile file) throws IOException {
//        获取文件名
        String originalFilename = file.getOriginalFilename();
//        生成唯一文件名
        String name = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        System.out.println(name);
//        本地存储
        file.transferTo(new File("E:\\图片\\"+name));
        return Result.success("ss");
    }

2、postman测试

图片