首页 前端知识 SpringBoot Vue 博客后台项目开发总结

SpringBoot Vue 博客后台项目开发总结

2024-09-01 23:09:56 前端知识 前端哥 186 458 我要收藏

目录

一、用户权限设置

前端逻辑

后端逻辑

二、登录界面逻辑

1、账号密码登录实现

前端逻辑

在Cookie中存储token的方法

在Cookie中存储和获取的token方法

后端逻辑

生成token工具类

补充:根据token获取当前登录对象信息

2、手机号登录

前台逻辑

后台逻辑

补充:实现点击发送验证码120s倒计时功能

三、文章管理功能

1、实现批量删除

前端逻辑

后端逻辑

2、数据编辑实现表格数据回显

3、实现表格中动态显示不同数据的图片信息

四、图片上传功能

前端逻辑

后端逻辑

五、在Vue中封装axios请求

1、在src中创建utils目录 创建request.js

2、在src下再创建不同接口请求的js文件

3、在src下再创建一个index.js

4、在main.js中引入这个api中的index.js

5、调用方式

六、Echarts的基本引入

1、在Vue中导入Echarts

 2、不同图形的属性

3、补充  Echarts取数据

1、在js对象中获取所有的key值

2、在js对象中获取所有的value值

3、将js对象转为集合类型使其能够通过索引取数据

七、使用SpringBoot的拦截器拦截特定url

1、创建Iterceptor

2、在WebMvc中注入并添加

八、实现账户退出并清空token

1、在main.js中添加导航路由守卫

2、退出逻辑

九、对前后端的数据请求进行拦截

后端拦截器逻辑

前端逻辑

后端逻辑


一、用户权限设置

只有管理员才能对后台进行登录,在用户表中设置管理员属性admin,设置默认值为0,当这个值大于0时,表示为管理员权限,其中将一个账号设置为超级管理员,管理其他的普通管理员,所以在账号进行登录时,判断他的admin属性值,是否能够进行登录

前端逻辑

先获取当前登录用户的token,以及要修改的这一行数据,作为参数传递给后端,根据返回值判断是否允许修改

后端逻辑

  1. 先使用JWT工具类解析当前的token值中的用户信息
  2. 判断token中的用户信息是否存在防止NPE
  3. 判断当前token中的用户是否是超级管理员,不是超级管理员返回fail
  4. 是超级管理员则对admin值进行修改

二、登录界面逻辑

1、账号密码登录实现

前端逻辑

使用element表单获取输入的数据,单击登录,将表单提交给后端,提交给后端后,后端会根据当前登录的账号创建一个token返回给前端,前端接收到token 将token存储进 Cookie中,接收到结果为真直接跳转到首页

在Cookie中存储token的方法

  1. 在Vue项目中安装 js-cookie       npm i js-cookie  
  2. 在项目中引入Cookie       import Cookie from 'js-cookie'
  3. 设置token存储进Cookie    Cookie.set('token',后端返回的token值)

在Cookie中存储和获取的token方法

  1. 在script中引入Cookie
  2. Cookie.get('token')

后端逻辑

根据登录的账户放入Redis中存储并设置过期事件再生成token并返回

@Service
public class LoginServiceImpl implements LoginService {
    //加密盐 防止md5被解密
    private static final String slat = "mszlu!@#";

    //登录功能需要查询用户表,将用户操作的service注入
    @Autowired
    private UserService userService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public Result login(LoginParam loginParam) {
        /**
         * 1、检查参数是否合法
         * 2、根据用户名和密码在 user表中查询是否存在
         * 3、如果不存在 登陆失败
         * 4、登陆成功 使用JWT生成token 返回给前端
         * 5、将token放入redis 设置:(tokenuser 信息 设置过期时间)
         * (登录认证的时候 先认证token字符串是否合法 再去Redis认证是否存在)
         */
        //获取登陆的用户名和密码
        String username = loginParam.getUsername();
        String password = loginParam.getPassword();

        //判断账户对象或者密码是否为空
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)){
            return Result.fail(1200,"账户对象或密码为空");
        }

        //给密码加密
        password = DigestUtils.md5Hex(password + slat);

        //查询用户表中有没有对应的用户对象
        SysUser sysUser = userService.findUserInLog(username,password);

        if (sysUser == null){
            return Result.fail(1201,"用户名或密码错误!");
        }

        //如果是管理员登录才允许进入
        Integer admin = sysUser.getAdmin();
        if (admin >= 1){
            //用户存在生成对应的token
            String token = JWTUtils.createToken(sysUser.getId());
            
            //将对象信息也放入redis中
            //如果用户登录那么一定会在redis中找到这个对象信息  
            //将token放入redis中 设置过期时间1天
            redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser), 1, TimeUnit.DAYS);
            return Result.success(token);
        }else {
            return Result.fail(3020,"对不起,当前用户权限不足!");
        }
    }
}

生成token工具类

public class JWTUtils {

    //JWT密钥
    private static final String jwtToken = "123456!@#$$";

    //创建一个token
    public static String createToken(Long userId){
        //B部分
        Map<String,Object> claims = new HashMap<>();
        claims.put("userId",userId);
        JwtBuilder jwtBuilder = Jwts.builder()
                //A部分
                .signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
                //设置B部分
                .setClaims(claims) // body数据,要唯一,自行设置
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000));// 一天的有效时间
        //设置token完成
        String token = jwtBuilder.compact();
        return token;
    }

    public static Map<String, Object> checkToken(String token){
        try {
            //JWT 通过密钥 解析token
            Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
            return (Map<String, Object>) parse.getBody();
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;

    }
}

补充:根据token获取当前登录对象信息

    @Override
    public Result getUserByToken(String token) {
        //先解析token
        Map<String, Object> map = JWTUtils.checkToken(token);
        //查看是否为空
        if (map == null){
            return Result.fail(5000,"当前未登录");
        }

        //查看redis中是否存在token   token中存放了对象
        String userJson = redisTemplate.opsForValue().get("TOKEN_" + token);
        if (StringUtils.isBlank(userJson)){
            return Result.fail(5000,"当前未登录");
        }

        //存在token 解析这个JSON
        SysUser sysUser = JSON.parseObject(userJson, SysUser.class);

        return Result.success(copy(sysUser));
    }

2、手机号登录

前台逻辑

同样使用表单构建 将当前用户输入的手机号发送给后端 当点击发送验证码时,将手机号发送给后端校验,校验通过后再申请发送验证码的接口,接收到后端发送的验证码先进行解密,在根据用户输入的验证码进行登录校验,校验成功将后端返回的token存储进Cookie中,跳转到首页。

后台逻辑

先接收前端发送的手机号查询到当前用户对象,先判断账号是否存在,如果不存在则提示先注册,在判断当前用户的权限是否允许登录,权限允许 则调用发送验证码接口,返回给前端加密的验证码,前端用户输入接收到的验证码点击登录后,再将当前的手机号发送给后端,生成token返回。

先在阿里云短信服务中申请签名和短信模板,获取阿里云accessKeyID的账号和密码,以及注册好的短信模板代码,调用接口

@Component
public class CodeUtils {
    //生成手机验证码工具类
    public boolean sendCode(String phoneNum,String templateCode,Map<String,Object> code) {
        DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "LTAI5tJX8MHTLx4mPGw5hNyN", "BTI4MSNNYq8Bl0KZ3mv449lmeBJhFl");

        IAcsClient client = new DefaultAcsClient(profile);
        //构建请求
        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        request.setSysDomain("dysmsapi.aliyuncs.com");
        request.setSysAction("SendSms");
        request.setSysVersion("2017-05-25");

        //自定义的参数(手机号、验证码、签名、模板)
        request.putQueryParameter("PhoneNumbers", phoneNum);
        //签名
        request.putQueryParameter("SignName", "猿客栈后台登录");
        //模板
        request.putQueryParameter("TemplateCode", templateCode);
        //构建一个短信验证码

        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(code));


        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            //返回是否发送成功
            return response.getHttpResponse().isSuccess();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 随机生成6位验证码
     * @return
     */
    public String getCode(){
        Random random = new Random();

        StringBuffer builder = new StringBuffer();
        //循环6次生成验证码
        for (int i=0;i<6;i++){
            int code = random.nextInt(10);
            builder.append(code);
        }
        return builder.toString();
    }

    /**
     * 将验证码加密
     */
    public String md5String(String code){
        //将验证码加密
        StringBuffer reverse = new StringBuffer(code).reverse();
        String s = reverse.toString();
        Long i = Long.valueOf(s);
        i += 123456;
        return String.valueOf(i);
    }

}

LoginService

    @Override
    public Result loginByPhone(String phone) {
        //根据手机号查找用户对象并判断权限
        SysUser userByPhone = userService.getUserByPhone(phone);
        if (Objects.isNull(userByPhone)){
            return Result.fail(5000,"该手机号未注册!请先注册");
        }
        //创建token返回
        //用户存在生成对应的token
        String token = JWTUtils.createToken(userByPhone.getId());

        //将token放入redis中 设置过期时间1天
        redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(userByPhone), 1, TimeUnit.DAYS);
        return Result.success(token);
    }

