vue setup+springboot 前后端分离新手必备知识点与请求实践(delete/put/restful/图片上传)
今天我要分享的是前后端分离技术,目前在很多大学里教的项目都是jsp+spring来做的,企业项目基本都是分离出来了,很多友友们不知道前后端怎么去对接,那么我这篇文章就是帮助小白一臂之力把这个流程给走通,如果已经了解过这篇文章也能够帮你好好复习。
这篇文章采用的技术栈是vue+springboot,主要讲解前端的请求怎么发,后端的api又是怎么样对接的。 在项目中又是如何产生交互的。
版本
-
springboot 2.7
-
vue 3.2
必备小知识
HTTP协议
HTTP协议是超文本传输协议的缩写,英文是Hyper Text Transfer Protocol。它是从WEB服务器传输超文本标记语言(HTML)到本地浏览器的传送协议,HTTP是一个基于TCP/IP通信协议来传递数据的协议,传输的数据可以为Html代码、图片、文字、文件。
这个协议就是按照一定的规则,向服务器要数据或发送数据,服务器也按照一定的规则回应数据。
请求信息
例如这是一个http请求 由这个例子来引出HTTP的请求信息与响应格式
-
请求URL: URL后面的有一串
?o=**
这就是query参数。又比如?name=tina&age=18
,query参数多个用&
符号连接,query参数出现在url地址中
-
请求方法: post方法 另外还有
get put post delete options
-
请求状态码: 200成功
状态码 | 定义 | 说明 |
---|---|---|
1** | 信息 | 接收到请求,继续处理 |
2** | 成功 | 操作成功,理解和接受 |
3** 301/2 永久/临时重定向 304未修改(缓存) | 重定向 | 为了完成请求,采取进一步措施 |
4** | 客户端错误 | 请求的语法有错误或不能完全被满足 |
5** 503服务器不可用 500内部服务器 | 服务端错误 | 服务器错误 |
304 客户端告诉服务器我缓存里面有这个资源,而且拿这个编号和修改时间与服务器来比对一下,如果没有变化就304
302 临时重定向 (默认) 如果是get的重定向没有问题 ,如果是post两次重定向 表单的数据就没有了
307 重定向中保持原有的请求数据
- Content-Length: 告诉服务器主体的长度
- Content-Type: 请求类型(根据你传入的数据类型而定)
请求类型 | 说明 | 后端 | 方法 |
---|---|---|---|
application/x-www-form-urlencoded | 如果是post请求 会把请求参数转为query(键值对)参数,例如表单提交,浏览器会默认转为query | @requestParam | get/post |
multipart/form-data | 文件上传 | @RequestParam | post |
application/json | json格式上传 | @requestbody | post |
跨域问题
后端设置可请求的地址
前端不需要代理
@Configuration
public class WebMvcConfg implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
//当**Credentials为true时,**Origin不能为星号,需为具体的ip地址
.allowedOrigins("http://localhost:3000", "http://127.0.0.1:8082")
//是否允许证书 不再默认开启
.allowCredentials(true)
//设置允许的方法
.allowedMethods("*")
//跨域允许时间
.maxAge(3600);
}
}
前端配置代理
这种方法不太采取,安全性太低,建议大家第一种,这种方法了解即可
// vite.config.js
server: {
// https: true,
port:3000,
proxy: {
'/api': { // 配置需要代理的路径 --> 这里的意思是代理http://localhost:80/api/后的所有路由
target: 'http://127.0.0.1:8080/', // 目标地址 --> 服务器地址
changeOrigin: true, // 允许跨域
ws: true, // 允许websocket代理
// 重写路径 --> 作用与vue配置pathRewrite作用相同
rewrite: (path) => path.replace(/^/api/, "")
}
},
},
axios请求实践
介绍完这些之后我们使用axios发起请求来实践一下
安装npm install axios
axios的默认请求类型为json格式
封装请求方法
//request.js
import axios from "axios"; //引入axios
import { ElMessage } from "element-plus";
const service = axios.create({
headers: {
},
baseURL: '/api', //接口访问的地址
timeout: 10000,//超时
});
//请求前拦截
service.interceptors.request.use(config => {
const token = localStorage.getItem("soul-token");
if (token) {
config.headers['Authorization'] = token;
}
return config;
}, (error) => {
return Promise.reject(error);
});
// 响应后拦截
service.interceptors.response.use(response => {
console.log(response);
let data = response.data;
if (data.code == 200) {
return response.data;
} else if (data.code == 501) {
ElMessage.error('请重新登录');
window.location.href = '/login';
} else {
ElMessage.error(response.data.message);
}
}, error => {
return Promise.reject(error);
})
export default service;
get请求
案例一
import request from '@/utils/request';
/**
* 查询所有用户
* @param {*} pageSize
* @param {*} pageNum
* @returns
*/
export function requestUsersApi(pageSize, pageNum) {
return request({
url: '/admin/users',
method: 'get',
params: {
pageSize,
pageNum
},
})
}
/**
* 查询所有用户
* @param admin
* @param pageNum
* @param pageSize
* @return
*/
@TokenCheck(roles = "admin")
@GetMapping("/users")
public BaseResponse<Page> selectAllUses(@LoginAdmin Admin admin, @RequestParam(value = "pageNum",defaultValue = "1") int pageNum , @RequestParam(value = "pageSize",defaultValue = "10") int pageSize){
Page<User> page = new Page<>(pageNum, pageSize);
return RespGenerator.returnOK(userService.selectAllUser(page));
}
案例二
不写@requestparam时,此时需要参数名称与query参数的名字与类型相同,否则就会为NUll或者报类型转换错误,那么如果前后端的参数名称不一样就可以采取@reqestparam 设置一个value值 如上图一样
@GetMapping("/get")
public BaseResponse testgetMethod(String name,int age){
return RespGenerator.returnOK("我收到数据了"+name+","+age);
}
post请求
发送post请求 默认为json格式 打开控制台,请求体与响应体的格式都为json 负荷就是传入服务器的参数 json格式 后端遇到@RequestBody解析为对象 对象的属性要与请求的json参数相同
/**
* 添加标签
* @param {Object} tag
* @returns
*/
export function requestAddTag(tag) {
return request({
url: '/admin/tag/add',
method: 'post',
data:tag
})
}
/**
* 管理员添加标签
*/
@TokenCheck(roles = "admin")
@PostMapping("/admin/tag/add")
public BaseResponse addTags(@LoginAdmin Admin admin,@RequestBody Tags tags){
log.info(tags.getTagName(),tags.getTypeId());
tagsService.insertTags(tags);
return RespGenerator.returnOK("添加成功");
}
put请求
put与post请求与服务器接收类似
/**
* 修改话题
* @param {Object} topic
* @returns
*/
export function requestUpdateTopic(topic) {
return request({
url: '/admin/topic/update',
method: 'put',
data:topic
})
}
@TokenCheck(roles = "admin")
@PutMapping("/admin/topic/update")
public BaseResponse<Object> updateTopic(@RequestBody Topic topic){
return RespGenerator.returnOK(topicService.updateById(topic));
}
delete请求
这里的删除与restful一起的 前面为路径 最后的/${id}
为参数
服务器用@PathVariable
接收参数 类型要匹配
/**
* 删除标签
* @param {*} id
* @returns
*/
export function requestDelTag(id) {
return request({
url: `/admin/tag/del/${id}`,
method: 'delete',
})
}
/**
* 删除
* @param id
* @return
*/
@DeleteMapping("/admin/topic/delete/{id}")
public BaseResponse<Object> deleteTopic(@PathVariable int id){
return RespGenerator.returnOK(topicService.removeById(id));
}
resetful
restful在delete请求的时候也介绍过,主要就是通过/参数/参数
的方法发起请求,后端@PathVariable接收,多个参数就加多个注解
图片上传 post
图片上传后端采用的是Multipart
<el-upload class="avatar-uploader" action="/api/image/upload/1" :show-file-list="false"
:on-success="onSuccess" :on-preview="handlePreview" :limit="1">
<!-- <el-image style="width: 178px;height: 178px;" fit="fill" v-if="topicForm.cover"
:src="topicForm.cover"></el-image> -->
<Image v-if="topicForm.cover" :width="178" :height="178" fit="fill" :src="topicForm.cover"></Image>
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
图片上传后端
@PostMapping("/image/upload/{type}")
public BaseResponse<String> getImage(@RequestParam("file") MultipartFile file,@PathVariable Integer type) throws IOException {
if (type==null){
throw new CommonException("请求参数错误");
}
String subPath = "";
if (type.equals(1)){
subPath = "topic";
}
// 源文件名称
final String originalFileName = file.getOriginalFilename();
// 文件后缀
final String suffix = originalFileName.substring(originalFileName.lastIndexOf(".")).toLowerCase();
if (!WebTools.IMAGE_EXTENSIONS.contains(suffix)) {
throw new CommonException("图片格式错误");
}
UUID uuid = UUID.randomUUID();
final String newFileName = uuid + suffix;
log.info("当前的路径为"+path+subPath+"/"+newFileName);
// File f = new File(path+subPath + "/" + newFileName);
// file.transferTo(f);
FileUtils.copyInputStreamToFile(file.getInputStream(), new File(path+subPath+"/"+newFileName));
return RespGenerator.returnOK(subPath+"/"+newFileName);
}
转载自:https://juejin.cn/post/7270799920826466338