SpringBoot阶段总结
1.序言
学习Java到现在,也快过去一年了,在这里写下这篇文章,算是自己的一个阶段性的学习总结,也是为了方便后续的复习,以下是我认为比较有用的知识点。
2.拦截器
创建一个SpringBoot项目,导入下列依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
先创建一个实体类,用来模拟登录用户:
@Data
public class User {
private Integer id;
private String username;
private String password;
}
创建拦截器,用于拦截用户请求,我们的拦截器基础自HandlerInterceptor,实现下面三个方法
- preHandle
调用时间:Controller方法处理之前
执行顺序:链式Intercepter情况下,Intercepter按照注册声明的顺序一个接一个执行,若返回false,则中断执行。
备注:不会进入afterCompletion方法 - postHandle
调用前提:preHandle返回true
调用时间:在Controller方法处理完之后,DispatcherServlet进行视图的渲染之前执行,即可以在这个方法中对ModelAndView进行操作
执行顺序:链式Intercepter情况下,Intercepter按照注册声明的顺序倒着执行。
备注:postHandle虽然post打头,但post、get方法都能处理 - afterCompletion
调用前提:preHandle返回true
调用时间:DispatcherServlet进行视图的渲染之后
执行顺序:链式Intercepter情况下,Intercepter按照注册声明的顺序倒着执行。
备注:多用于清理资源
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
//这里的登录逻辑是,先判断HttpSession会话中是否有用户信息,没有说明还未登录
HttpSession session=request.getSession();
User user=(User)session.getAttribute("user");
if(user==null){
//没有用户,请求被拦截
System.out.println("请求被拦截=============");
return false;
}else{
//用户已登录,将请求放行
System.out.println("请求被放行============");
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception{
System.out.println("处理完成进入");
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,
Object handler, Exception ex)throws Exception{
System.out.println("preHandler返回true后进入");
}
}
创建配置类
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
//配置放行的静态资源
}
@Override
public void addInterceptors(InterceptorRegistry registry){
//拦截除了login和register外的所有请求
registry.addInterceptor(loginInterceptor).addPathPatterns("/*")
.excludePathPatterns("/login","/register");
}
}
请求类
@RestController
public class UserController {
@RequestMapping("/login")
public String login(HttpServletRequest request, @RequestParam(value = "username",required = true)String username,
@RequestParam(value = "password",required = true)String password){
System.out.println("登录验证开始==========");
if(username.equals("admin")&&password.equals("123456")){
User user=new User();
user.setId(1);
user.setUsername(username);
user.setPassword(password);
//设置到httpSession会话中
request.getSession().setAttribute("user",user);
return "登录成功========";
}else{
return "登录失败==========";
}
}
@RequestMapping("/index")
public String index(){
return "index";
}
}
下面是配置文件application.yml
server:
port: 8081
启动项目,打开postman进行接口测试:
先测试http://localhost:8081/index
请求被拦截,我们进行登录:http://localhost:8081/login?username=admin&password=123456
再访问index
2.JdbcTemplate
之前我们有解除过JDBC,就是使用java来访问数据库,而JdbcTemplate是对JDBC的进一步封装,使用起来更加的方便
创建一个springboot项目,导入下列依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
修改配置文件application.yml
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
先打开Navicat,在young数据库中创建一个user表
创建一个实体类:
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
}
dao层:
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//添加用户
public int saveUser(User user){
//插入SQL语句
String sql="insert into user(username,password,age) values(?,?,?);";
//主键获取
KeyHolder keyHolder=new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
//指定主键
PreparedStatement preparedStatement=con.prepareStatement(sql,new String[]{"id"});
preparedStatement.setString(1,user.getUsername());
preparedStatement.setString(2,user.getPassword());
preparedStatement.setInt(3,user.getAge());
return preparedStatement;
}
},keyHolder);
return keyHolder.getKey().intValue();
}
//修改用户
public int updateUser(User user){
String sql="update user set password=? where id=?";
int res=jdbcTemplate.update(sql,user.getPassword(),user.getId());
return res;
}
//删除用户
public int deleteUser(Integer id){
String sql="delete from user where id=?";
int res=jdbcTemplate.update(sql,id);
return res;
}
//获取用户详情
public Map<String, Object> getUserById(Integer id){
String sql="select * from user where id="+id;
Map<String,Object>res =jdbcTemplate.queryForMap(sql);
return res;
}
//获取用户列表
public List<User>getUserList(){
String sql="select * from user";
List<User>list=jdbcTemplate.query(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user=new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setAge(rs.getInt("age"));
return user;
}
});
return list;
}
}
控制层:
@RestControler
public class UserController {
@Autowired
private UserDao userDao;
@RequestMapping("/saveUser")
public int saveUser(@RequestBody User user){
return userDao.saveUser(user);
}
@RequestMapping("/updateUser")
public int updateUser(@RequestBody User user){
return userDao.updateUser(user);
}
@RequestMapping("/deleteUser")
public int deleteUser(int id){
return userDao.deleteUser(id);
}
@RequestMapping("/getUserById")
public Map<String, Object> getUserById(int id){
return userDao.getUserById(id);
}
@RequestMapping("/getUserList")
public List<User>getUserList(){
return userDao.getUserList();
}
}
运行项目:
添加用户
修改用户
查询用户
查询用户列表:
删除用户
更多的jdbcTemplate操作,可以参考下列文章
SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解
JdbcTemplate基本使用
虽然JdbcTemplate不需要向JDBC一样一直创建连接、关闭连接,但是在使用上还是比较繁琐,因此不推荐大家使用,我们有方便的框架,如Mybatis,Mybatis-plus
3.接口返回值
在刚才的接口测试中,我们发现,返回值有时是一个数字或一个字符串,有时是一个json格式的数据,而在实际开发中,我们后端返回的数据,通常还需要一个状态码和消息提示来返回给前端,所以接下来,我们就来将一下接口该如何返回信息
创建一个springboot项目,引入下列依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
创建一个user实体类
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
}
创建一个ResultVO.java,用于返回结果
/**
* @param <T>
* code 表示状态码
* msg 表示提示信息
* data 表示返回的数据
*/
@Data
public class ResultVO <T>{
private Integer code;
private String msg;
private T data;
public ResultVO(){}
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
this.data=null;
}
public ResultVO(Integer code,String msg,T data){
this.code=code;
this.msg=msg;
this.data=data;
}
}
创建一个ResultVOUtil.java工具类
public class ResultVOUtil {
public static ResultVO success(){
return new ResultVO<>(200,"操作成功");
}
public static ResultVO success(Object data){
return new ResultVO<>(200,"操作成功",data);
}
public static ResultVO error(Integer code,String msg){
return new ResultVO<>(code,msg);
}
}
创建一个控制类
@RestController
public class UserController {
@RequestMapping("/success")
public ResultVO success(){
return ResultVOUtil.success();
}
@RequestMapping("/fail")
public ResultVO fail(){
return ResultVOUtil.error(404,"找不到页面");
}
@RequestMapping("/getUser")
public ResultVO getUser(){
User user=new User();
user.setId(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return ResultVOUtil.success(user);
}
@RequestMapping("/getUserList")
public ResultVO getUserList(){
List<User>list=new ArrayList<>();
for(int i=0;i<4;i++){
User user=new User();
user.setId(i+1);
user.setUsername("username"+i);
user.setPassword("123"+i);
user.setAge(18+i);
list.add(user);
}
return ResultVOUtil.success(list);
}
}
运行项目
4.SpringBoot 整合Mybatis
创建一个springboot项目,将上一个案例的User,ResultVO,ResultVOUtil类复制到当前项目,然后引入下列依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
修改application.yml配置文件
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
username: root
password: 3fa4d180
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: mapper/*.xml
创建mapper软件包,在该包下创建UserMapper.java
@Mapper
public interface UserMapper {
public int saveUser(User user);
public int updateUser(User user);
public int deleteUser(Integer id);
public User getUserById(Integer id);
public List<User>getUserList();
}
创建service软件包,在该包下创建UserService.java接口
public interface UserService {
ResultVO saveUser(JSONObject jsonObject);
ResultVO updateUser(JSONObject jsonObject);
ResultVO deleteUser(Integer id);
ResultVO getUserById(Integer id);
ResultVO getUserList();
}
创建UserServiceImpl实现UserService接口
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public ResultVO saveUser(JSONObject jsonObject) {
try{
String username=jsonObject.getString("username");
String password=jsonObject.getString("password");
Integer age=jsonObject.getInteger("age");
User user=new User();
user.setUsername(username);
user.setPassword(password);
user.setAge(age);
int addNum=userMapper.saveUser(user);
if(addNum<=0){
return ResultVOUtil.error(500,"添加用户失败");
}
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",user.getId());
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(500,"添加用户数据库操作失败");
}
}
@Override
public ResultVO updateUser(JSONObject jsonObject) {
try{
Integer id=jsonObject.getInteger("id");
String username=jsonObject.getString("username");
String password=jsonObject.getString("password");
Integer age=jsonObject.getInteger("age");
User user=new User();
user.setUsername(username);
user.setPassword(password);
user.setAge(age);
user.setId(id);
int updateNum=userMapper.updateUser(user);
if(updateNum<=0){
return ResultVOUtil.error(500,"修改用户失败");
}
Map<String,Object>res=new HashMap<>();
res.put("updateNum",updateNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(500,"修改用户数据库操作失败");
}
}
@Override
public ResultVO deleteUser(Integer id) {
try{
int deleteNum=userMapper.deleteUser(id);
if(deleteNum<=0){
return ResultVOUtil.error(500,"删除用户失败");
}else{
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",deleteNum);
return ResultVOUtil.success(res);
}
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(500,"删除用户失败");
}
}
@Override
public ResultVO getUserById(Integer id) {
try{
User user=userMapper.getUserById(id);
return ResultVOUtil.success(user);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(500,"获取用户详情失败");
}
}
@Override
public ResultVO getUserList() {
try{
List<User>list=userMapper.getUserList();
return ResultVOUtil.success(list);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(500,"获取用户列表失败");
}
}
}
创建UserController.java控制类
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public ResultVO saveUser(@RequestBody JSONObject jsonObject){
log.info("-------saveUser-------");
return userService.saveUser(jsonObject);
}
@RequestMapping(value = "/updateUser",method = RequestMethod.PUT)
public ResultVO updateUser(@RequestBody JSONObject jsonObject){
log.info("-------updateUser-------");
return userService.updateUser(jsonObject);
}
@RequestMapping(value = "/deleteUser",method = RequestMethod.DELETE)
public ResultVO deleteUser(@RequestParam(name = "id",required = true)Integer id){
log.info("------deleteUser---------");
return userService.deleteUser(id);
}
@RequestMapping(value = "/getUserById",method = RequestMethod.GET)
public ResultVO getUserById(@RequestParam(name = "id",required = true)Integer id){
log.info("------getUserById-------");
return userService.getUserById(id);
}
@RequestMapping(value = "/getUserList",method = RequestMethod.GET)
public ResultVO getUserList(){
log.info("-------getUserList-------");
return userService.getUserList();
}
}
在resource目录下,创建mapper目录,在该目录下创建UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.young.demo19.mapper.UserMapper">
<insert id="saveUser" parameterType="com.young.demo19.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into user(id,username,password,age)
values(#{id},#{username},#{password},#{age})
</insert>
<update id="updateUser" parameterType="com.young.demo19.entity.User">
update user
set
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="password!=null and password!=''">
password=#{password},
</if>
<if test="age!=null">
age=#{age},
</if>
<if test="1!=0">
id=#{id}
</if>
where id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from user
where id=#{id}
</delete>
<select id="getUserById" parameterType="int" resultType="com.young.demo19.entity.User">
select *
from user
where id=#{id}
</select>
<select id="getUserList" resultType="com.young.demo19.entity.User">
select *
from user
</select>
</mapper>
启动项目
刚才总结的,都是单个表的查询,接下来讲述多表查询的操作,我们先创建一个权限表
在该表中插入一些数据
创建Role实体类
@Data
public class Role {
private Integer id;
private String name;
}
修改User实体类
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private List<Role>roles;
}
当在数据库进行SQL查询时,如果有一些列的名称相同,我们可以使用别名,在多表查询中,我们一般不会返回resultType,而是返回ResultMap,ResultMap将SQL查询到的列与实体类对应的属性一一对应。
修改UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.young.demo19.mapper.UserMapper">
<resultMap id="userBean" type="com.young.demo19.entity.User">
<id property="id" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="age" column="age"/>
<collection property="roles" ofType="com.young.demo19.entity.Role">
<id property="id" column="rid"/>
<result property="name" column="name"/>
</collection>
</resultMap>
<insert id="saveUser" parameterType="com.young.demo19.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into user(id,username,password,age)
values(#{id},#{username},#{password},#{age})
</insert>
<update id="updateUser" parameterType="com.young.demo19.entity.User">
update user
set
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="password!=null and password!=''">
password=#{password},
</if>
<if test="age!=null">
age=#{age},
</if>
<if test="1!=0">
id=#{id}
</if>
where id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from user
where id=#{id}
</delete>
<select id="getUserById" parameterType="int" resultMap="userBean">
select u.id uid,u.username,
u.password,u.age,
r.id rid,r.name
from user u left join user_role ur
on u.id=ur.uid
left join role r
on r.id=ur.rid
where u.id=#{id}
</select>
<select id="getUserList" resultMap="userBean">
select u.id uid,u.username,
u.password,u.age,
r.id rid,r.name
from user u left join user_role ur
on u.id=ur.uid
left join role r
on r.id=ur.rid
</select>
</mapper>
创建一个user_role中间表
插入一些数据
运行项目
参考文章:
MyBatis快速入门第一讲–使用传统的JDBC编程带来的问题
推荐这个作者写的SSM系列的专栏,写的很详细
5.Restful风格和分页
RESTFUL特点包括:
1、每一个URI代表1种资源;
2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3、通过操作资源的表现形式来操作资源;
4、资源的表现形式是XML或者 JSON;
5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
顺便在这里补充一个内容,就是我们的接口的URL,可以写成这种形式/user/{id},下面是基于上个例子修改而来的实例
修改UserController.java
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public ResultVO saveUser(@RequestBody JSONObject jsonObject){
log.info("-------saveUser-------");
return userService.saveUser(jsonObject);
}
@RequestMapping(value = "/updateUser",method = RequestMethod.PUT)
public ResultVO updateUser(@RequestBody JSONObject jsonObject){
log.info("-------updateUser-------");
return userService.updateUser(jsonObject);
}
@RequestMapping(value = "/deleteUser",method = RequestMethod.DELETE)
public ResultVO deleteUser(@RequestParam(name = "id",required = true)Integer id){
log.info("------deleteUser---------");
return userService.deleteUser(id);
}
@RequestMapping(value = "/getUserById",method = RequestMethod.GET)
public ResultVO getUserById(@RequestParam(name = "id",required = true)Integer id){
log.info("------getUserById-------");
return userService.getUserById(id);
}
@RequestMapping(value = "/getUserList",method = RequestMethod.GET)
public ResultVO getUserList(){
log.info("-------getUserList-------");
return userService.getUserList();
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public ResultVO getUserById1(@PathVariable(name = "id",required = true)Integer id){
log.info("-----getUserById1---------");
return userService.getUserById(id);
}
}
刚才整合Mybatis时,忘记讲分页的操作了,在这里补充一下
修改UserMapper.java,添加一个方法
public int getTotal();
修改UserMapper.xml,添加以下内容
<select id="getTotal" resultType="int">
select count(*)
from user
</select>
修改UserService.java,添加一个方法
public ResultVO getUserList(int pageNum,int pageSize);
修改UserServiceImpl.java
@Override
public ResultVO getUserList(Integer pageNum,Integer pageSize){
try{
PageHelper.startPage(pageNum,pageSize);
List<User>list=userMapper.getUserList();
int total=userMapper.getTotal();
Map<String,Object>res=new HashMap<>();
res.put("total",total);
res.put("list",list);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(500,"分页获取失败");
}
}
修改UserController.java
@RequestMapping(value = "/user/getUserList/{pageNum}/{pageSize}",method = RequestMethod.GET)
public ResultVO getUserList(@PathVariable(name = "pageNum",required = true)Integer pageNum,
@PathVariable(name = "pageSize",required = true)Integer pageSize){
log.info("----getUserList1-------");
return userService.getUserList(pageNum,pageSize);
}
运行项目
6.Cookie和Session
创建一个springboot项目
实体类User.java,用来模拟用户登录
@Data
public class User {
private Integer id;
private String username;
private String password;
}
创建拦截器LoginInterceptor.java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
//获取cookie
Cookie[]cookies=request.getCookies();
for(Cookie cookie:cookies){
//判断是否存在对应的cookie
if(cookie.getName().equals(WebConfiguration.COOKIE_NAME)){
//判断session中是否有user
HttpSession session=request.getSession();
//模拟通过数据库获取用户信息
User user=(User)session.getAttribute(WebConfiguration.SESSION_NAME);
if(user==null){
user=new User();
user.setId(1);
user.setUsername("张三");
user.setPassword("123456");
//将用户存储到session中
session.setAttribute(WebConfiguration.SESSION_NAME,user);
}
System.out.println("放行========");
return true;
}
}
System.out.println("拦截成功===========");
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception{
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){
}
}
配置类WebConfiguration.java
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
public final static String COOKIE_NAME="user_cookie";
public final static String SESSION_NAME="user_session";
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
}
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/*")
.excludePathPatterns("/login");
}
}
控制类UserController.java
@RestController
public class UserController {
@RequestMapping("/login")
public Cookie login(@RequestParam(value = "username",required = true)String username,
@RequestParam(value = "password",required = true)String password,
HttpServletResponse response){
if(username.equals("admin")&&password.equals("123456")){
//登录成功后添加cookie
Cookie cookie=new Cookie(WebConfiguration.COOKIE_NAME,username);
cookie.setMaxAge(30);
cookie.setPath("/");
response.addCookie(cookie);
return cookie;
}else{
return null;
}
}
@RequestMapping("/index")
public User index(HttpServletRequest request){
HttpSession session=request.getSession();
User user=(User) session.getAttribute(WebConfiguration.SESSION_NAME);
return user;
}
@RequestMapping("/logout")
public String logout(HttpServletRequest request,HttpServletResponse response){
Cookie[]cookies=request.getCookies();
for(Cookie cookie:cookies){
if(cookie.getName().equals(WebConfiguration.COOKIE_NAME)){
request.getSession().removeAttribute(WebConfiguration.SESSION_NAME);
cookie.setMaxAge(0);
response.addCookie(cookie);
return "注销成功";
}
}
return "注销失败";
}
}
运行项目
30秒后,在访问index
7.AOP切面
AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。
AOP我一般是用来打印日志,当然它还有许多功能,如权限检验等,下面是整合步骤:
创建SpringBoot项目,引入下列依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
创建一个切面类:
@Aspect
@Component
@Slf4j
public class MyAop{
@Pointcut("execution(public * com.young.demo19.controller.*.*(..))")
public void log(){
log.info("切入方法");
}
//前置通知
@Before("log()")
public void doBefore(JoinPoint joinPoint)throws Throwable{
log.info("------------- start --------------");
ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request=attributes.getRequest();
log.info("URL:"+request.getRequestURL());
log.info("METHOD:"+request.getMethod());
log.info("CLASS_METHOD:"+joinPoint);
log.info("ARGS:"+Arrays.toString(joinPoint.getArgs()));
}
//返回通知
@AfterReturning(returning = "ret",pointcut = "log()")
public void afterReturning(Object ret){
log.info("返回值是:"+ret);
}
//异常通知
@AfterThrowing(throwing = "ex",pointcut = "log()")
public void throwss(JoinPoint joinPoint,Exception ex){
log.info("异常通知-----------");
log.info("产生异常的方法:"+joinPoint);
log.info("错误类型:"+ex);
}
//后置通知
@After("log()")
public void after(JoinPoint joinPoint){
log.info("------------end----------------");
}
}
HelloController.java
@RestController
public class HelloController {
@RequestMapping("/helloAop")
public Object helloAop(){
return "helloAop";
}
@RequestMapping("/helloError")
public Object helloError(){
return 1/0;
}
}
运行项目,先访问http://localhost:8080/helloAop
访问http://localhost:8080/helloError
参考文章:SpringBoot3切面AOP实现权限检验
SpringBoot的AOP使用
8.异步编程注解Async
AsyncService.java
@Service
@Slf4j
public class AsyncService {
@Async
public void doTask1(){
try{
long t1=System.currentTimeMillis();
Thread.sleep(2000L);
long t2=System.currentTimeMillis();
log.info("线程{}用时{}",Thread.currentThread().getName(),t2-t1);
}catch (Exception e){
e.printStackTrace();
}
}
@Async
public void doTask2(){
try{
long t1=System.currentTimeMillis();
Thread.sleep(3000L);
long t2=System.currentTimeMillis();
log.info("线程{}用时{}",Thread.currentThread().getName(),t2-t1);
}catch (Exception e){
e.printStackTrace();
}
}
}
AsyncController.java
@RestController
@Slf4j
public class AsyncController {
@Autowired
private AsyncService asyncService;
@RequestMapping("/doTask")
public void doTask(){
long t1=System.currentTimeMillis();
asyncService.doTask1();
asyncService.doTask2();
try{
Thread.sleep(1000L);
}catch (Exception e){
e.printStackTrace();
}
long t2=System.currentTimeMillis();
log.info("线程{}用时{}",Thread.currentThread().getName(),t2-t1);
}
}
运行项目,访问/doTask
发现虽然有Async注解, 但是还是同步进行,原因是我们没有添加配置类
AsyncConfiguration.java
@EnableAsync
@Configuration
public class AsyncConfiguration {
}
重新运行,访问doTask
不过现在还存在问题,因为默认的是直接创建线程,也就是说,当用户很多而且同时发出请求时,有可能因为创建太多线程而导致内存不足,我们修改配置类
@EnableAsync
@Configuration
public class AsyncConfiguration implements AsyncConfigurer{
//核心线程数
private final static int CORE_NUM=5;
//最大线程数
private final static int POOL_NUM=10;
//队列容量
private final static int QUEUE_CAPACITY=50;
//存活时间
private final static int ALIVE_TIME=200;
//线程前缀名
private final static String PRE_NAME="myThread_";
@Bean
public ThreadPoolTaskExecutor executor(){
ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_NUM);
executor.setMaxPoolSize(POOL_NUM);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(ALIVE_TIME);
executor.setThreadNamePrefix(PRE_NAME);
//设置当缓存队列已满并且线程中的线程数目达到MAX_NUM时,采取的行动
/**
* 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
9.邮件发送
创建一个Springboot姓名,引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--邮件服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
application.yml
spring:
redis:
username: root
host: localhost
port: 6379
database: 0
jedis:
pool:
max-idle: 8
min-idle: 0
max-wait: 1000ms
timeout: 5000ms
mail:
username: 2827523200@qq.com
password: 邮箱验证后的密码
host: smtp.qq.com
properties:
mail:
smtp:
ssl:
enable: true
default-encoding: UTF-8
server:
port: 8081
MailProperties.java
@ConfigurationProperties(prefix = "spring.mail")
@Component
public class MailProperties {
private String username;
public void setUsername(String username){
this.username=username;
}
public String getUsername(){
return this.username;
}
}
CodeUtil.java验证码工具类
public class CodeUtil {
public static String getCode(int n){
char[]chars="0123456789".toCharArray();
StringBuilder sb=new StringBuilder();
for(int i=0;i<n;i++){
sb.append(new Random().nextInt(chars.length));
}
return sb.toString();
}
}
RedisUtil.java
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate redisTemplate;
private String PRE="";
public <K,V> boolean set(K key,V value,long time){
if(key==null){
return false;
}
boolean res=false;
try{
redisTemplate.opsForValue().set(PRE+key, JSONObject.toJSONString(value),time, TimeUnit.SECONDS);
res=true;
}catch (Exception e){
e.printStackTrace();
}
return res;
}
public <K,V> V get(K key,Class<V>clazz){
if(key==null){
return null;
}
String value=redisTemplate.opsForValue().get(PRE+key);
return JSONObject.parseObject(value,clazz);
}
}
MailController.java
@RestController
public class MailController {
@Autowired
private JavaMailSenderImpl mailSender;
@Resource
private RedisUtil redisUtil;
@Resource
private MailProperties mailProperties;
@RequestMapping(value = "/sendMail",method = RequestMethod.POST)
public String sendMail(@RequestParam(value = "to",required = true)String to)throws Exception{
MimeMessage message=mailSender.createMimeMessage();
MimeMessageHelper helper=new MimeMessageHelper(message);
helper.setFrom(mailProperties.getUsername());
helper.setSubject("发送验证码");
helper.setTo(to);
String code= CodeUtil.getCode(4);
helper.setText("验证码为:<h2>"+code+"</h2>请勿泄露");
mailSender.send(message);
redisUtil.set("code",code,60);
return "发送成功";
}
@RequestMapping(value = "/verify",method = RequestMethod.POST)
public String verify(@RequestParam(value = "code",required = true)String code){
String code1=redisUtil.get("code",String.class);
if(code1==null||code1==""){
return "验证码已过期";
}
if(code1.equals(code)){
return "验证成功";
}else{
return "验证失败";
}
}
}
运行项目
打开QQ邮箱
参考文章:SpringBoot实现邮箱验证
10.SpringBoot整合Redis
在上一个案例中,我们看到一个RedisUtil工具类,Redis是一个缓存中间件,接下来我们简单介绍一下Redis的一些使用操作
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
下面简单介绍一个StringRedisTemplate的实例,我们在Redis中,以String类型进行存储,如果要存储的对象不是String类型,我们通过JSONObject.toString()进行转换
RedisUtil.java
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate redisTemplate;
private final String PRE="";
public <K,V> boolean set(K key,V value){
boolean res=false;
try{
redisTemplate.opsForValue().set(PRE+key,JSONObject.toJSONString(value));
res=true;
}catch (Exception e){
e.printStackTrace();
}
return res;
}
public <K,V> boolean set(K key,V value,long time){
if(time<0){
return this.set(key,value);
}
boolean res=false;
try{
redisTemplate.opsForValue().set(PRE+key,JSONObject.toJSONString(value),time,TimeUnit.SECONDS);
res=true;
}catch (Exception e){
e.printStackTrace();
}
return res;
}
public <K,V> V get(K key,Class<V>clazz){
if(key==null){
return null;
}
try{
String value=redisTemplate.opsForValue().get(PRE+key);
return JSONObject.parseObject(value,clazz);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public <K,V> List<V> getList(K key,Class<V>clazz){
if(key==null){
return Collections.emptyList();
}
try{
String value=redisTemplate.opsForValue().get(PRE+key);
List<V>list=JSONObject.parseArray(value,clazz);
return list;
}catch (Exception e){
e.printStackTrace();
return Collections.emptyList();
}
}
public <K> boolean setExpire(K key,long time){
boolean res=false;
if(key==null){
return false;
}
try{
res=redisTemplate.expire(PRE+key,time,TimeUnit.SECONDS);
res=true;
}catch (Exception e){
e.printStackTrace();
}
return res;
}
public <K> long getExpire(K key){
return redisTemplate.getExpire(PRE+key);
}
public <K> boolean del(K key){
try{
redisTemplate.delete(PRE+key);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public <K> boolean hasKey(K key){
if(key==null){
return false;
}
try{
return redisTemplate.hasKey(PRE+key);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
}
测试类:
@SpringBootTest
class Demo19ApplicationTests {
@Autowired
private RedisUtil redisUtil;
@Test
public void set(){
boolean res=redisUtil.set("num",100);
System.out.println(res);
res=redisUtil.set("str","helloWorld",100);
System.out.println(res);
res=redisUtil.set("expire","Expire",10);
System.out.println(res);
List<String>list=new ArrayList<>();
list.add("one");
list.add("two");
list.add("three");
res=redisUtil.set("list",list);
System.out.println(res);
}
@Test
public void get(){
Integer num=redisUtil.get("num",Integer.class);
System.out.println(num);
String str=redisUtil.get("str",String.class);
System.out.println(str);
long time=redisUtil.getExpire("str");
System.out.println(time);
String expire=redisUtil.get("expire",String.class);
System.out.println(expire);
List<String>list=redisUtil.getList("list",String.class);
for(String s:list){
System.out.print(s+" ");
}
System.out.println();
}
@Test
void contextLoads() {
}
}
先测试set方法
10秒后打开RedisDesktopManager,因为expire设置存活时间是10秒,所以看不到
测试get方法
StringRedisTemplate的使用相对比较简单,但是每次都需要将数据转为String类型进行存储,不太方便,而且对数组等类型的支持也不太方便,所以接下来我们介绍RedisTemplate的序列化存储
RedisConfiguration.java
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String,Object>redisTemplateBean(RedisConnectionFactory connectionFactory){
RedisTemplate<String,Object>redisTemplate=new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<>(Object.class);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
ObjectMapper om=new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//设置序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
UserInfo.java,注意,要存储的对象必须要实现序列化接口
@Data
public class UserInfo implements Serializable {
private Integer id;
private String username;
private String password;
}
RedisUtil.java
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,Object>redisTemplate;
private final String PRE="";
public boolean set(Object key,Object value){
if(key==null){
return false;
}
try{
redisTemplate.opsForValue().set(PRE+key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public boolean set(Object key,Object value,long time){
if(time<=0){
return this.set(key,value);
}
if(key==null){
return false;
}
try{
redisTemplate.opsForValue().set(PRE+key,value,time,TimeUnit.SECONDS);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public boolean setExpire(Object key,long time){
if(key==null){
return false;
}
try{
return redisTemplate.expire(PRE+key,time,TimeUnit.SECONDS);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public long getExpire(Object key){
if(key==null){
return -1;
}
try{
return redisTemplate.getExpire(PRE+key,TimeUnit.SECONDS);
}catch (Exception e){
e.printStackTrace();
return -1;
}
}
public Object get(Object key){
if(key==null){
return null;
}
try{
return redisTemplate.opsForValue().get(PRE+key);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
public boolean hasKey(Object key){
if(key==null){
return false;
}
try{
return redisTemplate.hasKey(PRE+key);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public boolean del(Object key){
if(key==null){
return false;
}
try{
return redisTemplate.delete(PRE+key);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public boolean leftPush(Object key,Object value){
if(key==null){
return false;
}
try{
redisTemplate.opsForList().leftPush(PRE+key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public boolean rightPush(Object key,Object value){
if(key==null){
return false;
}
try{
redisTemplate.opsForList().rightPush(PRE+key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
public Object getList(Object key){
List<Object>list=Collections.emptyList();
try{
list=redisTemplate.opsForList().range(PRE+key,0,-1);
}catch (Exception e){
e.printStackTrace();
}
return list;
}
}
测试类
@SpringBootTest
class Demo19ApplicationTests {
@Autowired
private RedisUtil redisUtil;
@Test
public void set(){
UserInfo userInfo=new UserInfo();
userInfo.setId(1);
userInfo.setUsername("zhangsan");
userInfo.setPassword("123456");
boolean res=redisUtil.set("userInfo",userInfo);
System.out.println(res);
for(int i=0;i<4;i++){
UserInfo userInfo1=new UserInfo();
userInfo1.setId(i+1);
userInfo1.setUsername("admin"+i);
userInfo1.setPassword("12345"+i);
redisUtil.leftPush("userList",userInfo1);
}
}
@Test
public void get(){
UserInfo userInfo=(UserInfo) redisUtil.get("userInfo");
System.out.println(userInfo);
List<UserInfo>list=(List<UserInfo>) redisUtil.getList("userList");
for(UserInfo userInfo1:list){
System.out.println(userInfo1);
}
}
@Test
void contextLoads() {
}
}
先测试set
再测试get
参考文章:SpringBoot整合Redis使用介绍
获取数据时,直接访问数据库是比较耗时的工作,我们可以将数据存入Redis缓存中间件中,从而提高访问速度,接下来,我们来将Redis和mysql进行一个整合
在上一个案例的基础上,我们在pom.xml添加下列信息
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
修改UserInfo.java
@Data
@TableName(value = "user")
public class UserInfo implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private Integer age;
}
修改application.yml
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
jedis:
pool:
max-idle: 8
min-idle: 0
max-wait: 1000ms
timeout: 5000ms
datasource:
username: root
password: 数据库密码
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml
ResultVO.java
/**
* @param <T>
* code 表示状态码
* msg 表示提示信息
* data 表示返回的数据
*/
@Data
public class ResultVO <T>{
private Integer code;
private String msg;
private T data;
public ResultVO(){}
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
this.data=null;
}
public ResultVO(Integer code,String msg,T data){
this.code=code;
this.msg=msg;
this.data=data;
}
}
ResultVOUtil.java
public class ResultVOUtil {
public static ResultVO success(){
return new ResultVO<>(200,"操作成功");
}
public static ResultVO success(Object data){
return new ResultVO<>(200,"操作成功",data);
}
public static ResultVO error(Integer code,String msg){
return new ResultVO<>(code,msg);
}
}
UserInfoMapper.java
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
UserInfoService.java
public interface UserService {
ResultVO saveUser(JSONObject jsonObject);
ResultVO updateUser(JSONObject jsonObject);
ResultVO deleteUser(Integer id);
ResultVO getUserById(Integer id);
ResultVO getUserList();
}
UserInfoServiceImpl.java
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoMapper userInfoMapper;
@Resource
private RedisUtil redisUtil;
private final String PRE="userInfo_";
private final String USERINFOLIST="userInfoList";
@Override
public ResultVO saveUser(JSONObject jsonObject) {
try{
String username=jsonObject.getString("username");
String password=jsonObject.getString("password");
Integer age=jsonObject.getInteger("age");
UserInfo userInfo=new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password);
userInfo.setAge(age);
int addNum=userInfoMapper.insert(userInfo);
if (addNum <= 0) {
return ResultVOUtil.error(403,"添加用户失败");
}
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",userInfo.getId());
//将用户信息放入缓存
redisUtil.set(PRE+userInfo.getId(),userInfo);
//更新缓存列表
this.updateUserList();
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"添加用户失败");
}
}
@Override
public ResultVO updateUser(JSONObject jsonObject) {
try{
String password=jsonObject.getString("password");
Integer id=jsonObject.getInteger("id");
UserInfo userInfo=new UserInfo();
userInfo.setId(id);
userInfo.setPassword(password);
UpdateWrapper<UserInfo>wrapper=new UpdateWrapper<>();
wrapper.eq("id",id);
int updateNum=userInfoMapper.update(userInfo,wrapper);
if (updateNum <= 0) {
return ResultVOUtil.error(403,"修改用户失败");
}
Map<String,Object>res=new HashMap<>();
res.put("updateNum",updateNum);
//修改缓存
userInfo=userInfoMapper.selectById(id);
//更新用户信息缓存
redisUtil.set(PRE+userInfo.getId(),userInfo);
//更新缓存列表
this.updateUserList();
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"修改用户失败");
}
}
@Override
public ResultVO deleteUser(Integer id) {
try{
int deleteNum=userInfoMapper.deleteById(id);
if(deleteNum<=0){
return ResultVOUtil.error(403,"删除用户失败");
}
this.updateUserList();
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",deleteNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"删除用户失败");
}
}
@Override
public ResultVO getUserById(Integer id) {
try{
//先从redis中获取
UserInfo userInfo=(UserInfo) redisUtil.get(PRE+id);
if(userInfo==null){
System.out.println("从数据库中获取");
userInfo=userInfoMapper.selectById(id);
//获取成功后存入缓存
if(userInfo!=null){
redisUtil.set(PRE+userInfo.getId(),userInfo);
}
}else{
System.out.println("从缓存中获取");
}
return ResultVOUtil.success(userInfo);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"缓存用户详情失败");
}
}
@Override
public ResultVO getUserList() {
try{
//先从缓存获取
List<UserInfo>userInfoList=(List<UserInfo>) redisUtil.getList(USERINFOLIST);
if(userInfoList==null){
System.out.println("从数据库中获取");
userInfoList=userInfoMapper.selectList(null);
//存入缓存
if(userInfoList!=null){
for(UserInfo userInfo:userInfoList){
redisUtil.rightPush(USERINFOLIST,userInfo);
}
}
}else{
System.out.println("从缓存中获取用户列表");
}
return ResultVOUtil.success(userInfoList);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"获取用户列表失败");
}
}
@Async
private void updateUserList(){
System.out.println("更新缓存");
List<UserInfo>userInfoList=userInfoMapper.selectList(null);
for(UserInfo userInfo:userInfoList){
redisUtil.rightPush(USERINFOLIST,userInfo);
}
}
}
AsyncConfiguration.java
@Configuration
@EnableAsync
public class AsyncConfiguration {
}
UserInfoController.java
@RestController
@Slf4j
public class UserInfoController {
@Autowired
private UserInfoService userInfoService;
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public ResultVO saveUser(@RequestBody JSONObject jsonObject){
log.info("--------saveUser----------");
return userInfoService.saveUser(jsonObject);
}
@RequestMapping(value = "/updateUser",method = RequestMethod.PUT)
public ResultVO updateUser(@RequestBody JSONObject jsonObject){
log.info("--------updateUser----------");
return userInfoService.updateUser(jsonObject);
}
@RequestMapping(value = "/deleteUser",method = RequestMethod.DELETE)
public ResultVO deleteUser(@RequestParam(value = "id",required = true)Integer id){
log.info("----------deleteUser-----------");
return userInfoService.deleteUser(id);
}
@RequestMapping(value = "/getUserById",method = RequestMethod.GET)
public ResultVO getUserById(@RequestParam(value = "id",required = true)Integer id) {
log.info("----------getUserById-----------");
return userInfoService.getUserById(id);
}
@RequestMapping(value = "/getUserList",method = RequestMethod.GET)
public ResultVO getUserList(){
log.info("---------getUserList----------");
return userInfoService.getUserList();
}
}
修改启动类
@SpringBootApplication
@MapperScan("com.young.demo19.mapper")
public class Demo19Application {
public static void main(String[] args) {
SpringApplication.run(Demo19Application.class, args);
}
}
数据库内容:
运行项目
这里添加、修改、删除后都要更新缓存,我们查看控制台如下,确实更新了三次缓存
第一次获取id=2的用户信息
第二次获取id=2的用户信息
获取用户列表
Redis还有一个发布订阅的功能
创建一个springboot项目
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
MessageReceiver.java
@Service
public class MessageReceiver {
public void receiveMessage(String message){
System.out.println("接收信息:"+message);
}
}
RedisConfig.java
@Configuration
public class RedisConfig {
//订阅的频道名
private static String topic="chat";
@Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
return new StringRedisTemplate(redisConnectionFactory);
}
//配置监听适配器
@Bean
public MessageListenerAdapter messageListenerAdapter(MessageReceiver receiver){
MessageListenerAdapter adapter=new MessageListenerAdapter(receiver,"receiveMessage");
return adapter;
}
//配置监听容器
@Bean
public RedisMessageListenerContainer getRedisMessageListenerContainer(RedisConnectionFactory connectionFactory,MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container=new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//订阅主题,可以将多个频道订阅到一个容器中
container.addMessageListener(listenerAdapter,new PatternTopic(RedisConfig.topic));
return container;
}
}
MessageController.java
@RestController
public class MessageController {
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/sendMessage")
public void sendMessage(String message){
//chat是订阅的频道名
redisTemplate.convertAndSend("chat",message+new Date());
}
}
application.yml
server:
port: 8081
spring:
redis:
host: localhost
port: 6379
database: 0
lettuce:
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: -1
运行项目:
参考文章:SpringBoot整合Redis实现发布订阅功能
11.SpringBoot整合MyBatis-plus
在上一个案例中,我们没有使用Mybatis来访问数据库,而是使用Mybatis-plus,Mybatis-plus是mybatis的升级版,mybatis有的,Mybatis-plus都有,而且Mybatis-plus对单表的查询更加的方便,不用写那么多sql语句
创建一个Springboot项目,引入下列依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
application.yml
spring:
datasource:
username: root
password: 数据库密码
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
下面是建表的SQL语句:
CREATE TABLE `t_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`version` int NOT NULL DEFAULT '0',
`is_delete` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
User.java
@TableName(value = "t_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
@TableField(value = "create_time",fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@Version
private Integer version;
@TableLogic
@TableField(value = "is_delete")
private Integer isDelete;
}
UserMapper.java
public interface UserMapper extends BaseMapper<User> {
Page<User>getUserList(Page<User>pages);
}
UserMapper.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.young.demo19.mapper.UserMapper">
<select id="getUserList" resultType="com.young.demo19.entity.User">
select *
from t_user
where is_delete=0
</select>
</mapper>
在启动类添加注解
@SpringBootApplication
@MapperScan("com.young.demo19.mapper")
public class Demo19Application {
public static void main(String[] args) {
SpringApplication.run(Demo19Application.class, args);
}
}
UserService.java
public interface UserService {
ResultVO saveUser(User user);
ResultVO updateUser(User user);
ResultVO deleteUser(Integer id);
ResultVO getUserById(Integer id);
ResultVO getUserList(Integer pageNum,Integer pageSize);
}
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public ResultVO saveUser(User user) {
try{
int addNum=userMapper.insert(user);
if(addNum<=0){
return ResultVOUtil.error(403,"添加失败");
}
Integer id=user.getId();
Map<String,Object>res=new HashMap<>();
res.put("id",id);
res.put("addNum", addNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"添加失败");
}
}
@Override
public ResultVO updateUser(User user) {
try{
int updateNum=userMapper.updateById(user);
if(updateNum<=0){
return ResultVOUtil.error(403,"修改失败");
}
Map<String,Object>res=new HashMap<>();
res.put("updateNum",updateNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"修改失败");
}
}
@Override
public ResultVO deleteUser(Integer id) {
try{
int deleteNum=userMapper.deleteById(id);
if(deleteNum<=0){
return ResultVOUtil.error(403,"删除失败");
}
Map<String,Object>res=new HashMap<>();
res.put("deleteNum",deleteNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"删除失败");
}
}
@Override
public ResultVO getUserById(Integer id) {
try{
User user=userMapper.selectById(id);
return ResultVOUtil.success(user);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"获取用户信息失败");
}
}
@Override
public ResultVO getUserList(Integer pageNum, Integer pageSize) {
try{
Page<User>pages=new Page<>(pageNum,pageSize);
pages=userMapper.getUserList(pages);
List<User>userList=pages.getRecords();
Long total=pages.getTotal();
Map<String,Object>res=new HashMap<>();
res.put("total",total);
res.put("list",userList);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
return ResultVOUtil.error(403,"获取用户列表失败");
}
}
}
MyMetaObjectHandler.java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
MybatisPlusConfiguration.java
@Configuration
public class MybatisPlusConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
//配置分页
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//配置乐观锁
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
ResultVO.java
@Data
public class ResultVO <T>{
private Integer code;
private String msg;
private T data;
public ResultVO(){}
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
this.data=null;
}
public ResultVO(Integer code,String msg,T data){
this.code=code;
this.msg=msg;
this.data=data;
}
}
ResultVOUtil.java
public class ResultVOUtil {
public static ResultVO success(){
return new ResultVO<>(200,"操作成功");
}
public static ResultVO success(Object data){
return new ResultVO<>(200,"操作成功",data);
}
public static ResultVO error(Integer code,String msg){
return new ResultVO<>(code,msg);
}
}
UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public ResultVO saveUser(@RequestBody User user){
return userService.saveUser(user);
}
@RequestMapping(value = "/updateUser",method = RequestMethod.PUT)
public ResultVO updateUser(@RequestBody User user){
return userService.updateUser(user);
}
@RequestMapping(value = "/deleteUser",method = RequestMethod.DELETE)
public ResultVO deleteUser(@RequestParam(value = "id",required = true)Integer id){
return userService.deleteUser(id);
}
@RequestMapping(value = "/getUserById",method = RequestMethod.GET)
public ResultVO getUserById(@RequestParam(value = "id",required = true)Integer id){
return userService.getUserById(id);
}
@RequestMapping(value = "/getUserList",method = RequestMethod.GET)
public ResultVO getUserList(@RequestParam(value = "pageNum",required = true)Integer pageNum,
@RequestParam(value = "pageSize",required = true)Integer pageSize){
return userService.getUserList(pageNum,pageSize);
}
}
运行项目
先添加多条数据
修改用户信息
删除用户
因为我们用TableLogic来定义逻辑删除,所以数据不会被物理删除,0表示存在,1表示删除
获取用户信息
获取用户列表
上面涉及的都是单表操作,多表操作的话,就我个人而言,还是使用xml文件来进行书写sql语句进行多表操作,这里就不累述了,有兴趣的可以参考之前提到的SpringBoot整合Mybatis的案例
下面来介绍Mybatis-plus的事务操作
修改saveUser方法
@Override
@Transactional(rollbackFor = {RuntimeException.class,Exception.class})
public ResultVO saveUser(User user) {
try{
int addNum=userMapper.insert(user);
if(addNum<=0){
throw new RuntimeException();
}
Integer id=user.getId();
Map<String,Object>res=new HashMap<>();
res.put("id",id);
res.put("addNum", addNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
//事务回顾
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ResultVOUtil.error(403,"添加失败");
}
}
添加数据
操作成功,不回滚,接着我们试着抛出一个错误
修改saveUser方法
@Override
@Transactional(rollbackFor = {RuntimeException.class,Exception.class})
public ResultVO saveUser(User user) {
try{
int addNum=userMapper.insert(user);
int i=1/0;
if(addNum<=0){
throw new RuntimeException();
}
Integer id=user.getId();
Map<String,Object>res=new HashMap<>();
res.put("id",id);
res.put("addNum", addNum);
return ResultVOUtil.success(res);
}catch (Exception e){
e.printStackTrace();
//事务回顾
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return ResultVOUtil.error(403,"添加失败");
}
}
添加数据
查看数据库,发现确实回滚了
参考文章:SpringBoot整合Mybatis-plus
mybatis-plus超详细讲解
12. httpClient
在实际项目中,我们可能需要在代码中发起http请求,而httpClient就是用来协作我们进行http请求的
我们先修改上一个案例的代码,修改UserController.java,我们等下要用httpClient来访问它
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserMapper userMapper;
@RequestMapping(value = "/saveUser",method = RequestMethod.POST)
public ResultVO saveUser(@RequestBody User user){
return userService.saveUser(user);
}
@RequestMapping(value = "/updateUser",method = RequestMethod.PUT)
public ResultVO updateUser(@RequestBody User user){
return userService.updateUser(user);
}
@RequestMapping(value = "/deleteUser",method = RequestMethod.DELETE)
public ResultVO deleteUser(@RequestParam(value = "id",required = true)Integer id){
return userService.deleteUser(id);
}
@RequestMapping(value = "/getUserById",method = RequestMethod.GET)
public ResultVO getUserById(@RequestParam(value = "id",required = true)Integer id){
return userService.getUserById(id);
}
@RequestMapping(value = "/getUserList",method = RequestMethod.GET)
public ResultVO getUserList(@RequestParam(value = "pageNum",required = true)Integer pageNum,
@RequestParam(value = "pageSize",required = true)Integer pageSize){
return userService.getUserList(pageNum,pageSize);
}
@RequestMapping(value = "/getUserList",method = RequestMethod.POST)
public ResultVO getUserList1(@RequestParam(value = "pageNum",required = true)Integer pageNum,
@RequestParam(value = "pageSize",required = true)Integer pageSize){
return userService.getUserList(pageNum,pageSize);
}
@RequestMapping(value = "/getUserById1",method = RequestMethod.POST)
public ResultVO getUserById1(@RequestParam(value = "id",required = true)Integer id){
return userService.getUserById(id);
}
@RequestMapping(value = "/getUserList1",method = RequestMethod.GET)
public ResultVO getUserList1(){
QueryWrapper<User>wrapper=new QueryWrapper<>();
wrapper.eq("is_delete",0);
List<User>list=userMapper.selectList(wrapper);
return ResultVOUtil.success(list);
}
@RequestMapping(value = "/getUserList2",method = RequestMethod.POST)
public ResultVO getUserList2(){
QueryWrapper<User>wrapper=new QueryWrapper<>();
wrapper.eq("is_delete",0);
List<User>list=userMapper.selectList(wrapper);
return ResultVOUtil.success(list);
}
@RequestMapping(value = "/saveUser1",method = RequestMethod.POST)
public ResultVO saveUser1(@RequestBody JSONObject jsonObject){
String username=jsonObject.getString("username");
String password=jsonObject.getString("password");
User user=new User();
user.setUsername(username);
user.setPassword(password);
int addNum=userMapper.insert(user);
Map<String,Object>res=new HashMap<>();
res.put("addNum",addNum);
res.put("id",user.getId());
return ResultVOUtil.success(res);
}
}
创建一个SpringBoot项目(注意,这个项目和上一个案例不是同一个项目),导入下列依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--httpclient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
测试Get无参请求
@Test
public void doGetTest1(){
//Http客户端
CloseableHttpClient httpClient= HttpClientBuilder.create().build();
//get请求
HttpGet httpGet=new HttpGet("http://localhost:8080/getUserList1");
//响应模型
CloseableHttpResponse response=null;
try{
response=httpClient.execute(httpGet);
if(response!=null){
System.out.println("响应状态:"+response.getStatusLine());
HttpEntity responseEntity=response.getEntity();
System.out.println("响应内容长度:"+responseEntity.getContentLength());
System.out.println("响应内容:"+ EntityUtils.toString(responseEntity));
}
}catch (ClientProtocolException e){
e.printStackTrace();
}catch (ParseException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(httpClient!=null){
httpClient.close();
}
if(response!=null){
response.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
测试get有参请求
@Test
public void doGetTest2(){
//获取http客户端
CloseableHttpClient httpClient=HttpClientBuilder.create().build();
CloseableHttpResponse response=null;
//参数
StringBuffer param=new StringBuffer();
try{
//对字符编码
param.append("id="+ URLEncoder.encode("1","utf-8"));
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
//get请求
HttpGet httpGet=new HttpGet("http://localhost:8080/getUserById?"+param);
try{
//配置
RequestConfig requestConfig=RequestConfig.custom()
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(5000)
.build();
httpGet.setConfig(requestConfig);
response=httpClient.execute(httpGet);
System.out.println("响应状态:"+response.getStatusLine());
HttpEntity responseEntity=response.getEntity();
if(responseEntity!=null){
System.out.println("响应内容长度:"+responseEntity.getContentLength());
System.out.println("响应内容:"+EntityUtils.toString(responseEntity));
}
}catch (ClientProtocolException e){
e.printStackTrace();
}catch (ParseException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(httpClient!=null){
httpClient.close();
}
if(response!=null){
response.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
测试post无参请求
@Test
public void doPostTest1(){
CloseableHttpClient httpClient=HttpClientBuilder.create().build();
HttpPost httpPost=new HttpPost("http://localhost:8080/getUserList2");
CloseableHttpResponse response=null;
try{
response=httpClient.execute(httpPost);
HttpEntity httpEntity =response.getEntity();
System.out.println("响应状态:"+response.getStatusLine());
if(httpEntity!=null){
System.out.println("响应内容长度:"+httpEntity.getContentLength());
System.out.println("响应内容:"+EntityUtils.toString(httpEntity));
}
}catch (ClientProtocolException e){
e.printStackTrace();
}catch (ParseException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(httpClient!=null){
httpClient.close();
}
if(response!=null){
response.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
测试POST有参请求
@Test
public void doPostTest2(){
CloseableHttpClient httpClient=HttpClientBuilder.create().build();
StringBuffer param=new StringBuffer();
try{
param.append("pageNum="+URLEncoder.encode("1","utf-8"));
param.append("&");
param.append("pageSize=4");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
HttpPost httpPost=new HttpPost("http://localhost:8080/getUserList?"+param);
httpPost.setHeader("Content-Type","application/json;charset=utf8");
CloseableHttpResponse response=null;
try{
response=httpClient.execute(httpPost);
HttpEntity httpEntity=response.getEntity();
System.out.println("响应状态:"+response.getStatusLine());
if(httpEntity!=null){
System.out.println("响应内容长度:"+httpEntity.getContentLength());
System.out.println("响应内容:"+EntityUtils.toString(httpEntity));
}
}catch (ClientProtocolException e){
e.printStackTrace();
}catch (ParseException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(httpClient!=null){
httpClient.close();
}
if(response!=null){
response.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
测试post请求体请求
@Test
public void doPostTest3(){
CloseableHttpClient httpClient=HttpClientBuilder.create().build();
JSONObject jsonObject=new JSONObject();
jsonObject.put("username","hahaha");
jsonObject.put("password","115116");
String json= jsonObject.toString();
HttpPost httpPost=new HttpPost("http://localhost:8080/saveUser1");
CloseableHttpResponse response=null;
try{
StringEntity entity=new StringEntity(json,"UTF-8");
httpPost.setEntity(entity);
httpPost.setHeader("Content-Type","application/json;charset=utf8");
response=httpClient.execute(httpPost);
System.out.println("响应状态:"+response.getStatusLine());
HttpEntity responseEntity=response.getEntity();
if(responseEntity!=null){
System.out.println("响应内容长度为:"+responseEntity.getContentLength());
System.out.println("响应内容:"+EntityUtils.toString(responseEntity));
}
}catch (ClientProtocolException e){
e.printStackTrace();
}catch (ParseException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(httpClient!=null){
httpClient.close();
}
if(response!=null){
response.close();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
接下来我们将刚才的请求过程封装为一个工具类
先创建一个接收类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HttpClientResult implements Serializable {
public HttpClientResult(Integer code){
this.code=code;
this.content=null;
}
//响应状态码
private Integer code;
//响应数据
private String content;
}
HttpClientUtil.java工具类:
public class HttpClientUtil {
private static final String ENCODING="UTF-8";
private static final int CONNECT_TIMEOUT=5000;
private static final int CONNECT_REQUEST_TIMEOUT=5000;
private static final int SOCKET_TIMEOUT=5000;
public static HttpClientResult doGet(String url)throws Exception{
return doGet(url,null,null);
}
public static HttpClientResult doGet(String url, Map<String,String>params)throws Exception{
return doGet(url,null,params);
}
public static HttpClientResult doGet(String url,Map<String,String>headers,Map<String,String>params)throws Exception{
//创建httpClient
CloseableHttpClient httpClient= HttpClientBuilder.create().build();
//创建访问地址
URIBuilder uriBuilder=new URIBuilder(url);
params.forEach((k,v)->uriBuilder.setParameter(k,v));
//创建http对象
HttpGet httpGet=new HttpGet(uriBuilder.build());
//配置
RequestConfig requestConfig=RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setConnectionRequestTimeout(CONNECT_REQUEST_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
httpGet.setConfig(requestConfig);
//设置请求头
packageHeader(headers,httpGet);
//创建httpResponse对象
CloseableHttpResponse httpResponse=null;
try{
//执行请求
return getHttpClientResult(httpResponse,httpClient,httpGet);
}finally {
//释放资源
release(httpResponse,httpClient);
}
}
public static HttpClientResult doPost(String url)throws Exception{
return doPost(url,null,null);
}
public static HttpClientResult doPost(String url,Map<String,String>params)throws Exception{
return doPost(url,null,params);
}
public static HttpClientResult doPost(String url,Map<String,String>headers,Map<String,String>params)throws Exception{
//创建httpClient
CloseableHttpClient httpClient=HttpClientBuilder.create().build();
//创建http对象
HttpPost httpPost=new HttpPost(url);
RequestConfig requestConfig=RequestConfig
.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setConnectionRequestTimeout(CONNECT_REQUEST_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
httpPost.setConfig(requestConfig);
//设置请求头
packageHeader(headers,httpPost);
//封装请求参数
if(params!=null){
List<NameValuePair>list=new ArrayList<>();
params.forEach((k,v)->list.add(new BasicNameValuePair(k,v)));
httpPost.setEntity(new UrlEncodedFormEntity(list,ENCODING));
}
//响应结果
CloseableHttpResponse httpResponse=null;
try{
return getHttpClientResult(httpResponse,httpClient,httpPost);
}finally {
release(httpResponse,httpClient);
}
}
//封装头部
public static void packageHeader(Map<String,String>headers, HttpRequestBase httpMethod){
if(headers!=null){
headers.forEach((k,v)->httpMethod.setHeader(k,v));
}
}
//获取响应结果
public static HttpClientResult getHttpClientResult(CloseableHttpResponse httpResponse,
CloseableHttpClient httpClient,
HttpRequestBase httpMethod)throws Exception{
httpResponse=httpClient.execute(httpMethod);
if(httpResponse!=null&&httpResponse.getStatusLine()!=null){
String content="";
if(httpResponse.getEntity()!=null){
content= EntityUtils.toString(httpResponse.getEntity(),ENCODING);
}
return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(),content);
}
return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}
public static void release(CloseableHttpResponse httpResponse,CloseableHttpClient httpClient)throws Exception{
if(httpResponse!=null){
httpResponse.close();
}
if(httpClient!=null){
httpClient.close();
}
}
}
测试无参get
@Test
public void doGet(){
try{
HttpClientResult clientResult= HttpClientUtil.doGet("http://localhost:8080/getUserList1");
System.out.println(JSON.toJSONString(clientResult));
}catch (Exception e){
e.printStackTrace();
}
}
测试有参get
@Test
public void doGet1(){
try{
Map<String,String>param=new HashMap<>();
param.put("pageNum","1");
param.put("pageSize","2");
HttpClientResult clientResult= HttpClientUtil.doGet("http://localhost:8080/getUserList",param);
System.out.println(JSON.toJSONString(clientResult));
}catch (Exception e){
e.printStackTrace();
}
}
参考文章:
HttpClient信息使用示例
13.WebService
pom.xml依赖:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-bindings-soap</artifactId>
<version>3.5.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
实体类
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
}
Admin.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
private Integer id;
private String name;
}
service层
AdminService.java
@WebService
public interface AdminService {
@WebMethod
String getAdminList();
}
AdminServiceImpl.java
@Component
@WebService(targetNamespace = "http://service.demo15.young.com/", endpointInterface = "com.young.demo15.service.AdminService")
public class AdminServiceImpl implements AdminService {
@Override
public String getAdminList() {
List<Admin>list=new ArrayList<>();
list.add(new Admin(1,"admin1"));
list.add(new Admin(2,"admin2"));
return JSON.toJSONString(list);
}
}
UserService.java
@WebService
public interface UserService {
@WebMethod
String getUserList();
}
UserServiceImpl.java
@Component
@WebService(targetNamespace = "http://service.demo15.young.com/",endpointInterface = "com.young.demo15.service.UserService")
public class UserServiceImpl implements UserService {
@Override
public String getUserList() {
List<User>list=new ArrayList<>();
list.add(new User(1,"user1","pwd1"));
list.add(new User(2,"user2","pwd2"));
list.add(new User(3,"user3","pwd3"));
return JSONObject.toJSONString(list);
}
}
配置类,WebServiceConfig.java
@Configuration
public class WebServiceConfig {
@Bean(name = SpringBus.DEFAULT_BUS_ID)
public SpringBus springBus(){
return new SpringBus();
}
@Bean(name = "wbsBean")
public ServletRegistrationBean registrationBean(){
ServletRegistrationBean registrationBean=new ServletRegistrationBean<>(new CXFServlet(),"/webService/*");
return registrationBean;
}
@Bean
public Endpoint endpoint1(SpringBus springBus, UserService userService){
EndpointImpl endpoint=new EndpointImpl(springBus,userService);
endpoint.publish("/userServer");
System.out.println("http://localhost:8080/webService/userServer?wsdl");
return endpoint;
}
@Bean
public Endpoint endpoint2(SpringBus springBus, AdminService adminService){
EndpointImpl endpoint=new EndpointImpl(springBus,adminService);
endpoint.publish("/adminServer");
System.out.println("http://localhost:8080/webService/adminServer?wsdl");
return endpoint;
}
}
工具类ClientUtil.java
public class ClientUtil {
public static String getResult(String wsdUrl,String methodName,String...params)throws Exception{
JaxWsDynamicClientFactory proxyFactoryBean=JaxWsDynamicClientFactory.newInstance();
Client client=proxyFactoryBean.createClient(wsdUrl);
Object[]objects;
if(params==null||params.length==0){
objects=client.invoke(methodName);
}else{
objects=client.invoke(methodName,params);
}
return JSONObject.toJSONString(objects);
}
}
TestController.java
@RestController
public class TestController {
@RequestMapping("/getUserList")
public String getUserList(){
String wsdUrl="http://localhost:8080/webService/userServer?wsdl";
String methodName="getUserList";
try{
return ClientUtil.getResult(wsdUrl,methodName,null);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
@RequestMapping("/getAdminList")
public String getAdminList(){
String wsdUrl="http://localhost:8080/webService/adminServer?wsdl";
String methodName="getAdminList";
try{
return ClientUtil.getResult(wsdUrl,methodName,null);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
运行项目
访问http://localhost:8080/webService/userServer?wsdl
访问http://localhost:8080/webService/adminServer?wsdl
参考文章聊聊日常开发中,如何对接WebService
14.WebSocket
创建一个SpringBoot项目,引入下列依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--websocket-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
websocketConfig.java
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
MyWebSocket.java
@Component
@ServerEndpoint(value = "/webSocket/{userId}")
@Slf4j
public class MyWebSocket {
private Session session;
private String userId;
private static int onlineCount=0;
private static ConcurrentMap<String,MyWebSocket>map=new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session,@PathParam(value = "userId") String userId){
if(map.containsKey(userId)){
log.info("用户{}已在线",userId);
}else{
this.session=session;
this.userId=userId;
map.put(userId,this);
addOnlineCount();
log.info("用户{}进入连接,当前人数:{}",userId,getOnlineCount());
}
}
@OnClose
public void onClose(){
if(map.containsKey(this.userId)){
map.remove(this.userId);
subOnlineCount();
log.info("用户{}退出连接,当前人数:{}",this.userId,getOnlineCount());
}
}
@OnError
public void onError(Session session,Throwable throwable){
log.info("用户{}发送错误{}",this.userId,throwable.getMessage());
}
@OnMessage
public void onMessage(Session session,String message){
log.info("来自客户端用户{}的消息{}",this.userId,message);
}
public void sendMessage(String message)throws Exception{
this.session.getBasicRemote().sendText(message);
}
public void sendMessageTo(String userId,String message)throws Exception{
if (map.containsKey(userId)) {
log.info("发送消息{} 给用户{}",message,userId);
map.get(userId).sendMessage(message);
}
}
public void sendMessageAll(String message)throws Exception{
log.info("群发信息{}",message);
for(String id:map.keySet()){
map.get(id).sendMessage(message);
}
}
public void sendMessageByJson(JSONObject jsonObject)throws Exception{
String fromId=jsonObject.getString("fromId");
String toId=jsonObject.getString("toId");
String message=jsonObject.getString("message");
message=fromId+":"+message;
this.sendMessageTo(toId,message);
}
public synchronized static void addOnlineCount(){
onlineCount++;
}
public synchronized static void subOnlineCount(){
onlineCount--;
}
public synchronized static int getOnlineCount(){
return onlineCount;
}
}
WebSocketController.java
@RestController
public class WebSocketController {
@Autowired
private MyWebSocket myWebSocket;
@RequestMapping(value = "/sendMessageToId")
public void sendMessageToId(@RequestParam(value = "userId", required = true) String userId,
@RequestParam(value = "message", required = true) String message) throws Exception {
myWebSocket.sendMessageTo(userId, message);
}
@RequestMapping(value = "/sendMessageToAll")
public void sendMessageAll(@RequestParam(value = "message", required = true) String message) throws Exception {
myWebSocket.sendMessageAll(message);
}
@RequestMapping(value = "/sendMessage")
public void sendMessage(@RequestBody JSONObject jsonObject)throws Exception{
myWebSocket.sendMessageByJson(jsonObject);
}
}
运行项目,搜索http://websocket.jsonin.com/
输入ws://localhost:8080/webSocket/用户
参考文章:SpringBoot整合WebSocket
SpringBoot整合websocket
15.Shiro安全框架
创建一个SpringBoot项目,引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf-shiro-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!--shiro-cache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
实体类:Role.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_role")
public class Role implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
}
User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_user")
public class User implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String salt;
private Integer age;
private String email;
private String address;
@TableField(exist = false)
private List<Role>roles;
}
UserRoleId.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "t_user_role")
public class UserRoleId {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value ="user_id")
private Integer userId;
@TableField(value = "role_id")
private Integer roleId;
}
UserMapper.java
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("select * from t_user where username=#{username}")
User findUserByUsername(String username);
}
RoleMapper.java
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
@Select("select r.id,r.name from t_role r left join t_user_role ur on r.id=ur.role_id where ur.user_id=#{userId}")
List<Role>getRoleByUserId(Integer id);
}
UserRoleIdMapper.java
@Mapper
public interface UserRoleIdMapper extends BaseMapper<UserRoleId> {
}
UserServic.java
public interface UserService {
int register(User user);
User findUserByUsername(String username);
}
UserServiceImpl.java
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserRoleIdMapper userRoleIdMapper;
@Override
public int register(User user) {
String salt= SaltUtil.getSalt(ShiroConstant.SHIRO_LENGTH);
Md5Hash md5Hash=new Md5Hash(user.getPassword(),salt,ShiroConstant.SHIRO_ITERATORS);
String password=md5Hash.toHex();
user.setSalt(salt);
user.setPassword(password);
//添加用户
userMapper.insert(user);
//添加权限
UserRoleId userRoleId=new UserRoleId();
userRoleId.setUserId(user.getId());
userRoleId.setRoleId(2);
userRoleIdMapper.insert(userRoleId);
return 1;
}
@Override
public User findUserByUsername(String username) {
return userMapper.findUserByUsername(username);
}
}
RoleService.java
public interface RoleService {
List<Role>getRolesByUserId(Integer userId);
}
RoleServiceImpl.java
@Service("roleService")
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List<Role> getRolesByUserId(Integer userId) {
return roleMapper.getRoleByUserId(userId);
}
}
SaltUtil.java 生成加密盐的工具类
public class SaltUtil {
public static String getSalt(int n){
char[]chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890".toCharArray();
StringBuffer sf=new StringBuffer();
for(int i=0;i<n;i++){
int j=new Random().nextInt(chars.length);
sf.append(chars[j]);
}
return sf.toString();
}
}
CustomerByteSource.java,将其他类型转换为byte类型
public class CustomerByteSource implements ByteSource {
private byte[]bytes;
public CustomerByteSource(byte[]bytes){
this.bytes=bytes;
}
public CustomerByteSource(char[]chars){
this.bytes= CodecSupport.toBytes(chars);
}
public CustomerByteSource(String str){
this.bytes=CodecSupport.toBytes(str);
}
public CustomerByteSource(ByteSource byteSource){
this.bytes=byteSource.getBytes();
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public String toHex() {
return Hex.encodeToString(bytes);
}
@Override
public String toBase64() {
return Base64.encodeToString(bytes);
}
@Override
public boolean isEmpty() {
return bytes==null||bytes.length==0;
}
}
ApplicationContextUtil.java
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context=applicationContext;
}
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}
ShiroConstant.java,定义一些常量
public class ShiroConstant {
public final static int SHIRO_ITERATORS=1024;
public final static String SHIRO_ALGORITHM="MD5";
public final static int SHIRO_LENGTH=8;
}
CustomerRealm.java自定义realm
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String principal=(String)principalCollection.getPrimaryPrincipal();
UserService userService=(UserService) ApplicationContextUtil.getBean("userService");
User user=userService.findUserByUsername(principal);
if(!Objects.isNull(user)){
RoleService roleService=(RoleService) ApplicationContextUtil.getBean("roleService");
List<Role>roles=roleService.getRolesByUserId(user.getId());
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
roles.forEach((role)->{
authorizationInfo.addRole(role.getName());
});
return authorizationInfo;
}
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal=(String) authenticationToken.getPrincipal();
//获取用户
UserService userService=(UserService) ApplicationContextUtil.getBean("userService");
User user=userService.findUserByUsername(principal);
if(user==null){
return null;
}
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new CustomerByteSource(user.getSalt()),this.getName());
}
}
ShiroConfiguration.java shiro的配置类
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String,String>map=new HashMap<>();
//设置受限资源和开放资源
map.put("/index","authc");
map.put("/login","anon");
map.put("/register","anon");
filterFactoryBean.setLoginUrl("/login");
filterFactoryBean.setFilterChainDefinitionMap(map);
return filterFactoryBean;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
@Bean
public Realm realm(){
CustomerRealm customerRealm=new CustomerRealm();
//设置加密管理器
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
//设置迭代次数
credentialsMatcher.setHashIterations(ShiroConstant.SHIRO_ITERATORS);
//设置加密算法
credentialsMatcher.setHashAlgorithmName(ShiroConstant.SHIRO_ALGORITHM);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//设置缓存管理器
customerRealm.setCacheManager(new EhCacheManager());
//开启全局缓存
customerRealm.setCachingEnabled(true);
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthenticationCachingEnabled(true);
customerRealm.setAuthorizationCacheName("authorizationCache");
customerRealm.setAuthenticationCacheName("authenticationCache");
return customerRealm;
}
//解决shiro标签在thymeleaf失效的问题
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public ModelAndView login(User user){
ModelAndView modelAndView=new ModelAndView();
try{
//获取主体
Subject subject= SecurityUtils.getSubject();
//设置令牌
UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());
//登录
subject.login(token);
//登录成功,跳转到主页
modelAndView.setViewName("index");
modelAndView.addObject("user",user);
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
modelAndView.setViewName("redirect:/login");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
modelAndView.setViewName("redirect:/login");
}catch (Exception e){
e.printStackTrace();
modelAndView.setViewName("redirect:/login");
}
return modelAndView;
}
@RequestMapping("/logout")
public ModelAndView logout(){
Subject subject=SecurityUtils.getSubject();
subject.logout();
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("redirect:/login");
return modelAndView;
}
@RequestMapping("/register")
public ModelAndView register(User user){
ModelAndView modelAndView=new ModelAndView();
try{
userService.register(user);
modelAndView.setViewName("redirect:/login");
}catch (Exception e){
e.printStackTrace();
modelAndView.setViewName("redirect:/register");
}
return modelAndView;
}
}
LoginController.java
@Controller
public class LoginController {
@RequestMapping("index")
public String index(){
return "index";
}
@RequestMapping("login")
public String login(){
return "login";
}
@RequestMapping("register")
public String register(){
return "register";
}
}
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>主页</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
#bodyLeft{
width:20%;
border-right:1px solid gray;
float:left;
height:100%;
margin-left:5%;
}
#bodyRight{
width:65%;
float:right;
margin-right:5%;
height:100%;
}
#foot{
width:100%;
height:100px;
font-size:12px;
position:absolute;
bottom:0px;
left:0px;
background:#333;
}
#foot ul{
margin-left:20px;
}
#foot li{
color:white;
}
</style>
</head>
<body>
<div id="bodyLeft">
<h1>管理</h1>
<ul>
<li><a th:href="@{/user/login}">退出登录</a></li>
<shiro:hasRole name="admin">
<li>
<a href="">用户管理</a>
</li>
<li>
<a href="">图书管理</a>
</li>
</shiro:hasRole>
<shiro:hasAnyRoles name="admin,user">
<li><a href="">图书借阅</a></li>
<li><a href="">我的借阅</a></li>
</shiro:hasAnyRoles>
</ul>
</div>
<div id="foot">
<ul>
<li>@CopyRight</li>
<li>版权所有:Young</li>
<li>2022-5-25</li>
</ul>
</div>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<div class="container">
<form th:action="@{/user/login}" method="post">
<label for="username">
用户名<input type="text" id="username" name="username" placeholder="请输入用户名"/>
</label>
<label for="password">
密码<input type="password" id="password" name="password" placeholder="请输入密码"/>
</label>
<input type="submit" value="登录"/>
</form>
<a th:href="@{/register}" style="float:right">注册--></a>
</div>
</body>
</html>
register.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>注册</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<div class="container">
<form th:action="@{/user/register}" method="post">
<label for="username">
用户名<input type="text" id="username" name="username"/>
</label>
<label for="password">
密码<input type="password" id="password" name="password"/>
</label>
<input type="submit" value="注册"/>
</form>
<a th:href="@{/login}" style="float:left;"><--返回</a>
</div>
</body>
</html>
application.yml
spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 8081
数据库内容如下:
运行项目:
参考文章:一篇适合小白的Shiro教程
16.SpringSecurity安全框架
创建项目,引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
创建控制器
@Controller
public class MyController {
@RequestMapping("/hello")
public String hello(){
return "redirect:/hello.html";
}
}
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>Hello Spring Security</h3>
</body>
</html>
配置application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
username: root
password: 1q2w3e
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: mapper/*.xml
运行项目,在浏览器中输入网址:http://localhost:8080/hello
username为user,password为控制台的密码
输入用户和密码后,跳转到如下页面
接下来我们自定义用户和权限
创建一个配置类SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http)throws Exception{
http.authorizeRequests()
//设置相应的访问权限
.antMatchers("/admin/*").hasRole("ADMIN")
.antMatchers("/user/*").hasAnyRole("ADMIN","USER")
.anyRequest().authenticated()
.and()
.formLogin()//表单登录
.and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.inMemoryAuthentication()
.withUser("admin")
//noop表示密码没有加密
.password("{noop}adminpass")
.roles("ADMIN","USER")
.and()
.withUser("spring")
.password("{noop}123456")
.roles("USER");
}
}
修改控制器
@Controller
public class MyController {
@RequestMapping("/hello")
public String hello(){
return "redirect:/hello.html";
}
@RequestMapping("/admin/home")
@ResponseBody
public String adminHome(){
return "this is admin home";
}
@RequestMapping("/user/info")
@ResponseBody
public String userInfo(){
String username="";
Object principal=SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principal instanceof UserDetails){
username=((UserDetails)principal).getUsername();
}else{
username=principal.toString();
}
return "this is user info:"+username;
}
}
运行项目
在浏览器输入http://localhost:8080/admin/home,将spring用户的密码输入
访问失败,因为spring没有admin权限,输入http://localhost:8080/user/info
访问成功,我们关闭浏览器,然后使用admin用户进行登录
接下来我们自定义登录页面
修改配置类SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http)throws Exception{
//表单提交
http.formLogin()
.loginPage("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login") .successForwardUrl("/toMain") .failureForwardUrl("/toError");
http.authorizeRequests()
//login.html不需要被认证
.antMatchers("/login.html").permitAll()
//error.html不需要被认证
.antMatchers("/error.html").permitAll()
.anyRequest().authenticated();
//关闭CSRF防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPW(){
return new BCryptPasswordEncoder();
}
}
修改控制类
@Controller
public class MyController {
@RequestMapping("/toMain")
public String toMain(){
return "redirect:/main.html";
}
@RequestMapping("/toError")
public String error(){
return "redirect:/error.html";
}
}
添加UserLoginServiceImpl类,实现UserDetailsService接口
@Service
public class UserLoginServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//模拟根据用户名获取对应的数据库信息
if(!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
String password=pw.encode("123456");
return new User(username,password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
输入错误的密码或用户名后,跳转到失败页面
重新输入正确用户名和密码
接下来讲解根据数据库获取用户信息进行登录
编写一个测试方法,用来获取加密后的密码
@SpringBootTest
class Demo08ApplicationTests {
@Test
public void test(){
PasswordEncoder pw=new BCryptPasswordEncoder();
String encode=pw.encode("123456");
System.out.println(encode);
boolean matches=pw.matches("123456",encode);
System.out.println("=========="+matches);
}
@Test
void contextLoads() {
}
}
数据库表
实体类UserEntity.java
@TableName(value = "user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "role")
private String role;
}
创建对应的dao(mapper)层
UserEntityMapper.java,继承BaseMapper
public interface UserEntityMapper extends BaseMapper<UserEntity> {
}
然后在启动类添加扫描,扫描mapper所在的包
@SpringBootApplication
@MapperScan("com.young.demo08.mapper")
public class Demo08Application {
public static void main(String[] args) {
SpringApplication.run(Demo08Application.class, args);
}
}
这里发现一个问题,我扫描后但是mapper仍然注入失败,所以我创建一个工具类
ApplicationContextUtil.java
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
}
修改UserLoginServiceImpl.java
@Service
public class UserLoginServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntityMapper userEntityMapper=(UserEntityMapper) ApplicationContextUtil.getBean("userEntityMapper");
QueryWrapper<UserEntity>wrapper=new QueryWrapper<>();
wrapper.eq("username",username);
UserEntity userEntity=userEntityMapper.selectOne(wrapper);
if(Objects.isNull(userEntity)){
throw new UsernameNotFoundException("用户不存在");
}
return new User(username,userEntity.getPassword(),
AuthorityUtils.commaSeparatedStringToAuthorityList(userEntity.getRole()));
}
}
修改控制类
@Controller
public class MyController {
@RequestMapping("/toMain")
public String toMain(){
return "redirect:/main.html";
}
@RequestMapping("/toError")
public String error(){
return "redirect:/error.html";
}
@RequestMapping("/admin/home")
@ResponseBody
public String adminHome(){
return "this is admin home";
}
@RequestMapping("/user/info")
@ResponseBody
public String userInfo(){
String username="";
Object principal=SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principal instanceof UserDetails){
username=((UserDetails)principal).getUsername();
}else{
username=principal.toString();
}
return "this is user info:"+username;
}
}
修改SecurityConfig.java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http)throws Exception{
//表单提交
http.formLogin()
.loginPage("/login.html")
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login")
.successForwardUrl("/toMain")
.failureForwardUrl("/toError");
http.authorizeRequests()
//login.html不需要被认证
.antMatchers("/login.html").permitAll()
//error.html不需要被认证
.antMatchers("/error.html").permitAll()
//认证
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/user/**").hasAnyAuthority("ADMIN","USER")
.anyRequest().authenticated();
//关闭CSRF防护
http.csrf().disable();
}
@Bean
public PasswordEncoder getPW(){
return new BCryptPasswordEncoder();
}
}
运行项目,输入admin及其密码
登录成功
点击admin/home页面
点击user/info页面
我们使用spring重新登录
登录成功后,点击admin/home页面
点击user/info页面
接下来我们整合JWT实现前后端分离
我们重新创建一个springboot项目,引入下列依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--json-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
application.yml
server:
port: 8080
spring:
datasource:
username: root
password: 1q2w3e
url: jdbc:mysql://localhost:3306/young?useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
jwt:
header: Authorization
secret: 1q2w3e
expire-time: 14400000
创建一个JwtProperties.java用来获取和jwt有关的配置信息
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtProperties {
private String header;
private String secret;
private Integer expireTime;
}
一般情况下,我们要创建一个实体类来实现UserDetails接口,之前的操作没有实现UserDetails接口也可以,只是转换比较麻烦,现在我们创建一个实体类JwtUser
@Data
@AllArgsConstructor
public class JwtUser implements UserDetails {
private String username;
private String password;
private Collection<? extends GrantedAuthority>grantedAuthorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.grantedAuthorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
创建一个ResultVO.java用来保存返回结果
public class ResultVO <T>{
private Integer code;
private String msg;
private T data;
public ResultVO(){}
public ResultVO(Integer code,String msg){
this.code=code;
this.msg=msg;
}
public ResultVO(Integer code,String msg,T data){
this.code=code;
this.msg=msg;
this.data=data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
ResultVOUtil.java工具类
public class ResultVOUtil {
public static ResultVO success(){
return new ResultVO<>(200,"操作成功");
}
public static ResultVO success(Object data){
return new ResultVO<>(200,"操作成功",data);
}
public static ResultVO error(Integer code,String msg){
return new ResultVO<>(code,msg);
}
}
JwtUtil.java工具类
@Component
public class JwtUtil {
@Resource
private JwtProperties jwtProperties;
public String createToken(Map<String,String>payload){
JWTCreator.Builder builder= JWT.create();
Calendar calendar=Calendar.getInstance();
calendar.add(Calendar.SECOND,jwtProperties.getExpireTime());
builder.withIssuedAt(new Date());
builder.withExpiresAt(calendar.getTime());
payload.forEach((k,v)->builder.withClaim(k,v));
return builder.sign(Algorithm.HMAC256(jwtProperties.getSecret()));
}
public Map<String,String>resolveToken(String token){
if(token==null){
return null;
}
JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(jwtProperties.getSecret())).build();
DecodedJWT decodedJWT=jwtVerifier.verify(token);
Map<String,Claim>map=decodedJWT.getClaims();
Map<String,String>res=new HashMap<>();
map.forEach((k,v)->res.put(k,v.asString()));
return res;
}
}
JwtUserDetailsService.java,实现UserDetailsService接口
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(!username.equals("admin")){
return null;
}
return new JwtUser("admin",passwordEncoder.encode("123456"),
AuthorityUtils.commaSeparatedStringToAuthorityList("user"));
}
}
JwtAccessDeniedHandler处理没有权限访问的情况
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
ResultVO resultVO= ResultVOUtil.error(403,"没有权限访问");
String json= JSON.toJSONString(resultVO);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(json);
}
}
JwtAuthenticationEntryPoint.java处理缺少凭证的情况
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResultVO resultVO= ResultVOUtil.error(404,"缺少凭证");
String json= JSON.toJSONString(resultVO);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(json);
}
}
LoginFailHandler.java处理登录失败的情况
@Component
public class LoginFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
ResultVO resultVO=ResultVOUtil.error(500,"用户名或密码错误");
String json= JSON.toJSONString(resultVO);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(json);
}
}
JwtAuthenticationSuccessHandler.java处理登录成功的情况
@Component
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JwtUtil jwtUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Object principal=authentication.getPrincipal();
JwtUser jwtUser=(JwtUser) principal;
String username=jwtUser.getUsername();
String password=jwtUser.getPassword();
Map<String,String>payload=new HashMap<>();
payload.put("username",username);
payload.put("password",password);
String token=jwtUtil.createToken(payload);
//将生成的authentication放入容器中,生成安全的上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
ResultVO resultVO=new ResultVO<>();
resultVO= ResultVOUtil.success(token);
String json= JSON.toJSONString(resultVO);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(json);
}
}
JwtAuthenticationTokenFilter.java
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Resource
private UserDetailsService userDetailsService;
@Resource
private JwtUtil jwtUtil;
@Resource
private JwtProperties jwtProperties;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token=request.getHeader(jwtProperties.getHeader());
Map<String,String>res=jwtUtil.resolveToken(token);
if(res!=null){
String username=res.get("username");
if(username!=null&& SecurityContextHolder.getContext().getAuthentication()==null){
UserDetails userDetails=this.userDetailsService.loadUserByUsername(username);
if(!Objects.isNull(userDetails)){
UsernamePasswordAuthenticationToken authenticationToken=
new UsernamePasswordAuthenticationToken(userDetails.getUsername(),null,userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
}
filterChain.doFilter(request,response);
}
}
WebSecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Resource
private JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;
@Resource
private LoginFailHandler loginFailHandler;
@Resource
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean()throws Exception{
return new JwtAuthenticationTokenFilter();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean()throws Exception{
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity)throws Exception{
httpSecurity.formLogin()
.successHandler(jwtAuthenticationSuccessHandler)
.failureHandler(loginFailHandler)
.loginProcessingUrl("/login")
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
//设置无状态连接,即不创建session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated();
//解决跨域问题
httpSecurity.cors().and().csrf().disable();
//配置自己的jwt验证过滤器
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
}
}
运行项目
参考文章:SpringSecurity整合JWT实现前后端分离认证和权限管理
SpringSecurity入门原理及实战
SpringSecurity安全框架详解
17.结束语
SpringBoot内容还是相当多的,上面只是一些我学习过并且认为挺有用的知识点,不过有些知识点因为还没有在项目中使用过,所以难免会有一些错误,希望谅解,欢迎指正,不喜勿喷。