Springboot3后端知识点汇总
一、SpringBoot依赖以及用法
1、springboot核心起步依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency >
2、spring-boot-starter-web起步依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
3、mybatis
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 3.0.3</version > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > </dependency >
修改application.yaml
1 2 3 4 5 6 7 #配置数据源 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql: username: root password: 2003917
使用方法:
创建mapper接口,写上@mapper注解
定义方法在方法上写出要调用增删改查哪一种方法,并且在里面写SQL语句
1 2 3 4 5 6 7 8 @Mapper public interface ArticleMapper { @Select("select * from article where id=#{id}") Article detail (Integer id) ; List<Article> list (Integer userId, Integer categoryId, String state) ; }
或者是使用动态sql
在resources文件夹下创建与mapper接口同路径同名的xml文件
具体格式与写法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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 ="org.itheima.mapper.ArticleMapper" > <select id ="list" resultType ="org.itheima.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 = #{userId} </where > </select > </mapper >
开启驼峰命名
1 2 3 4 mybatis: configuration: map-underscore-to-camel-case: true
4、pageHelper
导入依赖
1 2 3 4 5 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper-spring-boot-starter</artifactId > <version > 1.4.6</version > </dependency >
使用步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Data @NoArgsConstructor @AllArgsConstructor public class PageBean <T>{ private Long total; private List<T> items; } PageBean<Article> pb = new PageBean <>(); PageHelper.startPage(pageNum,pageSize); List<Article> as = articleMapper.list(userId,categoryId,state); Page<Article> p = (Page<Article>) as; pb.setTotal(p.getTotal()); pb.setItems(p.getResult());
5、Redis
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
application.yaml中:
1 2 3 4 5 6 spring: data: redis: host: localhost port: 6379 password: root@123456
Java中Redis的set和get的基本用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Autowired private StringRedisTemplate stringRedisTemplate;public void testSet () { ValueOperations<String, String> operations = stringRedisTemplate.opsForValue(); operations.set("username" ,"zhangsan" ); operations.set("id" ,"1" ,15 , TimeUnit.SECONDS); } public void testGet () { ValueOperations<String, String> operations = stringRedisTemplate.opsForValue(); String username = operations.get("username" ); System.out.println(username); }
StringRedisTemplate 这个名字清晰地表达了它的功能:这是一个用于操作 Redis 字符串类型数据的模板。继承自 RedisTemplate 并加以简化,它专注于字符串数据的处理,使得开发者能够更方便、快速地进行 Redis 字符串类型操作。
二、application.yaml配置文件修改
1、修改端口和访问根路径
1 2 3 4 server: port: 9191 servlet: context-path:/start2
2、配置信息的读取
@Value(“${键名}”)
在application.yaml文件中写下
1 2 3 email: user: 2636939357 @qq.com code: 123
java代码中写下
1 2 3 4 5 6 7 8 @Component public class EmailProperties { @Value("${email.user}") public String user; @Value("${email.code}") public String code; }
@ConfigurationProperties(prefix=“前缀”)
1 2 3 4 5 6 7 @ConfigurationProperties(prefix=“email”) @Component public class EmailProperties { public String user; public String code; }
使用时自动将对应的配置类自动注入即可
1 2 3 4 5 6 private final EmailProperties emailProperties;@Autowired public ServerController (EmailProperties emailProperties) { this .emailProperties = emailProperties; }
三、SpringBoot注解
1、Bean扫描
@ComponentScan(basePackages = “com.xinzhe”)
SpringBoot默认扫描启动类所在的包及其子包
2、注册生效条件的注解 @Conditional
@condition注解有很多用法,具体需要自己去试,这里不做展开
注解
说明
@ConditionalOnProperty
配置文件中存在对应的属性,才声明该bean
@ConditionalOnMissingBean
当不存在当前类型的bean时,才声明该bean
@ConditionalOnClass
当前环境存在指定的这个类时,才声明该bean
3、Spring Validation参数校验
①对传入的参数进行合法性校验@
在对应的controller类上加入@Validated注解
对应的接口加上@Pattern(regexp = “^\S{5,16}$”) regexp是一个正则表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @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("用户名已被占用" ); } }
可以搭配全局异常处理 使用
②可以在接收的java对象前加上@Validated以及在对象的私有字段中加上以下注解等等
注解
说明
@NotNull
值不能为null
@NotEmpty
值不能为null,且不能为空
@Email
满足邮箱格式
@Pattern(regexp=“正则”)
匹配正则表达式
@URL
满足链接格式
③分组校验。我们可以把检验项进行分组,在完成不同功能的时候,用不同的分组
在@Validated中指定自己的分组
在对象中定义检验分组,以及在参数校验条件中指定生效的分组
定义校验项时如果没有指定分组,则属于Default分组,分组可以继承
④自定义参数验证注解
自定义注解State
选择好提供检验规则的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Documented @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint( validatedBy = {stateValidation.class}//制定提供校验规则的类 ) public @interface State { String message () default "state参数的值只能是已发布或者草稿" ; Class<?>[] groups() default {}; Class<? extends Payload >[] payload() default {}; }
提供校验规则
写好具体的校验规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class stateValidation implements ConstraintValidator </*给那个注解提供校验规则 */State,String> { @Override public boolean isValid (String value, ConstraintValidatorContext constraintValidatorContext) { if (value == null ){ return false ; } if (value.equals("已发布" ) || value.equals("草稿" )){ return true ; } return false ; } }
在需要的地方使用自定义注解
4、GlobalExceptionHandler全局异常处理
@RestControllerAdvice
1 2 3 4 5 6 7 8 9 10 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result handleException (Exception e) { e.printStackTrace(); return Result.error(StringUtils.hasLength(e.getMessage())?e.getMessage():"操作失败" ); } }
自定义错误类
1、定义通用错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public enum CommonError { UNKOWN_ERROR("执行过程异常,请重试。" ), PARAMS_ERROR("非法参数" ), OBJECT_NULL("对象为空" ), QUERY_NULL("查询结果为空" ), REQUEST_NULL("请求参数为空" ); private String errMessage; public String getErrMessage () { return errMessage; } private CommonError ( String errMessage) { this .errMessage = errMessage; } }
2、自定义异常类型
异常类型名可以自定义,使用的时候用
new XueChengPlusException(“错误信息”)
new XueChengPlusException().cast(“错误信息”)
new XueChengPlusException().cast(CommonError.UNKOWN_ERROR.getErrMessage())
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class XueChengPlusException extends RuntimeException { private String errMessage; public XueChengPlusException () { super (); } public XueChengPlusException (String errMessage) { super (errMessage); this .errMessage = errMessage; } public String getErrMessage () { return errMessage; } public static void cast (CommonError commonError) { throw new XueChengPlusException (commonError.getErrMessage()); } public static void cast (String errMessage) { throw new XueChengPlusException (errMessage); } }
5、自动配置原理
进入主类的SpringBootApplication 注解
发现我们的启动类其实也是一个配置类,并且有@EnableAutoConfiguration表示开启自动配置
再次点开,其实它导入了一个AutoConfigurationImportSelector.class
AutoConfigurationImportSelector.class实现了selectImports方法
这个方法最终会读取META-INF 目录下的 后缀名 为imorts的文件,当然了,boot2.7以前的版本,读取的是spring.factories文件,读取到全类名了之后,会解析注册条件,也就是@Conditional及其衍生注解,把满足注册条件的Bean对象自动注入到IOC容器中,
6、自定义start
首先我们要创建2个工程,autoconfigure工程用于配置需要自动导入的Bean starter工程用于依赖管理
在autoconfigure下创建好需要的自动配置类
类上方加入@AutoConfiguration注解代表自动注入
在类中要写需要自动配置成@bean的类,那么当然对应需要的依赖要在pom文件中引入
在META-INF文件夹下创建.import文件org.springframework.boot.autoconfigure.AutoConfiguration.imports
在里面指定自动配置类名
在start项目pom文件中引入自动配置依赖,还要引入全部用到依赖
若报错不支持的发布版本
则需要在2个pom文件中都加上下列代码
1 2 3 4 5 6 7 8 9 10 11 12 13 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11 .0 </version> <configuration> <source>17 </source> <target>17 </target> </configuration> </plugin> </plugins> </build>
@RequestHeader(name=“Authorization”)
1 2 3 4 5 6 7 8 9 10 @GetMapping("/userInfo") public Result<User> userInfo () { Map<String,Object> map = JwtUtil.parseToken(token); String username = (String) map.get("username" ); User user = userService.findByUserName(username); return Result.success(user); }
8、@RequestBody
自动将前端传来的js对象转换成Java对象
通常是这样的js对象
1 2 3 4 const userInfoData = { username : "newUsername" , email : "newemail@example.com" };
9、@RequestParam
自动将前端传来的url中的param接收转换成我们需要的对象
通常是这样的URL
1 https://example.com/api?qq=2636939357
10、@PathVariable
自动识别路径参数,自动注入
1 2 3 4 5 public Result startOrStop (@PathVariable Integer status,Long id) { log.info("启用禁用员工账号:{},{}" ,status,id); employeeService.startOrStop(status,id); return Result.success(); }
四、JWT登录验证
JWT(JSON Web Token)通常用于身份验证和信息交换。分别是头部 (Header)、有效载荷 (Payload)和签名 (Signature)组成
1、结构分析
头部
示例 :
1 2 3 4 json{ "alg": "HS256", "typ": "JWT" }
有效载荷
注册声明 (Registered Claims):这些是JWT预定义的标准声明,如iss(发行者)、exp(过期时间)、sub(主题)、aud(受众)等。
公共声明 (Public Claims):这些是可由用户定义的声明,但要避免与注册声明冲突,通常使用自己命名的标准。
私有声明 (Private Claims):这些是自定义声明,用于双方之间交换信息,但没有预定义标准。
示例 :
1 2 3 4 5 { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
签名
假设使用HMAC SHA256算法,签名的生成公式为:
1 2 3 4 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
签名部分确保JWT未被篡改,因为任何微小的修改都会导致签名无效。
一个完整的JWT令牌看起来可能如下:
1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第一部分 是经过Base64Url编码的头部:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
第二部分 是经过Base64Url编码的有效载荷:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
第三部分 是签名:SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
2、JWT-生成与验证
引入依赖
1 2 3 4 5 <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4 .0 </version> </dependency>
JWT生成
1 2 3 4 5 6 7 8 9 private static final String KEY = "itheima" ;public static String genToken (Map<String, Object> claims) { return JWT.create() .withClaim("claims" , claims) .withExpiresAt(new Date (System.currentTimeMillis() + 1000 * 60 * 60 * 12 )) .sign(Algorithm.HMAC256(KEY)); }
JWT验证
1 2 3 4 5 6 7 8 9 10 private static final String KEY = "itheima" ;public static Map<String, Object> parseToken (String token) { return JWT.require(Algorithm.HMAC256(KEY)) .build() .verify(token) .getClaim("claims" ) .asMap(); }
一般来说会将上面的方法整合成一个工具类JwtUtil
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class JwtUtil { private static final String KEY = "itheima" ; public static String genToken (Map<String, Object> claims) { return JWT.create() .withClaim("claims" , claims) .withExpiresAt(new Date (System.currentTimeMillis() + 1000 * 60 * 60 * 12 )) .sign(Algorithm.HMAC256(KEY)); } public static Map<String, Object> parseToken (String token) { return JWT.require(Algorithm.HMAC256(KEY)) .build() .verify(token) .getClaim("claims" ) .asMap(); } }
3、HandlerInterceptor
配合HandlerInterceptor接口实现自动对客户进行身份验证
这里面还使用到了:
ThreadLocal存储业务数据
redis的数据的读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Component public class LoginInterceptor implements HandlerInterceptor { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization" ); try { ValueOperations<String, String> operations = stringRedisTemplate.opsForValue(); String redisToken = operations.get(token); if (redisToken == null ){ throw new RuntimeException (); } Map<String,Object> claims = JwtUtil.parseToken(token); ThreadLocalUtil.set(claims); return true ; }catch (Exception e){ response.setStatus(401 ); return false ; } } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ThreadLocalUtil.remove(); } }
添加拦截器需要配置WebConfig类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired LoginInterceptor loginInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .excludePathPatterns("/user/login" ,"/user/register" ); } }
五、ThreadLocal
提供线程访问内都可访问的局部变量,只有在线程内才能获取到对应的值,线程外则不能访问。
用来存取数据: set()/get()
使用ThreadLocal存储的数据, 线程安全
用完记得调用remove方法释放
1、封装的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class BaseContext { public static ThreadLocal<Long> threadLocal = new ThreadLocal <>(); public static void setCurrentId (Long id) { threadLocal.set(id); } public static Long getCurrentId () { return threadLocal.get(); } public static void removeCurrentId () { threadLocal.remove(); } }
六、文件上传处理
1、前端通常的代码
1 2 3 4 <form action ="/upload" method ="post" enctype ="multipart/form-data" > 头像: <input type ="file" name ="image" > <br > <input type ="submit" value ="提交" > </form >
2、后端方法定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @PostMapping("/upload") public Result<String> upload (MultipartFile file) throws Exception { String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("." )); String url = AliOssUtil.uploadFile(fileName,file.getInputStream()); return Result.success(url); }
阿里云oss上传代码模版
引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <dependency > <groupId > com.aliyun.oss</groupId > <artifactId > aliyun-sdk-oss</artifactId > <version > 3.17.4</version > </dependency > <dependency > <groupId > javax.xml.bind</groupId > <artifactId > jaxb-api</artifactId > <version > 2.3.1</version > </dependency > <dependency > <groupId > javax.activation</groupId > <artifactId > activation</artifactId > </dependency > <dependency > <groupId > org.glassfish.jaxb</groupId > <artifactId > jaxb-runtime</artifactId > <version > 2.3.3</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class AliOssUtil { private static String ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com" ; private static String ACCESS_KEY_ID="你的ACCESS_KEY_ID" ; private static String ACCESS_KEY_SECRET="你的ACCESS_KEY_SECRET" ; private static String BUCKET_NAME = "你的BucketName" ; public static String uploadFile (String objectName, InputStream in) throws Exception { String url = "" ; OSS ossClient = new OSSClientBuilder ().build(ENDPOINT, ACCESS_KEY_ID,ACCESS_KEY_SECRET); try { String content = "Hello OSS,你好世界" ; PutObjectRequest putObjectRequest = new PutObjectRequest (BUCKET_NAME, objectName, in); PutObjectResult result = ossClient.putObject(putObjectRequest); url = "https://" +BUCKET_NAME+"." +ENDPOINT.substring(ENDPOINT.lastIndexOf("/" )+1 )+"/" +objectName; } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason." ); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network." ); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null ) { ossClient.shutdown(); } } return url; } }
七、Redis
1、使用Redis进行的登录优化
当我们没有使用Redis的时候,前端直接拿Token令牌向后端交互。一般情况下没问题
但是当我们修改密码后,之前申请的token令牌理应立马失效。 我们可以用Redis来解决这个问题
令牌主动失效机制 :当用户修改密码成功后,删除redis中存储的旧令牌
1 2 3 4 5 6 userService.updatePwd(newPwd); ValueOperations<String, String> operations = stringRedisTemplate.opsForValue(); operations.getOperations().delete(token);
八、SpringBoot项目部署
1、打包插件
1 2 3 4 5 6 7 8 9 10 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 3.1.3</version > </plugin > </plugins > </build >
如何生成jar包?
执行package命令即可
如何运行jar包?
Java –jar jar包位置
Jar包部署对服务器有什么要求?
必须有jre环境
这里不详细展开
2、属性配置方式
命令行参数
当我们想要用cmd打开一个jar包的时候
1 java -jar big-event-1 .0 -SNAPSHOT .jar --server .port=9999
可以在后面添加参数
然后可以在主函数的args中读取到这个参数
配置文件方式
或者
环境变量方式
外部文件方式
配置优先级,有上到下依次升高
项目中resources目录下的application.yml
Jar包所在目录下的application.yml
操作系统环境变量
命令行参数
命令行参数 > 环境变量 > JAR包所在目录的配置 > 项目的 application.yml 配置
九、IDEA工具快捷用法
1、Http Client
1 2 3 4 5 6 7 8 POST /content/course/list?pageNo=2 &pageSize=1 Content-Type: application/json { "auditStatus" : "202002" , "courseName" : "" , "publishStatus" :"" }
十、Mapper
1、创建一个mapper接口
1 2 3 public interface CourseCategoryMapper extends BaseMapper <CourseCategory> { public List<CourseCategoryTreeDto> selectTreeNodes (String id) ; }
2、创建一个同名.xml文件
在同级下,例如CourseCategoryMapper.xml
尖括号指定方法类型 : select delete update insert
namespace 指定mapper接口
id 指定方法名
parameterType 指定传参类型
resultType 指定返回值类型
mybatis会自动转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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.xuecheng.content.mapper.CourseCategoryMapper" > <select id ="selectTreeNodes" parameterType ="string" resultType ="com.xuecheng.content.model.dto.CourseCategoryTreeDto" > with recursive t1 as ( select * from course_category p where id= '1' union all select t.* from course_category t inner join t1 on t1.id = t.parentid ) select * from t1 order by t1.id, t1.orderby </select > </mapper >
十一、常用
1、对于业务码可以定义一个字典表专门存储
2、解决跨域问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Configuration public class GlobalCorsConfig { @Bean public CorsFilter corsFilter () { CorsConfiguration config = new CorsConfiguration (); config.addAllowedOrigin("*" ); config.setAllowCredentials(true ); config.addAllowedHeader("*" ); config.addAllowedMethod("*" ); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (); source.registerCorsConfiguration("/**" , config); return new CorsFilter (source); } }