UserService查看权限

    @Override
    public Result isLoginByPhone(String phoneNum) {
        SysUser sysUser = getUserByPhone(phoneNum);
        if (Objects.isNull(sysUser)){
            return Result.fail(2500,"手机号不存在! 请先注册!");
        }else if (sysUser.getAdmin() >= 1){
            //只允许管理员登录
            return Result.success("允许登录");
        }
        return Result.fail(500,"对不起,权限不足无法登陆!");

    }

    /**
     * 通过手机号查询用户对象
     * @param phoneNum
     * @return
     */
    @Override
    public SysUser getUserByPhone(String phoneNum) {
        QueryWrapper<SysUser> qw = new QueryWrapper<>();
        qw.eq("mobile_phone_number",phoneNum);
        SysUser sysUser = userMapper.selectOne(qw);
        return sysUser;
    }

补充:实现点击发送验证码120s倒计时功能

在表单中添加一个span 添加button 发送验证码 并且实现动态显示按钮内容

 绑定实体类

 动态文字显示

	//验证码提示信息动态显示
			getCodeText: function() {
				if (this.wait_timer > 0) {
					return "已发送";
				}
				if (this.wait_timer === 0) {
					this.showNum = false;
					return "重新发送验证码!";
				}
				if (this.wait_timer === false) {
					return "发送验证码";
				}
				if (this.userForm.tele === '') {
					return "手机号不能为空"
				}
			},

实现倒计时120s

//验证码等待
if (this.wait_timer > 0) {
	return false;
}
this.showNum = true;
this.wait_timer = 120;
var that = this;
var timer_interval = setInterval(function() {
	if (that.wait_timer > 0) {
		that.wait_timer--;
	} else {
		learInterval(timer_interval);
	}
}, 1000);

三、文章管理功能

1、实现批量删除

前端逻辑

要实现批量删除功能首先要在表单中添加selection类型的表格列,通过this.$refs.table.selection获取当前选中的整行数据,并对这个数据进行遍历,获取所有选中数据的id,存放在实例对象的数组中,请求后端携带这个id数组

后端逻辑

后端通过List集合接收到前端传递的json数组 ,对集合遍历删除

2、数据编辑实现表格数据回显

对当前行数据进行深拷贝即可获得回显数据

3、实现表格中动态显示不同数据的图片信息

四、图片上传功能

前端逻辑

使用elementUI中的上传标签,将当期选中的图片上传到服务器中,并通过方法接收返回的图片地址对象。

  1. action 图片提交地址
  2. :on-exceed="handleExceed"  图片上传超过限制的提示
  3. :auto-upload="false"  设置手动提交
  4. :before-upload    图片上传前的方法,对图片格式校验
  5. :on-success="handleAvatarSuccess"  图片上传成功的方法

//图片上传
			beforeAvatarUpload(file) {
				const isJPG = file.type === 'image/jpeg';
				const isPNG = file.type === 'image/png';
				const isLt2M = file.size / 1024 / 1024 < 2;
			
				if (!isJPG && !isPNG) {
					this.$message.error('上传头像图片只能是 JPG或者PNG 格式!');
				}
				if (!isLt2M) {
					this.$message.error('上传头像图片大小不能超过 2MB!');
				}
				return (isJPG || isPNG) && isLt2M;
			},
			handleAvatarSuccess(res, file) {
				// 上传图片成功后返回图片地址
				this.imgUrl = res.data
			},
			handleExceed() {
				this.$message.warning(`当前限制只能选择 1 张图片!`);
			},
			upload() {
				//手动提交
				this.$refs.upload.submit();
			},

后端逻辑

在七牛云配置好存储空间,保留accessKey 账号,后端引入七牛云工具类,并调用上传方法baseUpload(),返回图片地址

@Component
public class QiniuUtils {

    public static  final String url = "http://cdn.zxlsc.top/";

    //配置文件中写的accessKey账号
    @Value("${qiniu.accessKey}")
    private  String accessKey;
    @Value("${qiniu.accessSecretKey}")
    private  String accessSecretKey;

