SpringBoot + Vue前后端分离项目实战 || 三:Spring Boot后端与Vue前端连接

前端 0

系列文章:
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;@Configurationpublic class MyCorsConfig {    @Bean    public CorsFilter corsFilter() {        CorsConfiguration configuration = new CorsConfiguration();        // 允许什么网址来异步访问        configuration.addAllowedOrigin("http://localhost:8888");        // 获取cookie        configuration.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;@Configurationpublic class MyRedisConfig {    @Resource    private RedisConnectionFactory factory;    @Bean    public 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,选择第一个,弹出的对话框再选第一个,会生成代码
在这里插入图片描述
在这里插入图片描述

生成的代码
在这里插入图片描述

在该函数中写上下述代码

@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,将用户信息存入redis    if (loginUser != null) {        // 用UUID,终极方案是jwt        String key = "user:" + UUID.randomUUID();        // 存入redis        loginUser.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    // 并将用户信息存入redis    return 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中写上下述代码

@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}

注意红圈中的代码,是联表查询,这是自定义的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">        SELECT               b.role_name        FROM             x_user_role a,x_role b        WHERE            a.role_id=b.role_id          AND            a.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也被注销了
在这里插入图片描述
在这里插入图片描述

后端所有代码

防止笔记失误,附上所有代码

  1. 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 {    @Autowired    private 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();    }}
  2. 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);}
  3. 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);}
  4. 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 */@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {    @Autowired    private RedisTemplate redisTemplate;    @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,将用户信息存入redis        if (loginUser != null) {            // 用UUID,终极方案是jwt            String key = "user:" + UUID.randomUUID();            // 存入redis            loginUser.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        // 并将用户信息存入redis        return null;    }    @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    }}

也许您对下面的内容还感兴趣: