新闻发布系统创建过程与源码分享

新闻发布系统,vue3+springboot,前后端分离项目

  • 前言,适合人群
    • 项目启动引导教程
      • vue3 vite项目启动
      • springboot项目启动
  • Day1 -2 新闻管理页面的基本搭建,后端搭建
    • 问题1:请求跨域问题
    • 问题2 增删改中子组件与父组件的通信问题
    • 问题3: element-plus怎么获取表格组件怎么获取行对象
    • 问题 4 前端如何发起请求
  • Day3-4 后端全局异常处理函数,后端模糊,分页查询实现
    • 数据库异常原因总结
  • Day5-6 登录校验与请求拦截,token
    • 问题一 怎么样登录校验
    • token校验

前言,适合人群

嘿,小伙伴,这是本人大二课程作业新闻发布系统的实现过程。如果您对前后端是如何交互感到困惑,或者想要初步学习vue3或后端的springboot框架的实践,又或是想要以此为基石完成属于自己课程作业或项目,那就上车,随我一起出发吧。(再次声明本文并非手把手教你的文章,需要您配合源码,以及我的半成品的图加以理解,因为这篇文章是我编写代码过程中成功经验给记录集合而已)
前提知识储备:springboot,mybatis sql(这部分不会讲)
主要分享:vue3

项目启动引导教程

方便各位看的懂我是怎么做的,

vue3 vite项目启动

我使用的是Intellij idea,直接新建项目找到vite项目即可
![在这里插入图片描述](https://img-blog.csdnimg.cn/ba854858a5d445598ded0a385ed270e3.png在这里插入图片描述

兄弟们刚创建完毕定启动项目会出现vite的欢迎界面
我这里的news部分的位置应当直接在src目录下(不要学我,我这里只是为了管理多个页面,配置就挺麻烦的,vite本身就是做单页面的。通过改变组件来实现页面切换)在src下建立news下文件夹,给大家上传的源码部分只会包含news部分,vite-config.js请君自行上网查阅,或私聊

在这里插入图片描述

springboot项目启动

在这里插入图片描述
在这里插入图片描述
数据库依赖请自行选择
在这里插入图片描述

Day1 -2 新闻管理页面的基本搭建,后端搭建

问题1:请求跨域问题

在vite-config.js处加上对server的配置

//前后端分离,跨域配置,后端服务器地址为http://localhost:8080//想要访问http://localhost:8080/news来获取新闻数据,那么就需要配置路由重写// 重写规则如下:// 1. 以/api开头的请求,都会被代理到target配置// 2. 重写后的地址为:target(去掉/api) + /api(去掉/api)// 3. 例如:/api/news 重写后就是 http://localhost:8080 + /news// 4. 最终得到 http://localhost:8080/news// 5. 重写后的地址就会被代理到target配置server: {proxy: {'/local': {target: 'http://localhost:8080',rewrite: (path) => path.replace(/^\/local/, '')}}}

问题2 增删改中子组件与父组件的通信问题

需求描述; 点击编辑按钮/加号,弹出对话框(标题相应不同)填写表单,对话框组件封装在newsForm.vue中,父组件为news,为实现数据回显,以及是否弹出对话框,以下提供一个解决思路,当然也可以使用v-if
在这里插入图片描述

父组件通过ref获得子组件的实例,通过@向子组件传递自己的方法
在这里插入图片描述
子组件通过暴露自己的属性和方法,让父组件通过 const formInstance=ref()
之后使用formInstance可以访问到暴露的方法。
子组件通过使用emit访问到父组件的getNewsList方法。
在这里插入图片描述

问题3: element-plus怎么获取表格组件怎么获取行对象

需求描述:在点击编辑/删除时,我们需要知道究竟是修改了哪一行的数值

可以通过如下方法通过scope.row获得行对象
在这里插入图片描述

问题 4 前端如何发起请求

首先在httpRequest.js中配置请求基本路径以及拦截器(拦截器里面的处理可以暂时不写,在后面有业务需求在写)/local在这里要配合前面的跨域配置来写
在这里插入图片描述
然后在相应文件中按照这种方式发出请求,url地址与后端的接收的url要一致,传输与接收的参数,键值对中键名要与双方接收数据的属性名一致。传输对象时无须提前转换成json格式,object对象,hashmap对象都可以直接传入
在这里插入图片描述

Day3-4 后端全局异常处理函数,后端模糊,分页查询实现

package com.kinman.exception;import com.kinman.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** 全局异常处理器*/
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)//捕获所有异常public Result ex(Exception ex){ex.printStackTrace();return Result.error("对不起,操作失败,请联系管理员");}
}

