Spring Boot项目(Vue3+ElementPlus+Axios+MyBatisPlus+Spring Boot前后端分离)
下载地址:
前端:https://download.csdn.net/download/2401_83418369/90811402
后端:https://download.csdn.net/download/2401_83418369/90811405
一、前端vue部分的搭建
这里直接看另一期刊的搭建Vue前端工程部分
前端vue+后端ssm项目_vue框架和ssm框架配置-CSDN博客
二、修改项目基础页面
修改App.vue页面
修改HomeView.vue 页面
删除HelloWorld.vue组件,新增一个组件Header
<script >
export default {name:"Header"
}
</script><template><div style="height: 50px; line-height: 50px; border-bottom: 1px solid #ccc; display:
flex"><div style="width: 200px; padding-left: 30px; font-weight: bold; color: dodgerblue">后台管理</div><div style="flex: 1"></div><div style="width: 100px">下拉框</div></div>
</template><style scoped></style>
创建全局样式global.css
* {margin: 0;padding: 0;box-sizing: border-box;
}
不要忘记在main.js文件中导入(导入才能生效)
导入 ElementPlus并运用
快速开始 | Element Plus (element-plus.org)
测试一下:添加一个按钮
增加下拉列表的组件
<script >
export default {name:"Header"
}
</script><template><div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc;display: flex"><div style="width: 200px;padding-left: 30px;font-weight: bold;color:dodgerblue">后台管理</div><div style="flex: 1"></div><div style="width: 100px"><el-dropdown><span class="el-dropdown-link">tom<el-icon class="el-icon--right"><arrow-down /></el-icon></span><template #dropdown><el-dropdown-menu><el-dropdown-item>个人信息</el-dropdown-item><el-dropdown-item>退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div></div>
</template><style scoped></style>
导入到App.vue页面
<template><div>
<!-- 引用组件--><Header/><div style="display: flex"><!-- 侧边栏:将侧边栏放入盒子里面然后设置盒子的展示是弹性的--><Aside/>
<!-- 通过路由展示--><router-view style="flex: 1"/></div></div>
</template>
<style>
</style>
<script>
import Header from "@/components/Header.vue";
import Aside from "@/components/Aside.vue";
export default {name:"Layout",components:{Header,Aside}
}
</script>
在HomeView.vue页面添加一个按钮
添加 增加、查询的组件和一个表格
<template>
<!--这个主要是用来路由到的页面,默认访问的页面-->
<div>
<!-- 添加按钮和查询框--><div style="margin: 10px 5px;display:inline-block"><el-button type="primary">新增</el-button><el-button>其他</el-button></div><div style="display: inline-block"><el-input v-model="input" style="width: 150px" placeholder="请输入关键字" /><el-button type="success">提交</el-button></div>
<!-- 表格--><el-table :data="tableData" stripe style="width: 100%"><el-table-column sortable prop="date" label="日期" /><el-table-column prop="name" label="姓名" /><el-table-column prop="address" label="地址" /><el-table-column fixed="right" label="操作" min-width="120"><template #default><el-button link type="primary" @click="handleClick">删除</el-button><el-button link type="primary">编辑</el-button></template></el-table-column></el-table>
</div>
</template><script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue'export default {name: 'HomeView',components: {},//数据池的方法data(){return{tableData:[{date: '2016-05-03',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},]}}
}
</script>
三、后端springboot环境搭建
主要是配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.study</groupId><artifactId>springboot_furn</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.5.3</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.17</version></dependency><!-- 引入Mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency></dependencies></project>
在application.yaml文件 配置数据源和端口
创建启动类
四、添加家居
完成后台代码从 mapper -> service -> controller , 并对每层代码进行测试 , 到 controller 这一层,使用 Postman 发送 http 请求完成测试
创建数据库和表
CREATE DATABASE springboot_vue;USE springboot_vue;CREATE TABLE furn(
`id` INT(11) PRIMARY KEY AUTO_INCREMENT, ## id
`name` VARCHAR(64) NOT NULL, ## 家居名
`maker` VARCHAR(64) NOT NULL, ## 厂商
`price` DECIMAL(11,2) NOT NULL, ## 价格
`sales` INT(11) NOT NULL, ## 销量
`stock` INT(11) NOT NULL ## 库存
);INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)
VALUES(NULL , '北欧风格小桌子' , '熊猫家居' , 180 , 666 , 7);
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)
VALUES(NULL , '简约风格小椅子' , '熊猫家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '典雅风格小台灯' , '蚂蚁家居' , 180 , 666 , 7 );
INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock` )
VALUES(NULL , '温馨风格盆景架' , '蚂蚁家居' , 180 , 666 , 7 );SELECT * FROM furn;
创建 Result.java 该工具类用于返回结果(json 格式),这个工具类,在网上也可找到,直接拿来使用 , SSM 项目也用过类似的工具类
package com.study.furn.utils;public class Result<T> {private String code;private String msg;private T data;public Result() {}public Result(String code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}public String getCode() {return code;}public void setCode(String 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;}public Result(T data){this.data=data;}//返回成功的result不携带data数据public static Result success(){Result<Object> objectResult = new Result<>();objectResult.setCode("200");objectResult.setMsg("success");return objectResult;}//返回成功的result携带data数据,将静态方法定义为泛型方法,这个泛型方法和泛型类的泛型参数是独立的//静态方法的泛型类型参数与泛型类的类型参数名称相同但是编译器会进行区分public static<K> Result success(K data){Result<K> tResult = new Result<>(data);tResult.setCode("200");tResult.setMsg("success");return tResult;}//返回错误的result不携带datapublic static Result error(String code,String msg){Result<Object> objectResult = new Result<>();objectResult.setCode(code);objectResult.setMsg(msg);return objectResult;}//返回失败的result携带data数据public static<K> Result error(K data,String code,String msg){Result<K> tResult = new Result<>(data);tResult.setCode(code);tResult.setMsg(msg);return tResult;}}
编写dao层
这里直接继承父接口BaseMapeer
package com.study.furn.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.study.furn.bean.Furn;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface FurnMapper extends BaseMapper<Furn> {
}
在测试类中进行测试 :
编写Service层
package com.study.furn.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.study.furn.bean.Furn;public interface FurnService extends IService<Furn> {
}
package com.study.furn.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.study.furn.bean.Furn;
import com.study.furn.dao.FurnMapper;
import com.study.furn.service.FurnService;
import org.springframework.stereotype.Service;@Service
public class FurnServiceImpl extends ServiceImpl<FurnMapper, Furn> implements FurnService {
}
测试类中进行测试:
编写controller层
package com.study.furn.controller;import com.study.furn.bean.Furn;
import com.study.furn.service.FurnService;
import com.study.furn.utils.Result;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;/*** 这里使用复合注解确保所有的方法返回给前端的数据都是json格式的数据*/
@RestController
public class FurnController {@Resourceprivate FurnService furnService;/*** 这是一个保存Furn对象的方法* @param furn* @return*/@PostMapping("/save")public Result save(@RequestBody Furn furn){furnService.save(furn);return Result.success();}
}
使用Postman进行测试:
注意细节:
1、@RequestBody注解是将json格式的数据封装到方法的参数对象中,如果前端发送的表单数据/请求参数,不使用@RequestBody注解也会进行自动封装(还要注意的是请求头的Content-Type要和请求的数据类型匹配:表单的数据类型是application/x-www-form-urlencoded,json数据类型是application/json)
2、使用@RequestBody注解接收 JSON 时,若 ID 字段显式设为 null
,可能导致 ORM(如 Hibernate)抛出异常,解决的方法是在id字段上添加@TableId(type = IdType.AUTO)注解
在前端项目中添加表单组件
<template><!--这个主要是用来路由到的页面,默认访问的页面--><div><!-- 添加按钮和查询框--><div style="margin: 10px 5px;display:inline-block"><el-button type="primary" @click="add">新增</el-button><el-button>其他</el-button></div><div style="display: inline-block"><el-input v-model="input" style="width: 150px" placeholder="请输入关键字" /><el-button type="success">提交</el-button></div><!-- 表格--><el-table :data="tableData" stripe style="width: 100%"><el-table-column sortable prop="date" label="日期" /><el-table-column prop="name" label="姓名" /><el-table-column prop="address" label="地址" /><el-table-column fixed="right" label="操作" min-width="120"><template #default><el-button link type="primary" @click="handleClick">删除</el-button><el-button link type="primary">编辑</el-button></template></el-table-column></el-table><!-- 下面是对话框和表单<el-input v-model="form.name" style="width: 80%"></el-input>这里的form.name 表示form对象的属性name必须和后端的对象字段一样因为要将这些字段信息生成json格式打包给后端,这里的属性名可以动态生成,不需要在数据池里面编写--><el-dialogtitle="提示"v-model="dialogVisible"width="30%"><el-form :model="form" label-width="120px"><!-- 家居名 --><el-form-item label="家居名"><el-input v-model="form.name" style="width: 80%"></el-input></el-form-item><!-- 厂商 --><el-form-item label="厂商"><el-input v-model="form.maker" style="width: 80%"></el-input></el-form-item><!-- 价格 --><el-form-item label="价格"><el-input v-model="form.price" style="width: 80%"></el-input></el-form-item><!-- 销量 --><el-form-item label="销量"><el-input v-model="form.sales" style="width: 80%"></el-input></el-form-item><!-- 库存 --><el-form-item label="库存"><el-input v-model="form.stock" style="width: 80%"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer">
<!-- 直接写dialogVisible = false,因为只用一句,所以可以直接写在属性上--><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="save">确定</el-button></span></template></el-dialog></div>
</template><script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue'
export default {name: 'HomeView',components: {},//数据池的方法data(){return{//这里默认表单是不显示的dialogVisible:false,form:{},tableData:[{date: '2016-05-03',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},]}},methods:{add(){this.dialogVisible=true;//但调用该方法后,将form对象的信息进行清除,// 因为表单里的数据和数据池的数据是双向绑定的如果填写了表单的数据那么数据池的数据就有了// 防止下次点击后出现上一次填写的数据,必须清空数据池的数据this.form={}}}
}
</script>
在vue项目的终端中安装Axios
npm i axios -S
编写request请求对象
//导入Axios包
import axios from "axios";
//通过Axios创建对象Request 发送请求到后端
const request = axios.create({timeout:5000
})
//Request拦截器加上统一的处理
//比如Content-Type,请求头中添加Content-Type表示请求体中的数据类型是json数据,
// 后端@RequestBody注解在内容协商中能匹配成功
request.interceptors.request.use(config=>{config.headers['Content-Type'] = 'application/json;charset=utf-8'return config
},error=>{return Promise.reject(error)
})//response拦截器拦截响应对象,统一处理返回的结果
request.interceptors.response.use(response=>{let res = response.data//如果返回的是文件,就返回if (response.config.responseType==='blob'){return res}//如果是String,就转成json对象if (typeof res==='string'){res = res ? JSON.parse(res):res}return res
},error=>{return Promise.reject(error)
})//导出Request对象,在其他组件就可以使用
export default request
在HomeView.vue页面中编写save方法发送json数据给后端
<template><!--这个主要是用来路由到的页面,默认访问的页面--><div><!-- 添加按钮和查询框--><div style="margin: 10px 5px;display:inline-block"><el-button type="primary" @click="add">新增</el-button><el-button>其他</el-button></div><div style="display: inline-block"><el-input v-model="input" style="width: 150px" placeholder="请输入关键字" /><el-button type="success">提交</el-button></div><!-- 表格--><el-table :data="tableData" stripe style="width: 100%"><el-table-column sortable prop="date" label="日期" /><el-table-column prop="name" label="姓名" /><el-table-column prop="address" label="地址" /><el-table-column fixed="right" label="操作" min-width="120"><template #default><el-button link type="primary" @click="handleClick">删除</el-button><el-button link type="primary">编辑</el-button></template></el-table-column></el-table><!-- 下面是对话框和表单<el-input v-model="form.name" style="width: 80%"></el-input>这里的form.name 表示form对象的属性name必须和后端的对象字段一样因为要将这些字段信息生成json格式打包给后端,这里的属性名可以动态生成,不需要在数据池里面编写--><el-dialogtitle="提示"v-model="dialogVisible"width="30%"><el-form :model="form" label-width="120px"><!-- 家居名 --><el-form-item label="家居名"><el-input v-model="form.name" style="width: 80%"></el-input></el-form-item><!-- 厂商 --><el-form-item label="厂商"><el-input v-model="form.maker" style="width: 80%"></el-input></el-form-item><!-- 价格 --><el-form-item label="价格"><el-input v-model="form.price" style="width: 80%"></el-input></el-form-item><!-- 销量 --><el-form-item label="销量"><el-input v-model="form.sales" style="width: 80%"></el-input></el-form-item><!-- 库存 --><el-form-item label="库存"><el-input v-model="form.stock" style="width: 80%"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer">
<!-- 直接写dialogVisible = false,因为只用一句,所以可以直接写在属性上--><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="save">确定</el-button></span></template></el-dialog></div>
</template><script>
// @ is an alias to /src
import request from "@/utils/request"
export default {name: 'HomeView',components: {},//数据池的方法data(){return{//这里默认表单是不显示的dialogVisible:false,form:{},tableData:[{date: '2016-05-03',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},]}},methods:{add(){this.dialogVisible=true;//但调用该方法后,将form对象的信息进行清除,// 因为表单里的数据和数据池的数据是双向绑定的如果填写了表单的数据那么数据池的数据就有了// 防止下次点击后出现上一次填写的数据,必须清空数据池的数据this.form={}},save(){request.post("http://localhost:9090/save",this.form).then(response=>{console.log("response",response)})}}
}
</script>
这里遇到一个跨域请求的问题:
跨域请求问题是指由于浏览器的**同源策略(Same-Origin Policy)**限制,导致网页无法直接访问不同源(协议、域名、端口任一不同)的资源,从而引发的访问限制问题。
1. 跨域问题的本质
跨域问题的根源是浏览器的同源策略,该策略规定:
- 同源条件:协议、域名、端口必须完全相同。例如,
https://example.com
与http://example.com
(协议不同)、example.com
与api.example.com
(子域名不同)均属于跨域
- 目的:防止恶意网站通过脚本窃取敏感数据(如Cookie、用户信息等),确保用户数据安全。
2. 跨域请求的触发场景
以下情况会触发跨域限制:
- Ajax/Fetch请求:前端通过JavaScript发起的HTTP请求目标不同源的服务端接口。
- 资源加载:跨域加载图片、CSS、JavaScript文件等静态资源。
- Web API调用:如调用第三方API(如支付接口、地图服务)时,若未配置CORS则会被拦截。
3. 跨域问题的表现
浏览器拦截:即使请求成功发送到服务端并返回数据,浏览器仍会拦截响应,导致前端无法获取结果。
错误提示:常见控制台报错如 No 'Access-Control-Allow-Origin' header is present
解决方法:代理服务器
- 原理:前端请求同源代理服务器,由代理转发请求至目标服务器,规避浏览器限制。常用工具如Nginx或后端框架(如Spring Boot)的代理配置
在vue.config.js文件中添加代理
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true
})module.exports={devServer:{port:10000 ,//启动端口// 设置代理proxy:{'/api':{ //设置拦截器,拦截器的格式 斜杆+名字 拦截规则:匹配所有以/api开头的请求路径target:'http://localhost:9090', //目标服务器地址:将匹配的请求转发到http://localhost:9090changeOrigin:true, //设置同源pathRewrite:{ //路径重写'/api':'' //api被替换为空,前端统一使用代理前缀(如 /api),但后端接口路径不含该前缀//前端请求:/api/user/list//实际转发:http://backend.com/user/list// /api 这部分被 target的路径代替}}}}
}
在HomeView.vue页面中进行修改
<template><!--这个主要是用来路由到的页面,默认访问的页面--><div><!-- 添加按钮和查询框--><div style="margin: 10px 5px;display:inline-block"><el-button type="primary" @click="add">新增</el-button><el-button>其他</el-button></div><div style="display: inline-block"><el-input v-model="input" style="width: 150px" placeholder="请输入关键字" /><el-button type="success">提交</el-button></div><!-- 表格--><el-table :data="tableData" stripe style="width: 100%"><el-table-column sortable prop="date" label="日期" /><el-table-column prop="name" label="姓名" /><el-table-column prop="address" label="地址" /><el-table-column fixed="right" label="操作" min-width="120"><template #default><el-button link type="primary" @click="handleClick">删除</el-button><el-button link type="primary">编辑</el-button></template></el-table-column></el-table><!-- 下面是对话框和表单<el-input v-model="form.name" style="width: 80%"></el-input>这里的form.name 表示form对象的属性name必须和后端的对象字段一样因为要将这些字段信息生成json格式打包给后端,这里的属性名可以动态生成,不需要在数据池里面编写--><el-dialogtitle="提示"v-model="dialogVisible"width="30%"><el-form :model="form" label-width="120px"><!-- 家居名 --><el-form-item label="家居名"><el-input v-model="form.name" style="width: 80%"></el-input></el-form-item><!-- 厂商 --><el-form-item label="厂商"><el-input v-model="form.maker" style="width: 80%"></el-input></el-form-item><!-- 价格 --><el-form-item label="价格"><el-input v-model="form.price" style="width: 80%"></el-input></el-form-item><!-- 销量 --><el-form-item label="销量"><el-input v-model="form.sales" style="width: 80%"></el-input></el-form-item><!-- 库存 --><el-form-item label="库存"><el-input v-model="form.stock" style="width: 80%"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer">
<!-- 直接写dialogVisible = false,因为只用一句,所以可以直接写在属性上--><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="save">确定</el-button></span></template></el-dialog></div>
</template><script>
// @ is an alias to /src
import request from "@/utils/request"
export default {name: 'HomeView',components: {},//数据池的方法data(){return{//这里默认表单是不显示的dialogVisible:false,form:{},tableData:[{date: '2016-05-03',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-02',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-04',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},{date: '2016-05-01',name: 'Tom',address: 'No. 189, Grove St, Los Angeles',},]}},methods:{add(){this.dialogVisible=true;//但调用该方法后,将form对象的信息进行清除,// 因为表单里的数据和数据池的数据是双向绑定的如果填写了表单的数据那么数据池的数据就有了// 防止下次点击后出现上一次填写的数据,必须清空数据池的数据this.form={}},save(){request.post("/api/save",this.form).then(response=>{console.log("response",response)this.dialogVisible=false})}}
}
</script>
1. 代理机制的核心逻辑
通过将浏览器发起的跨域请求 转发到同源的后端代理服务,再由代理服务转发到实际目标服务器,从而绕过浏览器的同源策略限制。具体流程如下:
- 前端请求路径:前端代码向同源地址(如
http://localhost:10000/api/data
)发送请求。 - 代理拦截:webpack-dev-server 根据
/api
规则拦截请求,替换目标地址为http://localhost:9090/data
(通过pathRewrite
移除/api
前缀)。 - 服务端转发:代理服务将请求转发到目标服务器,并返回响应给前端。
- 浏览器无感知:浏览器始终认为请求来自同源(
localhost:10000
),因此不会触发跨域拦截
过程:浏览器发送请求到vue,vue中进行拦截请求替换目标地址再请求到后端,后端响应数据到前端
2. 关键配置项的作用
(1)target: 'http://localhost:9090'
- 功能:指定目标服务器的真实地址,告知代理将请求转发至此地址。
- 本质:将前端路径中的
/api
替换为http://localhost:9090
,实现请求重定向
2)changeOrigin: true
- 作用:修改请求头中的
Host
和Origin
为目标服务器地址(localhost:9090
),使目标服务器认为请求来自合法源。 - 必要性:部分服务器会验证请求来源,若未设置此参数,目标服务器可能因
Host
不匹配而拒绝请求
3)pathRewrite: { '/api': '' }
- 功能:重写请求路径,移除前端添加的
/api
前缀。例如,/api/user
将被改写为/user
,确保目标接口路径正确。 - 灵活性:允许前端统一使用代理前缀,后端接口无需修改即可适配
3. 代理为何能绕过跨域限制?
(1)浏览器同源策略的规避
- 浏览器视角:所有请求均指向本地开发服务器(如
localhost:10000
),未触发跨域规则。 - 实际路径:代理服务器作为中间层,将请求转发至外部目标服务器(
localhost:9090
),而 服务器间通信不受浏览器同源策略限制
4. 与其他方案的对比
方案 | 优势 | 局限性 |
---|---|---|
代理服务器 | 无需后端配合修改,适合本地开发调试;配置简单灵活 | 仅适用于开发环境,生产环境需通过Nginx等实现代理 |
CORS | 标准化解决方案,支持所有HTTP方法 | 需后端配置响应头,对第三方API无法控制 |
JSONP | 兼容老旧浏览器 | 仅支持GET请求,存在安全风险 |
5. 注意事项
- 仅限开发环境:代理配置通常在
webpack.config.js
或vue.config.js
中设置,不适用于生产环境。生产环境需通过Nginx反向代理或后端服务处理跨域 - 复杂路径匹配:可通过正则表达式定义更灵活的代理规则(如
/api/**
匹配多级路径),满足复杂接口需求
进行测试:
五、显示家居信息
完成后台代码从 mapper -> service -> controller , 并对每层代码进行测试 , 到 controller 这一层,使用 Postman 发送 http 请求完成测试,由于mybatis-plus已经提供了父接口的方法,所以不需要再编写查询方法
在controller层添加查询方法
使用Postman进行测试
在前端vue项目中修改,先清空数据池中tableData中的数据
表格部分的字段进行修改
下面是Template标签 的组件
<template><!--这个主要是用来路由到的页面,默认访问的页面--><div><!-- 添加按钮和查询框--><div style="margin: 10px 5px;display:inline-block"><el-button type="primary" @click="add">新增</el-button><el-button>其他</el-button></div><div style="display: inline-block"><el-input v-model="input" style="width: 150px" placeholder="请输入关键字" /><el-button type="success">提交</el-button></div><!-- 表格:data="tableData" 单向渲染数据,从数据池的data数据池的tableData字段里获取数据,<=> v-bind:data --><el-table :data="tableData" stripe style="width: 100%"><el-table-column sortable prop="id" label="ID" /><el-table-column prop="name" label="家居名" /><el-table-column prop="maker" label="厂家" /><el-table-column prop="price" label="价格" /><el-table-column prop="sales" label="销量" /><el-table-column prop="stock" label="库存" /><el-table-column fixed="right" label="操作" min-width="120"><template #default><el-button link type="primary" @click="handleClick">删除</el-button><el-button link type="primary">编辑</el-button></template></el-table-column></el-table><!-- 下面是对话框和表单<el-input v-model="form.name" style="width: 80%"></el-input>这里的form.name 表示form对象的属性name必须和后端的对象字段一样因为要将这些字段信息生成json格式打包给后端,这里的属性名可以动态生成,不需要在数据池里面编写--><el-dialogtitle="提示"v-model="dialogVisible"width="30%"><el-form :model="form" label-width="120px"><!-- 家居名 --><el-form-item label="家居名"><el-input v-model="form.name" style="width: 80%"></el-input></el-form-item><!-- 厂商 --><el-form-item label="厂商"><el-input v-model="form.maker" style="width: 80%"></el-input></el-form-item><!-- 价格 --><el-form-item label="价格"><el-input v-model="form.price" style="width: 80%"></el-input></el-form-item><!-- 销量 --><el-form-item label="销量"><el-input v-model="form.sales" style="width: 80%"></el-input></el-form-item><!-- 库存 --><el-form-item label="库存"><el-input v-model="form.stock" style="width: 80%"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer">
<!-- 直接写dialogVisible = false,因为只用一句,所以可以直接写在属性上--><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="save">确定</el-button></span></template></el-dialog></div>
</template>
添加list方法
将data字段的数据填充到数据池中
注意response拦截器进行了拦截处理:将字符串转成json格式的对象,并且将变量进行赋值
<template><!--这个主要是用来路由到的页面,默认访问的页面--><div><!-- 添加按钮和查询框--><div style="margin: 10px 5px;display:inline-block"><el-button type="primary" @click="add">新增</el-button><el-button>其他</el-button></div><div style="display: inline-block"><el-input v-model="input" style="width: 150px" placeholder="请输入关键字" /><el-button type="success">提交</el-button></div><!-- 表格:data="tableData" 单向渲染数据,从数据池的data数据池的tableData字段里获取数据,<=> v-bind:data --><el-table :data="tableData" stripe style="width: 100%"><el-table-column sortable prop="id" label="ID" /><el-table-column prop="name" label="家居名" /><el-table-column prop="maker" label="厂家" /><el-table-column prop="price" label="价格" /><el-table-column prop="sales" label="销量" /><el-table-column prop="stock" label="库存" /><el-table-column fixed="right" label="操作" min-width="120"><template #default><el-button link type="primary" @click="handleClick">删除</el-button><el-button link type="primary">编辑</el-button></template></el-table-column></el-table><!-- 下面是对话框和表单<el-input v-model="form.name" style="width: 80%"></el-input>这里的form.name 表示form对象的属性name必须和后端的对象字段一样因为要将这些字段信息生成json格式打包给后端,这里的属性名可以动态生成,不需要在数据池里面编写--><el-dialogtitle="提示"v-model="dialogVisible"width="30%"><el-form :model="form" label-width="120px"><!-- 家居名 --><el-form-item label="家居名"><el-input v-model="form.name" style="width: 80%"></el-input></el-form-item><!-- 厂商 --><el-form-item label="厂商"><el-input v-model="form.maker" style="width: 80%"></el-input></el-form-item><!-- 价格 --><el-form-item label="价格"><el-input v-model="form.price" style="width: 80%"></el-input></el-form-item><!-- 销量 --><el-form-item label="销量"><el-input v-model="form.sales" style="width: 80%"></el-input></el-form-item><!-- 库存 --><el-form-item label="库存"><el-input v-model="form.stock" style="width: 80%"></el-input></el-form-item></el-form><template #footer><span class="dialog-footer">
<!-- 直接写dialogVisible = false,因为只用一句,所以可以直接写在属性上--><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" @click="save">确定</el-button></span></template></el-dialog></div>
</template><script>
//导入组件
import request from "@/utils/request";
export default {name: 'HomeView',components: {},//数据池的方法data(){return{//这里默认表单是不显示的dialogVisible:false,form:{},tableData:[]}},//钩子函数, created()函数调用后//数据池和方法池的数据都进行了初始化created() {//调用list方法展示数据this.list()},methods:{list(){request.get("/api/list").then(response=>{this.tableData=response.data})},add(){this.dialogVisible=true;//但调用该方法后,将form对象的信息进行清除,// 防止下次点击后出现上一次填写的数据this.form={}},save(){request.post("/api/save",this.form).then(response=>{console.log("response",response)this.dialogVisible=false//增加家居后调用查询this.list()})},}
}
</script>
六、修改家居信息
完成后台代码从 mapper -> service -> controller , 并对每层代码进行测试 , 到 controller 这一层,使用 Postman 发送 http 请求完成测试,由于mybatis-plus已经提供了父接口的方法,所以不需要再编写修改方法
在controller层添加修改方法
使用Postman进行测试
在前端vue项目中添加方法得到回显的数据
直接通过前端scope.row获取当前行的代理对象
发现这是个代理对象,需要转换 Proxy 为原始对象
将代理对象转成原始对象,再将原始json对象绑定到 form表单中并进行展示
save方法如下:根据form表单中是否有数据来区分要进行增加数据还是修改数据,同时根据响应的状态码来回显状态信息
save(){//添加、修改if (this.form.id){//如果表单中有数据id就是修改操作request.put("/api/update",this.form).then(response=>{if (response.code==="200"){//响应的状态码ElMessage({type:"success",message:"修改成功"})}else {ElMessage({type:"error",message:"修改失败"})}//刷新数据this.list()//关闭对话框this.dialogVisible=false})}else {//添加request.post("/api/save",this.form).then(response=>{console.log("response",response)//增加家居后调用查询,刷新数据this.list()this.dialogVisible=false})}}
补充回显数据的方式2:
将代理对象的id取出,然后将id发送给后端,后端根据id查询对应的Furn对象,然后再将对象进行回显
在HomeView.vue页面中添加一个方法
同样能回显数据,这种方式回显才能确保数据库的真实数据
七、删除家居信息
完成后台代码从 mapper -> service -> controller , 并对每层代码进行测试 , 到 controller 这一层,使用 Postman 发送 http 请求完成测试,由于mybatis-plus已经提供了父接口的方法,所以不需要再编写删除方法
在controller层添加删除方法
使用Postman进行测试
在前端vue实现删除的方法
将删除按钮进行修改
实现删除的方法
删除按钮:
<!-- 删除按钮 --><el-popconfirm title="确认删除吗?" @confirm="handleDel(scope.row.id)"><template #reference><el-button size="small" type="danger">删除</el-button></template></el-popconfirm>
删除方法:
handleDel(id){request.delete("/api/delete?id="+id).then(response=> {if (response.code === "200") {//响应的状态码ElMessage({type: "success",message: "删除成功"})} else {ElMessage({type: "error",message:"删除失败"})}//刷新数据this.list()})}
八、分页显示列表
在配置类中配置分页拦截器
package com.study.furn.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 这是一个mybatis-plus配置类,在这里配置分页插件*/
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//配置分页拦截器mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return mybatisPlusInterceptor;}
}
在controller层添加分页方法
使用Postman进行测试
在表格下面添加导航栏组件
<!-- 添加分页导航--><div style="margin:10px 0"><el-pagination@size-change="handlePageSizeChange"@current-change="handleCurrentChange":current-page="currentPage":page-sizes="[5, 10]":page-size="pageSize"layout="total, sizes, prev, pager, next, jumper":total="total"></el-pagination></div>
数据池中绑定初始化数据
完善导航栏的两个方法,双向数据绑定
修改list方法为分页方法
修改请求的路径并传两个参数(当前页,每页的数量)给后端,后端返回的total绑定到数据池
list方法如下 :
list(){// request.get("/api/list").then(response=>{// this.tableData=response.data// })request.get("/api/page",{params:{pageNum:this.currentPage, //传给后端,当前页是第几页pageSize:this.pageSize//传给后端,当前页的数量}}).then(response=>{this.tableData=response.data.recordsthis.total=response.data.total})},
九、切换数据源为druid数据源
新创建一个配置类
package com.study.furn.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;/*** 数据源配置类*/
@Configuration
public class DruidDataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource dataSource(){DruidDataSource druidDataSource = new DruidDataSource();return druidDataSource;}
}
在application.yaml文件中配置也是可以的
十、带条件查询分页显示列表
完成后台代码从 mapper -> service -> controller , 并对每层代码进行测试 , 到 controller 这一层,使用 Postman 发送 http 请求完成测试,由于mybatis-plus已经提供了父接口的方法,所以不需要再编写方法
在controller层添加方法
使用Postman进行测试
在查询输入框中绑定数据池,查询按钮中绑定点击方法
这里还是调用list方法
修改list方法
十一、添加家居表单完善前端校验
在数据池中添加校验规则
//校验规则rules:{name:[{required:true,message:"请输入家居名",trigger:"blur"}],maker:[{required:true,message:"请输入厂家名",trigger:"blur"}],price:[{required:true,message:"请输入价格",trigger:"blur"},{pattern:/^([1-9]\d*|0)(\.\d+)?$/ ,message: "请输入数字",trigger:"blur"}],sales:[{required:true,message:"请输入销量",trigger:"blur"},{pattern:/^([1-9]\d*|0)$/ ,message: "请输入数字",trigger:"blur"}],stock:[{required:true,message:"请输入库存",trigger:"blur"},{pattern:/^([1-9]\d*|0)$/ ,message: "请输入数字",trigger:"blur"}]}
表单中绑定校验规则
在点击确定按钮时进行校验,如果校验通过才发送请求到后端
清空上一次的校验
//清空上一次的校验this.$nextTick(() => {this.$refs['form'].resetFields();});
十二、添加家居表单完善后端校验
后端校验主要是防止别人绕过前端校验直接发送save请求到后端
引入jsr303数据校验支持
记得刷新Maven
使用注解对furn的字段进行校验
使用Postman进行测试
在数据池中添加一个后端校验对象
在save方法中添加代码根据后端的状态码来处理
在表单中展示后端的校验信息
清空上一次的后端校验信息
总结:
该项目使用了前后端分离,前端的主体框架Vue3+后端的基础框架Spring-Boot
1.前端技术栈: vue3 + Axios + ElementsPlus
2.后端技术栈:SpringBoot + MyBatis Plus
3.数据库-Mysql
4.项目依赖管理-Maven
5.分页-MyBatis Plus 分页插件
6.切换数据源DruidDataSources
7.项目前端我们使用到 request 和 response拦截器,并且我们解决了跨域问题
目前该项目还有很多bug,后续再完善...
相关文章:

Spring Boot项目(Vue3+ElementPlus+Axios+MyBatisPlus+Spring Boot前后端分离)
下载地址: 前端:https://download.csdn.net/download/2401_83418369/90811402 后端:https://download.csdn.net/download/2401_83418369/90811405 一、前端vue部分的搭建 这里直接看另一期刊的搭建Vue前端工程部分 前端vue后端ssm项目_v…...

Spyglass:在batch/shell模式下运行目标的顶层是什么?
相关阅读 Spyglasshttps://blog.csdn.net/weixin_45791458/category_12828934.html?spm1001.2014.3001.5482 除了可以在图形用户界面(GUI)中运行目标外,使用Batch模式或Shell模式也可以运行目标,如下面的命令所示。 % spyglass -project test.prj -ba…...
没有Mac,我是怎么上传IPA到App Store的?
没有Mac,我是怎么上传IPA到App Store的? 最近赶一个小项目上线,写的是一个Flutter做的App。安卓版本一晚上搞定,iOS上架却差点把人整崩。 不是我技术菜,是实在太麻烦了。最关键的,是我这台Windows笔电根本…...

微服务架构中如何保证服务间通讯的安全
在微服务架构中,保证服务间通信的安全至关重要。服务间的通信通常是通过HTTP、gRPC、消息队列等方式实现的,而这些通信链路可能面临多种安全风险。为了应对这些风险,可以采取多种措施来保证通信安全。 常见的服务间通信风险 1.数据泄露:在服务间通信过程中,敏感数据可能会…...
2025-05-11 项目绩效域记忆逻辑管理
好的,我们可以用一个故事来帮助记忆这些规划绩效域的要素,同时通过逻辑关系来串联它们。以下是一个故事化的版本: 《项目管理的奇幻之旅》 在一个遥远的王国里,有一个勇敢的项目经理名叫小K。小K被国王赋予了一个艰巨的任务&…...

工具篇-Cherry Studio之MCP使用
一、添加MCP 打开Cherry Studio,如果没有可以到官网下载:Cherry Studio 官方网站 - 全能的AI助手 按上面步骤打开同步服务器 1、先去注册ModelScope,申请令牌 2、再打开MCP广场,找到高德MCP 选择工具测试,这里有个高德的api key需要申请 打开如下地址高德开放平…...
DeepSeek“智”造:解锁旅游行业新玩法
目录 一、DeepSeek 简介1.1 DeepSeek 技术原理1.2 DeepSeek 在 AI 领域地位 二、DeepSeek 在旅游攻略生成的应用2.1 生成流程展示2.2 优势分析2.3 实际案例剖析 三、DeepSeek 助力旅游宣传文案创作3.1 文案创作模式3.2 效果评估3.3 创意亮点挖掘 四、DeepSeek 优化游客咨询服务…...
LOJ 6346 线段树:关于时间 Solution
Description 给定序列 a ( a 1 , a 2 , ⋯ , a n ) a(a_1,a_2,\cdots,a_n) a(a1,a2,⋯,an),另有一个存储三元组的列表 L L L. 有 m m m 个操作分两种: add ( l , r , k ) \operatorname{add}(l,r,k) add(l,r,k):将 ( l , r , …...
java 多核,多线程,分布式 并发编程的现状 :从本身的jdk ,到 spring ,到其它第三方。
Java 在多核、多线程和高性能编程领域提供了丰富的现成框架和工具,既有标准库中的并发组件,也有第三方框架。以下是一些关键框架及其应用场景的总结:便于后面我们站在巨人的肩膀上,继续前行 一、Java 标准库中的多线程框架 Execut…...
httpclient请求出现403
问题 httpclient请求对方服务器报403,用postman是可以的 解决方案: request.setHeader( “User-Agent” ,“Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0” ); // 设置请求头 原因: 因为没有设置为浏览器形式&#…...

Python 运维脚本
1、备份文件 import os import shutil# 定义配置文件目录和备份目录的路径 config_dir "/root/python/to/config/files/" backup_dir "/root/python/to/backup/"# 遍历配置文件目录中的所有文件 for filename in os.listdir(config_dir):# 如果文件名以…...
MySQL数据库常见面试题之三大范式
写在前面 此文章大部分不会引用最原始的概念,采用说人话的方式。 面试题:三大范式是什么?目的是什么?必须遵循吗? 假设有一张表(学号,姓名,课程,老师) 是…...

大模型项目:普通蓝牙音响接入DeepSeek,解锁语音交互新玩法
本文附带视频讲解 【代码宇宙019】技术方案:蓝牙音响接入DeepSeek,解锁语音交互新玩法_哔哩哔哩_bilibili 目录 效果演示 核心逻辑 技术实现 大模型对话(技术: LangChain4j 接入 DeepSeek) 语音识别(…...
C/C++复习--C语言隐式类型转换
目录 什么是隐式类型转换?整型提升 规则与示例符号位扩展的底层逻辑 算术转换 类型层次与转换规则混合类型运算的陷阱 隐式转换的实际应用与问题 代码示例分析常见错误与避免方法 总结与最佳实践 1. 什么是隐式类型转换? 隐式类型转换是C语言在编译阶段…...
Pandas 时间处理利器:to_datetime() 与 Timestamp() 深度解析
Pandas 时间处理利器:to_datetime() 与 Timestamp() 深度解析 在数据分析和处理中,时间序列数据扮演着至关重要的角色。Pandas 库凭借其强大的时间序列处理能力,成为 Python 数据分析领域的佼佼者。其中,to_datetime() 函数和 Ti…...

单链表设计与实现
01. 单链表简介 在数据结构中,单链表的实现可以分为 带头结点 和 不带头结点 两种方式,这里我们讨论第二种方式。 头结点:链表第一个节点不存实际数据,仅作为辅助节点指向首元节点(第一个数据节点)。头指…...
JDS-算法开发工程师-第9批
单选题 print(fn.__default__) 哪一个不是自适应学习率的优化算法 (选项:Adagrad,RMSprop,Adam,Momentum,动量法在梯度下降的基础上,加入了“惯性”概念,通过累积历史的梯度更新来加速收敛&…...
Git标签删除脚本解析与实践:轻松管理本地与远程标签
Git 标签删除脚本解析与实践:轻松管理本地与远程标签 在 Git 版本控制系统中,标签常用于标记重要的版本节点,方便追溯和管理项目的不同阶段。随着项目的推进,一些旧标签可能不再需要,此时就需要对它们进行清理。本文将通过一个完整的脚本,详细介绍如何删除本地和远程的 …...
Python中,async和with结合使用,有什么好处?
在Python的异步编程中,async和with的结合使用(即async with)为开发者提供了一种优雅且高效的资源管理模式。这种组合不仅简化了异步代码的编写,还显著提升了程序的健壮性和可维护性。以下是其核心优势及典型应用场景的分析&#x…...

springboot生成二维码到海报模板上
springboot生成二维码到海报模板上 QRCodeController package com.ruoyi.web.controller.app;import com.google.zxing.WriterException; import com.ruoyi.app.domain.Opportunity; import com.ruoyi.app.tool.QRCodeGenerator; import com.ruoyi.common.core.page.TableDat…...

SEO长尾关键词布局优化法则
内容概要 在SEO优化体系中,长尾关键词的精准布局是突破流量瓶颈的关键路径。相较于竞争激烈的核心词,长尾词凭借其高转化率和低竞争特性,成为内容矩阵流量裂变的核心驱动力。本节将系统梳理长尾关键词布局的核心逻辑框架,涵盖从需…...

python:trimesh 用于 STL 文件解析和 3D 操作
python:trimesh 是一个用于处理三维模型的库,支持多种格式的导入导出,比如STL、OBJ等,还包含网格操作、几何计算等功能。 Python Trimesh 库使用指南 安装依赖库 pip install trimesh Downloading trimesh-4.6.8-py3-none-any.w…...

应急响应基础模拟靶机-security2
PS:杰克创建的流量包(result.pcap)在root目录下,请根据已有信息进行分析 1、首个攻击者扫描端口使用的工具是? 2、后个攻击者使用的漏洞扫描工具是? 3、攻击者上传webshell的绝对路径及User-agent是什么? 4、攻击者反弹shell的…...
ROS 2 FishBot PID控制电机代码
#include <Arduino.h> #include <Wire.h> #include <MPU6050_light.h> #include <Esp32McpwmMotor.h> #include <Esp32PcntEncoder.h>Esp32McpwmMotor motor; // 创建一个名为motor的对象,用于控制电机 Esp32PcntEncoder enco…...
Bash 字符串语法糖详解
Bash 作为 Linux 和 Unix 系统中最常用的 Shell 之一,其字符串处理能力是脚本开发者的核心技能之一。为了让字符串操作更高效、更直观,Bash 提供了丰富的语法糖(syntactic sugar)。这些语法糖通过简洁的语法形式,隐藏了…...

OpenCV定位地板上的书
任务目标是将下面的图片中的书本找出来: 使用到的技术包括:转灰度图、提取颜色分量、二值化、形态学、轮廓提取等。 我们尝试先把图片转为灰度图,然后二值化,看看效果: 可以看到,二值化后,书的…...
C++ string初始化、string赋值操作、string拼接操作
以下介绍了string的六种定义方式,还有很多,这个只是简单举例 #include<iostream>using namespace std;int main() {//1 无参构造string s1;cout << s1 << endl;//2 初始化构造string s2 ({h, h, l, l, o});cout << s2 <<…...
linux动态占用cpu脚本、根据阈值增加占用或取消占用cpu的脚本、自动检测占用脚本状态、3脚本联合套用。
文章目录 说明流程占用脚本1.0版本使用测试占用脚本2.0版本使用测试测试脚本使用测试检测脚本使用测试脚本说明书启动说明停止说明内存占用cpu内存成品任务测试说明 cpu占用实现的功能整体流程 1、先获取当前实际使用率2、设置一个最低阈值30%,一个最高阈值80%、一个需要增加的…...

NHANES稀有指标推荐:MedHi
文章题目:Association of dietary live microbe intake with frailty in US adults: evidence from NHANES DOI:10.1016/j.jnha.2024.100171 中文标题:美国成人膳食活微生物摄入量与虚弱的相关性:来自 NHANES 的证据 发表杂志&…...
无人机空中物流优化:用 Python 打造高效配送模型
友友们好! 我是Echo_Wish,我的的新专栏《Python进阶》以及《Python!实战!》正式启动啦!这是专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会…...