项目地址:课程管理模块
功能实现中用到的知识及技巧
1. 项目部署war和war exploded的区别
IDEA中部署项目两种方式
-
war模式:将项目以war包的形式上传到服务器的webapps目录中;
- 可以称之为发布模式:先打成war包,再发布
-
war exploded模式:将WEB工程以当前文件夹的位置关系上传到服务器,仅仅是目录的映射,就相当于tomcat在项目源文件夹中启动一样;
- 热部署:一般在开发的时候用这种方式
2. 访问图片 upload路径
2.1 在webapps中创建upload目录
upload目录专门用来保存上传过来的图片
2.2 修改代码,将图片上传到服务器
修改图片的输出路径
-
获取到项目的运行目录信息
-
截取到webapps的 目录路径
-
拼接输出路径,将图片保存到upload
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { //1.创建磁盘文件项工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); //2.创建文件上传核心类 ServletFileUpload upload = new ServletFileUpload(factory); //2.1 设置上传文件名的编码 upload.setHeaderEncoding("utf-8"); //2.2 判断表单是否是文件上传表单 boolean multipartContent = upload.isMultipartContent(req); //2.3 是文件上传表单 if(multipartContent){ //3. 解析request ,获取文件项集合 List<FileItem> list = upload.parseRequest(req); if(list != null){ //4.遍历获取表单项 for (FileItem item : list) { //5. 判断是不是一个普通表单项 boolean formField = item.isFormField(); if(formField){ //普通表单项, 当 enctype="multipart/form-data"时, request的getParameter()方法 无法获取参数 String fieldName = item.getFieldName(); String value = item.getString("utf-8");//设置编码 System.out.println(fieldName + "=" + value); }else{ //文件上传项 //文件名 String fileName = item.getName(); //避免图片名重复 拼接UUID String newFileName = UUIDUtils.getUUID()+"_"+ fileName; //获取上传文件的内容 InputStream in = item.getInputStream(); // 动态获取项目的运行目录 String path = this.getServletContext().getRealPath("/"); // 截取到 webapps路径 String webappsPath = path.substring(0, path.indexOf("lagou_edu_home")); // 拼接输出路径 OutputStream out = new FileOutputStream(webappsPath+"/upload/"+newFileName); //拷贝文件到服务器 IOUtils.copy(in,out); out.close(); in.close(); } } } } } catch (FileUploadException e) { e.printStackTrace(); } }
2.3 页面加载图片
将tomcat作为图片服务器使用时,存储上传的图片后,如果想要图片可以访问,需要在idea中进行配置:
选择external source —> 找到webapps目录下的的upload文件夹
在项目内部页面加载图片
<img src="/upload/文件名.jpg">
也可以通过HTTP方式访问
http://localhost:8080/upload/文件名.jpg
3. 跨域问题解决
3.1 出现跨域问题
当我们在前端项目中,向后端发送请求的获取课程数据的时候,出现了跨域问题:
-
已被CORS策略阻止:请求的资源上没有
Access-Control-Allow-Origin
标头(跨域请求失败)Access to XMLHttpRequest at 'http://localhost:8080/lagou_edu_home/course? methodName=findCourseList' from origin 'http://localhost:8088' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
3.2 什么是跨域
跨域是指通过JS在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,只要协议、域名、端口有任何一个不同,都被当作是不同的域,浏览器就不允许跨域请求。
-
跨域常见情况
3.3 解决跨域问题
跨域的允许主要由服务器端控制。服务器端通过在响应的 header 中设置 Access-Control-Allow-Origin
及相关一系列参数,提供跨域访问的允许策略。
设置响应头中的参数来允许跨域请求,标识允许跨域的请求有哪些
- Access-Control-Allow-Credentials
- Access-Control-Allow-Origin
引入依赖直接解决
-
在POM文件中引入依赖
<!-- 解决跨域问题所需依赖 --> <dependency> <groupId>com.thetransactioncompany</groupId> <artifactId>cors-filter</artifactId> <version>2.5</version> </dependency>
-
在web.xml中 配置跨域 filter
<!--配置跨域过滤器--> <filter> <filter-name>corsFilter</filter-name> <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class> </filter> <filter-mapping> <filter-name>corsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
4. 路由跳转
4.1 什么是路由?
在Web开发中,路由是指根据URL分配到对应的处理程序。 路由允许我们通过不同的 URL 访问不同的 内容。
通过 Vue.js 可以实现多视图单页面web应用(single page web application,SPA)
- 单页面Web应用(single page web application,SPA),就是只有一张Web页面的应用, 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。
官方文档: https://router.vuejs.org/zh-cn/essentials/getting-started.html
观看 Vue School 的关于 Vue Router 的免费视频课程 (英文)
4.2 使用步骤(以login为例)
- 定义路由所需的组件
- 定义路由
- path (路径)
- component (组件)
- 创建router路由器实例,管理路由
- 创建Vue实例,注入路由对象,使用$mount() 指定挂载点
4.2.1 创建login.vue 组件
在components 下创建login.vue
根据上述代码自行修改,完整版在下方
4.2.2 配置路由
import Vue from "vue";
import VueRouter from "vue-router";
//导入Login.vue组件
import Login from "@/components/Login";
Vue.use(VueRouter);
const routes = [
//访问 / .也调转到 login
{
path: "/",
redirect: "login", //重定向到login
},
//登录路由
{
path: "/login",
name: "login",
component: Login,
},
];
// 解决ElementUI导航栏中的vue-router在3.0版本以上重复点菜单报错问题
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
};
const router = new VueRouter({
routes,
});
export default router;
4.2.3 修改App.vue
<template>
<div id="app">
<!-- 根据访问路径,渲染路径匹配到的组件 -->
<router-view></router-view>
</div>
</template>
<style></style>
4.2.4 编写登录功能
<template>
<el-dialog
:show-close="false"
title="用户登录"
:visible.sync="dialogFormVisible"
>
<el-form>
<!-- 2.2使用 v-model, 将视图与模型进行绑定 -->
<el-form-item label="用户名称" :label-width="formLabelWidth">
<el-input v-model="user.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户密码" :label-width="formLabelWidth">
<el-input v-model="user.password" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<!-- 1.修改登陆触发事件 -->
<el-button type="primary" @click="login">登 录</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
//2.1 data 中定义数据
data() {
return {
dialogFormVisible: true,
formLabelWidth: "120px",
user: { username: "", password: "" },
};
},
methods: {
//3.编写login方法
login() {
//定义常量保存url
//Postman搭建mock server 模拟一个服务器,模拟后台接口,对请求进行响应
const url = "https://bdd7f56b-a299-4340-9773-8d3c7c24a6a8.mock.pstmn.io/login";
//发送请求
this.axios(url, {
//携带的参数
params: {
username: this.user.username,
password: this.user.password,
},
})
.then((resp) => {
console.log(resp);
alert("登录成功");
//成功 关闭对话框
this.dialogFormVisible = false;
//进行页面跳转,跳转到首页,在前端进行页面跳转 必须使用路由.
this.$router.push('index');
})
.catch((error) => {
//登录失败 提供消息提示
this.$message.error("对不起!登录失败!");
});
},
},
};
</script>
<style scoped></style>
4.3 路由总结
- router是Vue中的路由管理器对象,用来管理路由
- route是路由对象,一个路由就对应了一条访问路径,一组路由用routes表示
- 每个路由对象都有两部分 path(路径)和component (组件)
- router-link 是对a标签的封装,通过to属性指定连接
- router-view 路由访问到指定组件后,进行页面展示
5. 前端输入数据保存不成功
5.1 BaseServlet
if("application/json;charset=utf-8".equals(contentType))
修改为:
if ("application/json;charset=utf-8".equalsIgnoreCase(contentType)) // utf-8 || UTF-8(前端传回格式)
5.2 CourseContentServlet
前端发送数据格式
handleAddSection() {
axios
.post("/courseContent", {
methodName: "saveOrUpdateSection",
section: this.addSectionForm
})
后端接收格式
// BeanUtils.populate(section, map);
BeanUtils.copyProperties(section, map.get("section"));
6. @click.stop 阻止事件冒泡
6.1 表现形式
点击按钮后,先弹出 button点击事件
后弹出 div点击事件
,但只需弹出 button点击事件
<body>
<!-- 事件冒泡: 解决方案 @click.stop -->
<div id="app" @click="log('div点击事件')">
<button @click="log('button点击事件')">事件冒泡</button>
</div>
</body>
<script src="./js/vue.min.js"></script>
<script>
var VM = new Vue({
el: "#app",
methods: {
log(t) {
alert(t);
},
},
});
</script>
6.2 解决方法
<button @click="log('button点击事件')">事件冒泡</button>
修改为:
<button @click.stop="log('button点击事件')">事件冒泡</button>