在这里插入图片描述
这里给各位说明一下,分页查询的工作实际上是后端在做,利用sql语句实现的(当然可以借助插件)前端element-plus分页组件并不能直接与表格显示的内容相关联,仅仅是为了好看,用于通知前端或者后端pageSize,以及currentPage的数据,利用这我们可以使用前端或者后端进行数据剪枝。模糊查询使用like即可
前后端分页查询参考https://blog.csdn.net/ws6afa88/article/details/108955852

数据库异常原因总结

数据库服务未开启,库名,表明字段名出现了该数据库专有的关键字,例如postgresql中user与name。 博主因为这个原因浪费一个上午的时光和一天的好心情。
其余原因比较简单容易察觉的我就不再这里列出了

Day5-6 登录校验与请求拦截,token

问题一 怎么样登录校验

  1. 准备表单
  2. 准备规则
  3. 将规则绑定到表单上(到这一步只是提示无法防止用户直接提交表单)
  4. 获取表单对象
  5. 对表单对象进行整体校验
<script lang="ts" setup>
import {reactive, ref} from "vue";
import 'element-plus/theme-chalk/el-message.css'
import { ElMessage } from 'element-plus'
import {useRouter} from "vue-router";
import {useUserStore} from "@/pages/news/dataStore/userdata.js";
const userStore = useUserStore()
//reactive 用于将对象转化为响应式对象,在vue3中,ref只能用于基本类型,reactive可以用于对象
const form = reactive({username: '',password: '',agree: false
})//校验规则,校验名必须与form中的属性名一致,按照产品经理的要求,用户名和密码都是必填项
const rules = {username: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 4, max: 14, message: '长度在 4 到 14 个字符', trigger: 'blur' }],agree: [{validator: (rule, value, callback) => {if (!value) {callback(new Error('请勾选协议'))} else {callback()}}}]
}
//获取form实例
const formRef = ref(null)
//获取路由实例
const router = useRouter()
const handleLogin = () => {//触发表单校验formRef.value.validate(async (valid) => {if (valid) {//校验成功console.log('校验成功')//todo 登录逻辑const {username, password} = formawait userStore.getUserInfo({username, password})console.log(userStore.userInfo)//todo 登录成功后跳转到首页if (userStore.userInfo.code === 1) {//todo 提示登录成功ElMessage.success("登录成功")//todo 保存token//localStorage.setItem('token', res.data.token)//todo 跳转到首页await router.replace('/')}} else {//校验失败console.log('校验失败')}})console.log(form)
}</script>
<template>
<div class="background">
<!--让elcard居中透明,上下也居中,不要遮挡背景--><el-card class="loginCard" shadow="hover" style="width: 400px; margin: 200px auto 0;background: rgba(7,189,255,0.3)"><el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="80px" status-icon>
<!--            prop指定校验名--><el-form-item prop="username" >
<!--              #号的作用是将内容放到label中--><template #label><span style="color: #eeff00">用户名</span></template><el-input v-model="form.username" /></el-form-item><el-form-item prop="password" ><template #label><span style="color: #eeff00">密码</span></template><el-input v-model="form.password" /></el-form-item><el-form-item prop="agree" label-width="22px"><el-checkbox size="large" v-model="form.agree"><i style="color: #00ffc4"> 我已同意隐私条款和服务条款</i></el-checkbox></el-form-item></el-form><!-- 登录按钮水平居中--><div style="text-align: center"><el-button type="primary" @click="handleLogin">登录</el-button></div></el-card>
</div>
</template>
<style scoped>
.background{width: 100%;height: 100%;background: url("../../assets/loginbg.jpg");background-size:100% 100%;position: fixed;top: 0;left: 0;
}
.loginCard{margin-top: 200px;
}
</style>