    public  boolean upload(MultipartFile file,String fileName){

        //构造一个带指定 Region 对象的配置类
        Configuration cfg = new Configuration(Region.huabei());
        //...其他参数参考类注释
        UploadManager uploadManager = new UploadManager(cfg);
        //...生成上传凭证,然后准备上传
        String bucket = "myblogssr";
        //默认不指定key的情况下,以文件内容的hash值作为文件名
        try {
            byte[] uploadBytes = file.getBytes();
            Auth auth = Auth.create(accessKey, accessSecretKey);
            String upToken = auth.uploadToken(bucket);
            Response response = uploadManager.put(uploadBytes, fileName, upToken);
            //解析上传成功的结果
            DefaultPutRet putRet = JSON.parseObject(response.bodyString(), DefaultPutRet.class);
            return true;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }



    //防止冗余 直接写在工具类  Controller中调用
    public Result baseUpload(MultipartFile file, String path){
        //获取到文件名称 如a.jpg
        String originalFilename = file.getOriginalFilename();
        //唯一的文件名称
        String fileName = path + UUID.randomUUID().toString() + "." + StringUtils.substringAfterLast(originalFilename,".");
        //上传文件到哪呢? 上传到七牛云 将图片发放到离用户最近的服务器上,降低自身服务器的带宽消耗

        boolean upload = upload(file, fileName);
        if (upload){
            return Result.success(QiniuUtils.url + fileName);
        }
        return Result.fail(20001,"上传失败");
    }
}

五、在Vue中封装axios请求

1、在src中创建utils目录 创建request.js

在request中对axios进行二次封装,直接粘贴即可

//对axios二次封装
import axios from 'axios'

//对封装的axios方法起个名字
const request = axios.create({
	//通用请求地址前缀
	baseURL:'http://localhost:8989/api',
	//请求的超时时间
	timeout: 10000, 
})

//添加请求拦截器
request.interceptors.request.use(function (config){
	//在请求之前做什么
	return config;
},function(error){
	//对错误做点什么
	return Promise.reject(error);
});

//添加响应拦截器
request.interceptors.response.use(function (response){
	//对响应数据做点什么
	return response;
},function (error){
	//对响应错误做点什么
	return Promise.reject(error);
});

//将这个常量暴露
export default request

2、在src下再创建不同接口请求的js文件

//从request中引入
import request from "../utils/request.js"

//将请求方法暴露

//获取所有文章列表
export function getArticle(data){
	return request({
		method:"post",
		url:"/articles/list",
		data:data
	})
}

//获取文章总数
export function getCount(){
	return request({
		method:"get",
		url:"/article"
	})
}

3、在src下再创建一个index.js

此文件负责将所有的接口文件统一暴露出去直接使用

//按格式引入
import * as article from './article.js'
import * as user from'./user.js'
import * as tag from './tag.js'
import * as log from './log.js'

//负责将写好的请求暴露出去使用
export default {
	article,
	user,
	tag,
	log,
}

4、在main.js中引入这个api中的index.js

给暴露出去的方法设置全局属性 使得方法能够在全局调用

import api from './api/index.js'

//给这个api中的index起别名 后序调用直接使用this.$api
Vue.prototype.$api = api

5、调用方式

//原本使用axios方式
axios({
    method:"post",
    url:"http://localhost:8989/api/user"
    data:data
}).then(res => {
    //返回值
})


//使用封装的axios
//使用user.js中的getUser()请求
this.$api.user.getUser().then(res => {})

六、Echarts的基本引入

1、在Vue中导入Echarts

  1. 使用npm安装 echarts    npm i echarts
  2. 在vue文件中导入echarts 
  3. 在需要的标签内容设置 ref ---- vue提供的获取dom节点的方式
  4. 配置图形对象  
  5. 将图形对象暴露出去
官网提供的Demo  描述了图形的基本属性设置
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
    <div id="main" style="width: 600px;height:400px;"></div>
    <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 指定图表的配置项和数据
      var option = {
        title: {
          text: 'ECharts 入门示例'
        },
        tooltip: {},

        legend: {
          data: ['销量']
        },
        xAxis: {
          data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
        },
        yAxis: {},
        series: [
        //当有多条数据时使用遍历的方式设置series属性
          {
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
          }
        ]
      };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
    </script>

