系列文章:
SpringBoot + Vue前后端分离项目实战 || 一:Vue前端设计
SpringBoot + Vue前后端分离项目实战 || 二:Spring Boot后端与数据库连接
SpringBoot + Vue前后端分离项目实战 || 三:Spring Boot后端与Vue前端连接
SpringBoot + Vue前后端分离项目实战 || 四:用户管理功能实现
SpringBoot + Vue前后端分离项目实战 || 五:用户管理功能后续
文章目录
- 前后端对接
- 前端接口修改对接后端
- 后端总体配置
- 后端编写登录登出业务代码
- 测试
- 后端所有代码
前后端对接
前端接口修改对接后端
src\api\user.js
中修改请求地址,与后端保持一致
记录下前端的src\utils\request.js
中的X-Token
字段
改变开发环境中的请求地址,更改为后端地址http://localhost:9999
将前端的模拟数据服务注释关闭
后端总体配置
后端新建一个config
包,包中新建两个类
MyCorsConfig
用于配置异步访问,对接前端的访问链接"http://localhost:8888"
,此配置可用Nginx
代替MyRedisConfig
用于配置Redis序列化服务
MyCorsConfig
中配置的代码如下:
package com.ums.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;@Configuration
public class MyCorsConfig {@Beanpublic CorsFilter corsFilter() {CorsConfiguration configuration = new CorsConfiguration();// 允许什么网址来异步访问configuration.addAllowedOrigin("http://localhost:8888");// 获取cookieconfiguration.setAllowCredentials(true);// 允许什么方法? POST、GET,此处为* 意味全部允许configuration.addAllowedMethod("*");// 允许所有的请求头configuration.addAllowedHeader("*");// 设置资源过滤器,过滤什么资源UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",configuration);return new CorsFilter(urlBasedCorsConfigurationSource);}
}
MyRedisConfig
中用于配置的代码如下:
package com.ums.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.TimeZone;@Configuration
public class MyRedisConfig {@Resourceprivate RedisConnectionFactory factory;@Beanpublic RedisTemplate redisTemplate(){RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);// 设置键值序列化redisTemplate.setKeySerializer(new StringRedisSerializer());Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);redisTemplate.setValueSerializer(serializer);// 序列化,死代码ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));om.setTimeZone(TimeZone.getDefault());om.configure(MapperFeature.USE_ANNOTATIONS, false);om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);om.setSerializationInclusion(JsonInclude.Include.NON_NULL);serializer.setObjectMapper(om);return redisTemplate;}
}
后端编写登录登出业务代码
前端VUE项目的登录接口请求方法为POST
,之前介绍过
在UserController
中新增代码,用于登录控制
@PostMapping("/login")
public Result<Map<String,Object>> login(@RequestBody User user){// 因为 user传过来为json字符串,所以用@RequestBody 进行实体转换// 业务代码在userService里完成Map<String,Object> data = userService.login(user);if(data != null){return Result.success(data,"登录成功");}return Result.fail(2002,"用户名或密码错误");
}
如下图所示userService.login()
方法会爆红,因为该方法没有被定义或实现,此时鼠标点击并按Alt+Enter
选择第一项:
IDEA会自动生成接口代码
此时,接口上方有个提示1 related problem
,鼠标左击,会跳转至接口的实现代码处UserServiceImpl
在整个类的定义之处,同样Alt + Enter
,选择第一个,弹出的对话框再选第一个,会生成代码
生成的代码
在该函数中写上下述代码
@Override
public Map<String, Object> login(User user) {// 查询数据库LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUsername, user.getUsername());wrapper.eq(User::getPassword, user.getPassword());User loginUser = this.baseMapper.selectOne(wrapper);// 结果不为空,生成token,将用户信息存入redisif (loginUser != null) {// 用UUID,终极方案是jwtString key = "user:" + UUID.randomUUID();// 存入redisloginUser.setPassword(null); // 设置密码为空,密码没必要放入redisTemplate.opsForValue().set(key, loginUser,30, TimeUnit.MINUTES); // timeout为登录时间// 返回数据Map<String, Object> data = new HashMap<>();data.put("token",key);return data;}// 结果不为空,生成token,前后端分离,前端无法使用session,可以使用token// 并将用户信息存入redisreturn null;
}
返回UserController
,此时代码正常
按照上述方法如法炮制
在UserController
中写获取token
的代码和logout
代码
其中,这两个接口均未实现
代码如下
@GetMapping("/info")
public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){// @RequestParam("token") 是从url中获取值// 根据token获取用户信息,信息存进了redis中Map<String,Object> data = userService.getUserInfo(token);if(data != null){return Result.success(data);}return Result.fail(2003,"登录信息无效,请重新登录");
}@PostMapping("/logout")
public Result<?> logout(@RequestHeader("X-Token") String token){userService.logout(token);return Result.success();
接着就是Alt + Enter
修复bug
在UserServiceImpl
中先定义一个redisTemplate
然后在UserServiceImpl
中写上下述代码
@Override
public Map<String, Object> getUserInfo(String token) {// 之前已将对象进行序列化处理存入redis,现在从redis中取出需要反序列化处理Object obj = redisTemplate.opsForValue().get(token); // 此对象是map类型,稍后需要序列化为Json字符串if (obj!= null) {User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class);Map<String,Object> data = new HashMap<>();data.put("name",loginUser.getUsername());data.put("avatar",loginUser.getAvatar());// 先在xml里写SQL语句id=getRoleNameByUserId,然后去UserMapper里实现接口List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());data.put("roles",roleList);return data;}return null;
}@Override
public void logout(String token) {redisTemplate.delete(token); // 从redis中删除token
}
注意红圈中的代码,是联表查询,这是自定义的SQL
查询,接下来定义它
找到UserMapper
然后写上代码:
public List<String> getRoleNameByUserId(Integer userId);
然后去resources\mapper\sys\UserMapper.xml
中写SQL语句
<?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.ums.sys.mapper.UserMapper"><select id="getRoleNameByUserId" parameterType="Integer" resultType="String">SELECTb.role_nameFROMx_user_role a,x_role bWHEREa.role_id=b.role_idANDa.user_id = #{userId}</select>
</mapper>
测试
由于配置了redis
,所以在启动SpringBoot之前先启动Redis
先找到redis的安装目录
打开cmd
定位到该目录
运行命令redis-server.exe redis.windows.conf
,回车,出现下述界面,然后此窗口最小化,千万别关闭
接着启动SprinfBoot
后端
然后启动Vue
前端
点击登录后,可以看到http://localhost:9999/user/login
接口地址的变化
后端生成的token
也注册在redis
中
点击退出登录
,redis中的token也被注销了
后端所有代码
防止笔记失误,附上所有代码
UserController
中的代码package com.ums.sys.controller;import com.ums.common.vo.Result; import com.ums.sys.entity.User; import com.ums.sys.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.stereotype.Controller;import java.util.List; import java.util.Map;/*** <p>* 前端控制器* </p>** @author anthony* @since 2023-06-16*/ @RestController @RequestMapping("/user") // @CrossOrigin //处理跨域,因为前端和后端的IP一致但端口不一致,所以浏览器认为跨域,不给访问,可用Ngx来解决 public class UserController {@Autowiredprivate IUserService userService;@GetMapping("/all")public Result<List<User>> getAllUser() {List<User> list = userService.list();return Result.success(list,"查询成功");}@PostMapping("/login")public Result<Map<String,Object>> login(@RequestBody User user){// 因为 user传过来为json字符串,所以用@RequestBody 进行实体转换// 业务代码在userService里完成Map<String,Object> data = userService.login(user);if(data != null){return Result.success(data,"登录成功");}return Result.fail(2002,"用户名或密码错误");}@GetMapping("/info")public Result<Map<String,Object>> getUserInfo(@RequestParam("token") String token){// @RequestParam("token") 是从url中获取值// 根据token获取用户信息,信息存进了redis中Map<String,Object> data = userService.getUserInfo(token);if(data != null){return Result.success(data);}return Result.fail(2003,"登录信息无效,请重新登录");}@PostMapping("/logout")public Result<?> logout(@RequestHeader("X-Token") String token){userService.logout(token);return Result.success();} }
mapper\UserMapper
中的代码package com.ums.sys.mapper;import com.ums.sys.entity.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper;import java.util.List;/*** <p>* Mapper 接口* </p>** @author chenhao* @since 2023-06-16*/ public interface UserMapper extends BaseMapper<User> {public List<String> getRoleNameByUserId(Integer userId); }
service\IUserService
接口中的代码package com.ums.sys.service;import com.ums.sys.entity.User; import com.baomidou.mybatisplus.extension.service.IService;import java.util.Map;/*** <p>* 服务类* </p>** @author chenhao* @since 2023-06-16*/ public interface IUserService extends IService<User> {// Map<String, Object> login(User user);Map<String, Object> getUserInfo(String token);void logout(String token);Map<String, Object> login(User user); }
service\impl\UserServiceImpl
中的代码package com.ums.sys.service.impl;import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ums.sys.entity.User; import com.ums.sys.mapper.UserMapper; import com.ums.sys.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service;import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit;/*** <p>* 服务实现类* </p>** @author chenhao* @since 2023-06-16*/ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic Map<String, Object> login(User user) {// 查询数据库LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getUsername, user.getUsername());wrapper.eq(User::getPassword, user.getPassword());User loginUser = this.baseMapper.selectOne(wrapper);// 结果不为空,生成token,将用户信息存入redisif (loginUser != null) {// 用UUID,终极方案是jwtString key = "user:" + UUID.randomUUID();// 存入redisloginUser.setPassword(null); // 设置密码为空,密码没必要放入redisTemplate.opsForValue().set(key, loginUser,30, TimeUnit.MINUTES); // timeout为登录时间// 返回数据Map<String, Object> data = new HashMap<>();data.put("token",key);return data;}// 结果不为空,生成token,前后端分离,前端无法使用session,可以使用token// 并将用户信息存入redisreturn null;}@Overridepublic Map<String, Object> getUserInfo(String token) {// 之前已将对象进行序列化处理存入redis,现在从redis中取出需要反序列化处理Object obj = redisTemplate.opsForValue().get(token); // 此对象是map类型,稍后需要序列化为Json字符串if (obj!= null) {User loginUser = JSON.parseObject(JSON.toJSONString(obj), User.class);Map<String,Object> data = new HashMap<>();data.put("name",loginUser.getUsername());data.put("avatar",loginUser.getAvatar());// 先在xml里写SQL语句id=getRoleNameByUserId,然后去UserMapper里实现接口List<String> roleList = this.baseMapper.getRoleNameByUserId(loginUser.getId());data.put("roles",roleList);return data;}return null;}@Overridepublic void logout(String token) {redisTemplate.delete(token); // 从redis中删除token}}