token校验

  1. 向后端请求登录(一般校验到这就结束了,但因为是登录我们还需要之后保持token完成跳转功能)
  2. 后端返回token,配置拦截器
import com.kinman.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration//配置类
public class WebConfigure implements WebMvcConfigurer {@Autowiredprivate LoginCheckInterceptor loginCheckInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");}
}

后端拦截器

package com.kinman.interceptor;import com.kinman.pojo.Result;
import com.kinman.utils.JwtUtils;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {//1.获取请求url。String url = req.getRequestURL().toString();log.info("请求的url: {}",url);//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。if(url.contains("login")){log.info("登录操作, 放行...");return true;}//3.获取请求头中的令牌(token)。String jwt = req.getHeader("token");//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。if(!StringUtils.hasLength(jwt)){log.info("请求头token为空,返回未登录的信息");Result error = Result.error("NOT_LOGIN");//手动转换 对象--json --------> 阿里巴巴fastJSONString notLogin = JSONObject.toJSONString(error);//手动写入响应体,返回给前端,前端解析json,如果是未登录,跳转到登录页面resp.getWriter().write(notLogin);return false;}//5.解析token,如果解析失败,返回错误结果(未登录)。try {JwtUtils.parseJWT(jwt);} catch (Exception e) {//jwt解析失败e.printStackTrace();log.info("解析令牌失败, 返回未登录错误信息");Result error = Result.error("NOT_LOGIN");//手动转换 对象--json --------> 阿里巴巴fastJSONString notLogin = JSONObject.toJSONString(error);resp.getWriter().write(notLogin);return false;}//6.放行。log.info("令牌合法, 放行");return true;}@Override //目标资源方法运行后运行public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle ...");}@Override //视图渲染完毕后运行, 最后运行public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion...");}
}
  1. 前端使用pinia或者使用localstorage进行持久化存储,使用方法见登录校验处的代码
import {defineStore} from "pinia";
import {ref} from "vue";
import {loginAPI} from "@/pages/news/apis/usersHandler"; // 1️⃣ import defineStore
export const useUserStore = defineStore("user", ()=>{ // 2️⃣ define a storeconst userInfo=ref({})// 3️⃣ add some state// 4️⃣ 定义一些actionsconst getUserInfo=async ({username,password})=>{userInfo.value=await loginAPI({username, password})}const clearUserInfo=()=>{userInfo.value={}}// 5️⃣ return the state and the actionsreturn {userInfo,getUserInfo,clearUserInfo}
},{persist: true,
});

10.前端在请求拦截器请求处加上token字段,以及在后端响应处理后端传来token校验失败的信息

httpRequest.interceptors.request.use(config => {// 1.当发送网络请求时, 在页面中添加一个loading组件, 作为动画// 2.某些网络请求要求用户必须登录, 判断用户是否有token, 如果没有token跳转到login页面// 3.对请求的参数进行序列化(看服务器是否需要序列化)const userStore = useUserStore()const token=userStore.userInfo.data?userStore.userInfo.data.token:''if (token) {config.headers['token'] = token}//拦截管理员请求,验证用户权限,如果identity 为true放行,否则抛出错误if (config.url.includes("/ad")){if (!userStore.userInfo.data.user.identity){ElMessage({type: 'warning',message: '您没有权限访问该页面'})throw new Error('您没有权限访问该页面')}}return config;
}, error => {return Promise.reject(error);}
)
// 响应拦截器
httpRequest.interceptors.response.use(response => {//   console.log(response)//一般而言,只需要返回data即可if(response.data.code === 0){ElMessage({type: 'warning',message: response.data.msg})if (response.data.msg === 'NOT_LOGIN') {const userStore = useUserStore()userStore.clearUserInfo()router.push('/login')}}return response.data;
}, error => {return Promise.reject(error);}
)
export default httpRequest;  

前端项目地址
现在已经暂时完结,感谢各位陪伴


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部