 2、不同图形的属性

折线:  series->type: "line"

柱状图: series->type: "bar"

柱状图与折线图只有type不同其他相同

饼状图:series->type: "pie"  radius:"50%"  其中对于饼状图不需要设置x,y轴  他也可以设置其他属性

3、补充  Echarts取数据

对于Echarts的使用难点在于如何对后端返回的数据进行筛选,将不同的数据提取出来对图形图像进行设置,只要将数据取出并赋值就可以完成Echarts的使用

所以在这里提供几个取数据的方法

数据类型  let obj = {"www":23,"ww":78}

数据类型为Array可以直接通过[0]的方式  通过索引位置获取

1、在js对象中获取所有的key值

Object.keys(obj)

2、在js对象中获取所有的value值

Object.values(obj)

3、将js对象转为集合类型使其能够通过索引取数据

Object.entries(obj2)

4、Echarts问题记录

1、x轴数据与y轴数据的位置不匹配

在x轴中添加一个属性

lineOption.xAxis = {
	data:Object.keys(orderData[0]) ,
    //x轴数据自动调节大小
	axisLabel:{
		interval:0
	}
}
				

2、当x轴数据超过指定长度时变为省略号

lineOption.xAxis = {
	data: xAxis,
	axisLabel: {
		formatter: function(value) {
			if (value.length > 6) {
				return `${value.slice(0,5)}...`;
			}
			return value
		}
	}
}

七、使用SpringBoot的拦截器拦截特定url

使用SpringBoot拦截器将特定url请求拦截 实现一个简单的 记录网站访问次数的功能,

1、创建Iterceptor

@Component
@SuppressWarnings({"all"})
public class URLInterceptor implements HandlerInterceptor {


/**
     * 请求前置处理(后置处理同理)
     *
     * @param request
     * @param response
     * @param handler
     * @return boolean
     */

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {
       //获取前端请求的接口地址
         String path = request.getServletPath();

        //对特定的接口进行设置
        if ("/articles/list".equals(path)) {
            //后置处理
            //对访问这个路径后执行的操作

        }
    }

2、在WebMvc中注入并添加

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    //注入
    @Autowired
    private URLInterceptor urlInterceptor;

    //重写方法  将拦截器注入
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(urlInterceptor);
    }
}

八、实现账户退出并清空token

注意退出登录并清空token后应该返回登录页面

1、在main.js中添加导航路由守卫

当不存在token时会立马跳转到登录页

//添加路由守卫
router.beforeEach((to,from,next) => {
	//判断token是否存在 从Cookie读取
	const token = Cookie.get('token')
	if(!token && to.name !== 'login'){
		//token不存在 说明当前用户未登录 应该跳转未登录页面
        //name 是路由配置中指定路由的名称
		next({ name: 'login' })
	}else if(token && to.name === 'login'){
		//token存在 说明登录 此时跳转首页
		// next({ name:from.name })
		next({ name:'home' })
	}else{
		next()
	}
})

2、退出逻辑

前端逻辑

后端逻辑

九、对前后端的数据请求进行拦截

对前端发送的请求统一封装数据,将当前登录的账户的token放在请求头中,在发送给后端的时候使用拦截器对请求进行拦截,执响应前拦截,在请求之前判断,当前的请求 请求头中是否含有token,并且校验这个token是否合法,如果不合法说明,有用户直接使用浏览器中设置token试图发送请求,所以需要进行安全校验

后端拦截器逻辑

前端逻辑

在 request.js中设置响应拦截器 对后端返回的错误状态码进行拦截

 

后端逻辑

@Component
public class HeaderInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //先判断是否在登录界面 防止登录拦截
        //获取请求路径
        String path = request.getServletPath();
        
        //对登录界面的请求进行放行
        if ("/login".equals(path) || "/code".equals(path) || "/users/judgephone".equals(path) || "/users/token".equals(path)){
            return true;
        }

        //这个一定要加否则报错  对预检请求放行
        //查看是否是预检请求
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            System.out.println("OPTIONS请求,放行");
            return true;
        }


        //获取请求头中的token  前端设置存储在Authorization
        String token = request.getHeader("Authorization");

        //当前没有token
        if (token == ""){
            //给前端响应
            //设置响应码
            response.setStatus(438);
            return false;
        }
        Result result = null;
        try {
            result = userService.getUserByToken(token);
        } catch (Exception e) {
            //报错说明token不合法
            //给前端响应
            response.setStatus(438);
            return false;
        }

        if (!result.isSuccess()){
            response.setStatus(438);
            return false;
        }
        return true;
    }

}

在WebMVCConfig中注册


@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    @Autowired
    private HeaderInterceptor headerInterceptor;


     //登录验证
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(headerInterceptor).addPathPatterns("/**");
    }


    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //进行跨域配置
        //前端占用8080 后端占用8888
        //两个端口之间的访问就是跨域
        //防止跨域报错
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }

}

转载请注明出处或者链接地址:https://www.qianduange.cn//article/17430.html
标签
架构设计
评论
发布的文章

Spring MVC-JSON

2024-06-02 09:06:53

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!