Spring Boot
SpringBoot概述
以Spring为基础进行开发的框架,对spring的搭建进行了简化,基于约定大于配置的思想,部分配置从配置转变为java类的编写,内嵌tomcat服务器可以快速创建一个企业级应用程序,有大量的核心功能:起步依赖和自动配置(主要还是配置繁琐和依赖多进行优化)
起步依赖:spring基本的依赖会自动导入
自动配置:spring启动时,通过配置的相关依赖自动加载配置
集成日志
日志更新 昨天七月六号的日志文件被打包为:log.log.2024-07-06.0.gz的文件
统一异常处理
AOP的使用场景:集成日志,异常处理
不修改原来代码的同时提供新功能,分离业务和非业务代码,使代码清晰简洁
搭建前端项目,实现前后端交互
### 登录功能
1.复制之前宿舍管理系统的前端项目 命名为newsweb
2.前端发送异步请求,出现跨域问题
java
package com.ffyc.news.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Collections;
/*
@Configuration添加在类上,表明此类是一个配置类 SpringBoot启动时,就会扫描该类
*/
@Configuration
public class CorsConfig {
/*
@Bean用在方法上,等同于<bean id="" class="">
表示此方法会由程序员创建使用的类的对象,
最终返回, 返回是交给spring框架统一管理的
*/
//配置跨域处理的过滤器
@Bean
public CorsFilter corsFilter() {
System.out.println("跨域过滤器");
CorsConfiguration corsConfiguration = new CorsConfiguration();
//1,允许任何来源
corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
//2,允许任何请求头
corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
//3,允许任何方法
corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);
//4,允许凭证
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}
3.在后端登陆成功后,生成token
导入jwt jar包
xml
<!--JWT组件-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
导入JWTUtil
java
package com.ffyc.news.Util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.ffyc.news.model.Admin;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类
*/
public class JWTUtil {
/**
* 根据用户id,账号生成token
* @param admin
* @return
*/
public static String getToken(Admin admin) {
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 3600*1000*3);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)//第一部分
.withClaim("id",admin.getId())//第二部分
.withClaim("account",admin.getAccount())//第二部分
.withExpiresAt(expireDate)//第二部分
.sign(algorithm);//标签 加盐
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
/**
* 验证token是否有效
* @param token
* @return
*/
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {//当传过来的token如果有问题,抛出异常
return false;
}
}
/**
* 获得token 中playload部分数据,按需使用
* @param token
* @return
*/
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
}
}
4.前端保存用户token,账号
vue
sessionStorage.setItem("adminToken",resp.data.result.adminToken); //会话期间存储,关闭浏览器后,数据就会销毁
sessionStorage.setItem("account",resp.data.result.account)
5.在后端搭建管理员token验证的拦截器
java
package com.ffyc.news.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ffyc.news.Util.JWTUtil;
import com.ffyc.news.model.Result;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.Reader;
@Component
public class AdminTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入到拦截器");
String adminToken = request.getHeader("adminToken");
if(JWTUtil.verify(adminToken)){
return true;
}else {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
Result result = new Result(401,"token验证失败",null);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(result);
writer.write(json);
return false;
}
}
}
6.测试拦截器
权限管理
业务分析
回顾宿舍管理系统,一个管理员登陆到系统当中并没有什么权限限制,什么操作都可以做.
稍微大一点的项目,都不是一个管理员操作的,可以有多个管理员参与,不同的管理员部门岗位不同,登陆后操作菜单也应该不一样,需要对不同的管理员分配对应的权限菜单
##### 管理员管理
由超级管理员(only 1)登录到系统中,然后添加其他普通管理员,并为其分配操作菜单
登录
登录菜单需要动态显示,不同的管理员登录时可以根据给其分配的权限菜单动态显示
数据库设计
表单设计
-
管理员表 admin
id,account,password,phone,gender,type(1-超级管理员, 2-普通管理员),adminid,oper_time
-
菜单表 menu
id,name,url,type(1-系统菜单,2-普通菜单 系统菜单是不能分配给普通管理员的)
-
管理员_菜单表 admin_menu
id adminid menuid
数据库初始化数据:
- 超级管理员
- 菜单表中的菜单数据
编码
查询管理员列表
查id,account,phone,gender,type,adminid,oper_time 只查询普通管理员(where type = 2)
为列表添加查询条件
1.为什么要添加查询条件
方便我们快速的查询到想要的数据
添加查询条件:
1.前端将查询的方法抽取出来,需要用的时候调用该方法即可.
html
<script>
export default {
data() {
return {
tableData: [],
form:{
account:"",
gender:"",
/* 这里注意向后传参不是null,而是空串 */
}
}
},
methods: {
//查询按钮
search(){
this.admins();
},
admins(){
this.$http.post("/adminCtl/adminList",this.form).then((resp) => {
/* ↑ 这里注意,不能使用get请求传递参数,get请求传递参数的方法是?接数据,这里使用了请求体,需要用post方法进行传参
*/
this.tableData = resp.data.data;
});
}
},
mounted() {
/* 向后端发送请求查询管理员列表 */
this.admins();
}
}
</script>
2.后端使用Admin对象接收前端传过来的数据(@RequestBody进行json转换)
java
@RequestMapping(path = "/adminList")
public Result login(@RequestBody Admin admin){
/* @RequestBody 对Json字符串进行转换 */
List<Admin> list = adminService.findAdminList(admin);
if(list!=null){
return new Result(200,"登陆成功",list);
}else {
return new Result(201,"账号或密码错误",null);
}
}
3.Mapper中对sql语句进行添加:
sql
WHERE a.type = 2
<if test=" account!='' ">
/* ↑ 不能为null,而是一个空串 */
and a.account = #{account}
</if>
<if test=" gender!='' ">
and a.gender = #{gender}
</if>
注意这里的查询条件不能为null,form传回来的数据不是null而是一个空串
为列表添加分页功能
1.为什么要添加分页功能,它能实现什么效果
为了限制一次查询过多的数据,提高查询效率
2.分析分页技术实现和条件
必备条件:
1.总记录数---总共有多少条数据(已知条件)
2.每页显示的条数(已知条件)
3.总页数 = 总记录数/每页显示的条数(不能整除 + 1)
5%2==0?5:5/2+1
4.分页实现---mysql使用的limit
LIMIT 0,2(第几个开始查询, 查询多少条)
5.当前页码 n limit (n-1)*每页显示的条数
前端向后端传递的数据:
每页显示的条数
当前页数
后端向前端传递的数据:
总记录数
对应页的数据集合
通过page组件来实现该功能
<!-- pagehelper依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
application.yml文件配置
spring:
main:
allow-circular-references: true #开始支持spring循环依赖
service类中
// pageNum当前页面 pageSize每页大小
public PageInfo<Admin> admins(Admin admin) {
PageHelper.startPage(admin.getPageNo(), admin.getPageSize()); //1.会自动算出limit后面的开始位置
List<Admin> admins = adminDao.admins(admin); //2.重新发一条sql,查询总条数
PageInfo<Admin> pageInfo = new PageInfo<>(admins);
return pageInfo;
}
pageInfo.getList();
pageInfo.getTotal()
PS:
权限还可以分配到按钮级别,控制哪些按钮是否显示
补充:
注解
@SpringBootApplication
使SpringBoot中的核心注解标签,用来启动
@ComponentScan
声明要扫描该类
@Configuration
加入@Configuration 注解,表明这就是一个配置类.有一个 myBean()的方法并用@Bean 进行注释,返回一个MyBean()的实例,表明这个方法是需要被Spring进行管理的bean @Bean 如果不指定名称的话,默认使用 myBean名称,也就是小写的名称.
@Bean
相当于XML中的,放在方法的上面,而不类,意思是产生一个bean,并交给spring管理.
Lombok
能干什么,有什么作用,解决什么问题
简化大部分模板化复杂的代码(普通的Get Set等),可以使用,但是日后会有地方要进行逻辑处理,所以不建议过多使用.