项目前后端接口联调
联调准备
运行后台项目
clean 清空项目的编译文件
compile 重新编译项目
将项目部署到 Tomcat:项目名为 lagou_edu_home,端口号: 8080,使用 war 方式部署
部署图片上传路径为 Tomcat 的 webapps 目录下的 upload 目录
最后运行前端项目
首先导入前端项目到 VS Code
运行项目
课程管理首页
Courses.vue 的视图部分代码
查询 新建课程 {{ scope.row.status == "1" ? "下架" : "发布" }} 营销信息 内容管理
获取课程列表
Courses.vue JS 部分代码
export default { name: "Courses", title: "课程管理", // 定义数据部分 data() { return { filter: { course_name: "", status: "" }, // 查询对象 courses: [], // 课程信息集合 loading: false // 是否弹出加载 }; }, // 钩子函数 created() { this.loadCourses(); }, methods: { // 方法 1: 获取课程列表 loadCourses() { this.loading = true; // 请求后台查询课程列表接口 return axios .get("/course", { params: { methodName: "findCourseList" } }) .then(resp => { console.log(resp); this.loading = false; // 关闭加载 this.courses = resp.data; // 取出数据 }) .catch(error => { this.$message.error("数据获取失败! ! !"); }); }, }};
条件查询课程信息
Courses.vue JS 代码的 methods
//方法 2: 条件查询课程信息filterQuery() { this.loading = true; // 定义查询条件对象 const search = {}; // 保存用户输入的条件 if (this.filter.course_name) search.course_name = this.filter.course_name; if (this.filter.status) search.status = this.filter.status; // 请求后台条件查询接口 return axios .get("/course", { // 准备参数 params: { methodName: "findByCourseNameAndStatus", course_name: search.course_name, status: search.status } }) .then(resp => { console.log(resp); this.loading = false; // 将响应数据保存到 courses this.courses = resp.data; }) .catch(error => { this.$message.error("数据获取失败! ! !"); });},
跳转到新建课程页面
Courses.vue JS 代码的 methods
// 方法 3: 添加课程跳转方法 addCourse() { // 路由跳转到 CourseItem.vue 组件 this.$router.push({ name: "CourseItem", params: { courseId: "new" } });},
修改课程状态
Courses.vue JS 代码的 methods
// 方法 4: 修改课程状态updateStatus(item) { // item 表示选中的数据 = Course 对象 axios .get("/course", { params: { methodName: "updateCourseStatus", id: item.id } }) .then(res => { console.log(res); // 将返回的状态字段,封装到对象中 Object.assign(item, res.data); // 重新加载页面 window.location.reload; });},
跳转课程营销或内容管理
Courses.vue JS 代码的 methods
// 方法 5: 根据路由名称,导航到对应组件 handleNavigate(name, id) { this.$router.push({ name, params: { courseId: id } });},
新建 & 修改课程
Course 组件中的跳转方法
营销信息
// 方法 3: 添加课程跳转方法addCourse() { // 路由跳转到 CourseItem.vue 组件 this.$router.push({ name: "CourseItem", params: { courseId: "new" } });},
router.js 路由
找到 name 为: CourseItem 的路由
// 添加课程的路由{ path: "/courses/:courseId", // 路径,携带参数: 课程 ID name: "CourseItem", // 路由导航到的组件 component: () => import(/* webpackChunkName: 'courses' */ "../views/CourseItem.vue")},
CourseItem 组件
CourseItem.vue 的视图部分代码
this.$router.back()" :content="course.title" /> 保存
基本信息 销售信息 元 元 分享信息 点击上传 课程详情
保存
CourseItem.vue 的 JS 部分代码
data() { // 数据 return { rules, //规则 course: {}, //课程 loading: false, params: {} //参数对象 };},// 钩子函数created() { // 1.显示当前页面在网站中的位置 this.$breadcrumbs = [ { name: "Courses", text: "课程管理" }, { text: "营销信息" } ]; // 2.从路由中获取传递的参数, 课程 id const id = this.$route.params.courseId; // 3.判断 id 是否有值 if (!id) return this.redirectToError(); // 4.判断是 new 还是具体的id if (id === "new") { // new 代表新增 this.course.title = "新增课程"; } else { // 否则就是修改 this.loadCourse(id); }},
图片上传分析
页面部分
点击上传
FormData 使用演示
// 通过 FormData 构造函数创建一个空对象var formdata = new FormData();// 可以通过 append() 方法来追加数据formdata.append("name","laotie");// 通过 get 方法对值进行读取console.log(formdata.get("name")); // laotie// 通过 set 方法对值进行设置formdata.set("name","laoliu");console.log(formdata.get("name")); // laoliu
- 将 form 表单元素的 name 与 value 进行组合,实现表单数据的序列化,从而减少表单元素的拼接,提高工作效率。
- 异步上传文件
- 创建 FormData 对象
CourseItem.vue 的 JS 的 methods的 created 部分的代码
//5.创建FormData对象,将图片与表单一同上传this.params = new FormData();
methods 添加方法
// 文件上传onchange(file) { // 判断文件不为空 if (file != null) { // 将文件信息保存到 params 中 this.params.append("file", file.raw, file.name); }},
新建课程信息
CourseItem.vue 的 JS 的 methods 的代码
// 方法 1: 保存和修改课程信息handleSave() { // 检查是否拿到了正确的需要验证的 form this.$refs.form.validate(valid => { if (!valid) return false; // 1.设置 Content-Type 为多部件上传 let config = { headers: { "Content-Type": "multipart/form-data" } }; // 2.获取表单中的数据,保存到 params (params 就是 FromData对象) for (let key in this.course) { //debugger console.log(key + "---" + this.course[key]); this.params.append(key, this.course[key]); } // 3.保存课程信息 axios .post("/courseSalesInfo", this.params, config) .then(res => { //debugger if (res.data.status == 0) { // 保存成功,跳转到首页 this.$router.back(); } else if (res.data.status == 1) { this.$message({ type: "error", message: res.data.msg }); } }) .catch(err => { this.$message.error("保存失败! ! !"); }); });},
修改课程信息
CourseItem.vue 的 JS 的 methods 的代码
// 方法 2: 根据 ID 回显课程信loadCourse(id) { this.loading = true; axios .get("/course", { params: { methodName: "findCourseById", id: id } }) .then(res => { this.loading = false; this.course = res.data; }) .catch(() => { this.$message.error("回显数据失败! ! !"); });},
内容管理
Course 组件中的跳转方法
内容管理
// 方法 5: 根据路由名称, 导航到对应组件handleNavigate(name, id) { this.$router.push({ name, params: { courseId: id } });},
router.js 路由
// 内容管理的路由{ path: "/courses/:courseId/tasks", name: "CourseTasks", meta: { requireAuth: true }, component: () => import(/* webpackChunkName: 'courses' */ "../views/CourseTasks.vue")}
CourseTasks 组件
树形控件测试
- 打开之前编写 Element UI 的项目
- 在 component 目录下添加一个组件 TestTree.vue
- 在 Index.vue 组件中的导航菜单位置添加一个树形控件导航;注意要设置 index 的路径为 “/tree”
导航菜单 课程管理 树形控件
- 在 router/index.js 路由文件中进行配置,在布vue局路由中再添加一个子路由
// 导入树形控件组件import Tree from "@/components/TestTree.vue"... { path: "/index", name: "index", component: Index, children: [ { path: "/course", name: "course", component: Course }, // Tree控件测试路由 { path: "/tree", name: "tree", component: Tree } ] }...
- 在 ElementUI 官网中查找树形控件
- 先来查看基础用法,赋值代码到 TestTree.vue
- Tree 组件属性分析
data:展示数据
props:配置树形结构;label - 设置节点名称,children - 指定生成子树的属性名称
- 自定义树节点内容:data - 数据对象,node - 节点对象
{{ data.label }} 级别:{{node.level}}
- 展示树形结构章节与课时
{{ data.section_name || data.theme }} 级别:{{node.level}}
显示当前课程的名称
this.$router.back()" :content="addSectionForm.course_name" />
data 数据
data() { // 定义章节信息 const addSectionForm = { course_id: undefined, course_name: "", section_name: "", description: "", order_num: 0 }; // 章节与课时信息,树形结构 const treeProps = { label: item => { return item.section_name || item.theme; }, children: "lessonList" }; // 定义章节状态信息 const statusMapping = { 0: "已隐藏", 1: "待更新", 2: "已更新" }; const statusForm = { status: 0 }; return { addSectionForm, treeProps, sections: [], statusForm, // 状态表单 statusMapping, loading: false, // 树形控件 showAddSection: false, // 添加或修改章节 showStatusForm: false // 状态修改 };},
加载课程信息
created() { // 1.显示当前页面在网站中的位置 this.$breadcrumbs = [ { name: "Courses", text: "课程管理" }, { text: "课程结构" } ]; // 2.从路由中获取传递的参数, 课程 id const id = this.$route.params.courseId; if (!id) return this.redirectToError(); // 3.加载课程信息 this.loadCourse(id); // 4.加载课程对应的章节与课时 this.loadChildren(id);},methods: { // 方法 1: 加载课程信息 loadCourse(id) { axios .get("/courseContent", { params: { methodName: "findCourseById", course_id: id } }) .then(res => { // 将数据保存到表单对象中 this.addSectionForm.course_id = res.data.id; this.addSectionForm.course_name = res.data.course_name; }) .catch(error => { this.loading = false; this.$message.error("数据获取失败! ! !"); }); },},
加载章节与课时信息
// 方法 2: 加载树 (章节与课程)loadChildren(id) { this.loading = true; axios .get("/courseContent", { params: { methodName: "findSectionAndLessonByCourseId", course_id: id } }) .then(resp => { // 获取章节数据, 保存到 sections this.sections = resp.data; this.loading = false; }) .catch(error => { this.loading = false; this.$message.error("数据获取失败! ! !"); });},
Element UI 树形控件 el-tree::data 列表数据;:props 配置选项。
{{ data.section_name || data.theme }} ...
...
:props 配置选项:label - 指定节点标签为节点对象的某个属性值;children - 指定子树为节点对象的某个属性值。
// 章节与课时信息, 树形结构const treeProps = { label: item => { return item.section_name || item.theme; }, children: "lessonList"};
操作按钮显示:node.level 获取当前节点的级别;@click.stop 事件冒泡,点击哪个元素就执行哪个元素绑定的事件
编辑 {{ statusMapping[data.status] }}
回显信息
添加章节
JS 代码
// 方法 3: 显示添加章节表单, 回显课程信息handleShowAddSection() { // 显示表单 this.showAddSection = true;},
添加章节
确 定
JS 代码
// 方法 4: 添加&修改章节操作handleAddSection() { axios .post("/courseContent", { methodName: "saveOrUpdateSection", section: this.addSectionForm }) .then(resp => { this.showAddSection = false; // 重新加载列表 return this.loadChildren(this.addSectionForm.course_id); }) .catch(error => { this.loading = false; this.$message.error("操作执行失败! ! !"); });},
后台接口问题解决
BaseServlet 中代码修改
if("application/json;charset=utf-8".equals(contentType)) { ...} // 修改为:if("application/json;charset=utf-8".equalsIgnoreCase(contentType)) { ...}
CourseContentServlet 中的 saveOrUpdateSection 方法修改
// 使用 BeanUtils 的 copyProperties 方法将 map 中的数据封装到 section 对象里BeanUtils.copyProperties(section,map.get("section"));
修改章节
编辑
JS 回显示方法
// 方法 5: 修改章节回显方法handleEditSection(section) { // 对象拷贝 Object.assign(this.addSectionForm, section); this.showAddSection = true;},
事件冒泡:当点击子元素的事件。如果父元素也有同样的事件的话。他就会一并的触发。
解决冒泡事件的方法:@click.stop
事件冒泡
章节状态回显
{{ statusMapping[data.status] }}
JS 代码
// data 函数中定义的章节状态const statusMapping = { 0: "已隐藏", 1: "待更新", 2: "已更新"};
// 方法 6: 显示章节状态showStatus(data) { this.statusForm.id = data.id; this.statusForm.status = data.status.toString(); this.statusForm.data = data; this.showStatusForm = true;// 显示状态表单},
Select 选择器
- 打开之前编写 Element UI 的项目
- 在 component 目录下添加一个组件 TestSelect.vue
- 在 Index.vue 组件中的导航菜单位置添加一个 Select 选择器导航;注意要设置 index 的路径为 /select
Select选择器
- 在 index.js 路由文件中进行配置,在布局路由中再添加一个子路由
import Select from "@/components/TestSelect.vue"{ path: "/select", name: "select", component: Select,},
- 在 Element UI 官网中查找 Select 选择器
- 查看基础用法,将代码复制到 TestSelect.vue 中
- select 选择器属性分析
v-model 的值为当前被选中的 el-option 的 value 属性值
el-option 选项:label 选项的标签名;value 选择的值
- 使用 Select 选择器展示状态信息
Object.keys()
var obj = { 0: 'a', 1: 'b', 2: 'c' };console.log(Object.keys(obj)); // console: ['0', '1', '2']
章节状态修改
确 定
JS 部分
//方法7: 修改章节状态updateStatus(statusForm) { axios .get("/courseContent", { params: { methodName: "updateSectionStatus", status: this.statusForm.status, id: this.statusForm.id } }) .then(resp => { this.statusForm.data.status = this.statusForm.status; this.statusForm = {}; this.showStatusForm = false; }) .catch(error => { this.showStatusForm = false; this.$message.error("修改状态失败! ! !"); });},
v-for 里面数据层次太多, 修改过数据变了,页面没有重新渲染,需手动强制刷新。
项目上线部署发布
简介
服务器与操作系统
服务器是计算机的一种,它比普通计算机运行更快、负载更高、价格更贵。
服务器从硬件上等同于电脑 PC。而服务器跟 PC 都是由 CPU、内存、主板、硬盘、电源等组成;但服务器的性能要远远超过 PC,因为它要保证全年无休。
操作系统是作为应用程序与计算机硬件之间的一个接口。
没有安装操作系统的计算机,被称为裸机, 如果想在裸机上运行自己的程序,就需要使用机器语言。
安装操作系统之后,就可以配置一些高级语言的环境,进行高级语言的开发。
Linux 系统是最具稳定性的系统。
Linux 比 Windows 更具安全性。
Linux 服务器在应用开发上更能节约成本。
项目的发布部署
项目的开发流程大致要经过一下几个步骤:
- 项目立项
- 需求分析阶段
- 原型图设计阶段
- 开发阶段
- 测试阶段
- 系统上线
后台项目部署
安装虚拟机
在 Linux 阶段已经安装过了虚拟机,使用的是 Linux 操作系统 CentOS 7 版本。
安装软件环境
以下软件在 Linux 阶段都已安装完成:
- JDK 11
- Tomcat 8.5
- MySQL 5.7
查看 Java 版本
java -version
查看 tomcat 是否能够正常启动
# 进入到 tomcat 目录cd /usr/tomcat/# 启动 tomcat./bin/startup.sh # 关闭 tomcat./bin/shutdown.sh
关闭防火墙
#查看已经开放的端口:firewall-cmd --list-ports#开启端口 firewall-cmd --zone=public --add-port=8080/tcp --permanent#命令含义: –zone #作用域 –add-port=8080/tcp #添加端口,格式为:端口/通讯协议 –permanent #永久生效,没有此参数重启后失效#重启防火墙firewall-cmd --reload# 关闭防火墙# 停止 firewallsystemctl stop firewalld.service# 禁止 firewall 开机启动systemctl disable firewalld.service# 查看默认防火墙状态(关闭后显示 notrunning,开启后显示 running)firewall-cmd --state
登录 MySQL 检查数库连接是否正常
-- 创建用户CREATE USER 'JiuYuan'@'%' IDENTIFIED BY 'JiuYuan@123';-- 授予所有权限GRANT ALL PRIVILEGES ON *.* TO 'JiuYuan'@'%' IDENTIFIED BY 'JiuYuan@123';-- 刷新FLUSH PRIVILEGES;
使用 SQLYog 连接 Linux 上的 MySQL,导入 SQL 脚本创建项目所需的数据库
项目打包发布
- 修改项目的数据库配置文件:数据库的 IP、用户名、密码。
- 修改 Constants 常量类中的项目 URL
// 生产环境地址public static final String LOCAL_URL = "http://192.168.91.128:8080";
- 修改后启动项目,测试一下 保证数据库连接没有问题
- 检查 POM 文件打包方式必须是 war 编译版本为 JDK 11
war UTF-8 11 11
- 执行打包命令
// 清除 target 文件夹clean // 打包跳过测试package
- 复制出 target 目录下的 war 包
- 修改一下 war 包名称为 lagou_edu_home.war
- 上传到 tomcat 的 webapps 目录中启动测试,访问接口:
http://192.168.91.128:8080/lagou_edu_home/course?methodName=findCourseList
前端项目部署
修改配置文件
前端项目的配置文件有两个:一个是开发环境的配置文件,一个是生产环境的配置文件。
先修改一下开发环境文件 .env.development 的后端服务器访问地址,然后进行一下测试:
VUE_APP_API_BASE = http://192.168.91.128:8080/lagou_edu_home
修改生产环境的配置文件 .env.production:
VUE_APP_API_BASE = http://192.168.91.128:8080/lagou_edu_home
前端项目打包
修改 vue.config.js 配置文件
module.exports = { // relative path for dev publicPath: process.env.NODE_ENV === "production" ? "/edu-boss/" : "./", // for gh-pages indexPath: "index.html", assetsDir: "static", lintOnSave: process.env.NODE_ENV !== "production", productionSourceMap: false, css: { // sourceMap: process.env.NODE_ENV !== 'production' }, devServer: { open: true, port: 8081 }};
执行下面的打包命令:
npm run build
在项目下会生成一个 dist 目录
在本地 Tomcat 的 webapps 目录下创建一个 edu-boss 文件夹将 dist 目录中的文件拷贝到里面
启动本地 Tomcat 访问前端项目路径为
http://localhost:8081/edu-boss/
前端项目发布
- 验证没有问题后将 edu-boss 项目压缩上传到 Tomcat 服务器
# 复制一份 Tomcatcp -r /usr/tomcat/ /usr/tomcat2# 上传 edu-boss.zip 并解压unzip edu-boss.zip # 删除 edu-boss.ziprm -rf edu-boss.zip
- 修改 tomcat2 的 server.xml 配置文件的 3 个端口避免与 tomcat 冲突,修改结果如下:
... ... ... ...
- 在部署后端项目的 tomcat1 的 webapps 目录下创建一个 upload 文件夹保存图片
- 运行前端项目:
# 进入 tomcat2,启动项目./bin/startup.sh# 动态查看日志tail -f logs/catalina.out
- 运行后端项目
# 进入 tomcat1,启动项目./bin/startup.sh # 动态查看日志tail -f logs/catalina.out
- 前后端都启动后进行测试