600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > SSM-下(拉勾教育后台管理系统-前端)

SSM-下(拉勾教育后台管理系统-前端)

时间:2024-05-22 18:11:12

相关推荐

SSM-下(拉勾教育后台管理系统-前端)

第六阶段模块五

任务一 课程和广告模块前端开发

1.Vue回顾

1.1 项目结构说明

们使用脚手架快速构建Vue项目

|--- edu-boss 项目名称|--- node_modules 存放依赖包的目录|--- public 静态资源管理目录|--- src 组件源码目录(我们写的代码)|--- assets 存放静态图片资源(CSS也可以放在这里)|--- components 存放基础组件,可复用|--- router 存放了项目路由文件|--- services 存放请求后台的 JS文件,|--- store 保存组件之间的共享数据|--- utils 管理公用的JS文件|--- views 放置的为公共组件(各个主要页面)|--- App.vue app.vue可以当做是网站首页,是一个vue项目的主组件,页面入口文件|--- main.js 打包运行的入口文件,引入了vue模块和app.vue组件以及路由route|--- babel.config.js babel配置文件, 对源代码进行转码(把es6=>es5)|--- package.json 项目及工具的依赖配置文件|--- paxkage-lock.json 依赖配置文件|--- README.md 项目说明|--- vue.config.js 自定义配置文件

1.2 Views 目录说明

前端项目的页面部分

CourseManage: 课程管理AdvertiseManage: 广告管理PermissionManage: 权限管理CommentManage:公共Users.vue: 用户管理Login.vue: 登录

1.3 vue组件化开发

每一个***.vue 文件都可以看做是一个组件**

组件的组成部分:

template: 组件的HTML部分

script: 组件的JS脚本 (使用ES6语法编写)

style: 组件的CSS样式

<!-- 1.template 代表html结构, template中的内容必须有且只有一个根元素编写页面静态部分就是view部分 --><template><div>测试页面...</div></template><!-- 2.编写vue.js代码 --><script>//可以导入其组件// import Header from '../components/header.vue'//默认写法, 输出该组件export default {name:"Home", // 组件名称,用于以后路由跳转data() {// 当前组件中需要使用的数据return {}},methods: {}}</script><!-- 编写当前组件的样式代码 --><style scoped>/* 页面样式 加上scoped 表示样式就只在当前组件有效*/</style>

2.课程模块回顾

2.1 课程数据展示

在开始编写前端代码之前,导入最新的数据库脚本文件

2.1.1 功能分析

Course.vue 组件,完成课程数据的展示和条件查询

使用ElementUI 表格进行数据展示 /#/zh-CN/component/table

2.1.2 JS代码编写

1.定义数据部分

// 数据部分data() {// 查询条件const filter = {courseName: "",status: ""};return {filter,courses: [], // 课程数据loading: false};},// 钩子函数created() {this.loadCourses();},

2.根据接口文档,编写查询课程数据方法

// 获取课程数据loadCourses() {this.loading = true;const data = {};if (this.filter.courseName) data.courseName = this.filter.courseName;if (this.filter.status) data.status = this.filter.status;// 发送请求return axios.post("/course/findAllCourse", data).then(resp => {this.courses = resp.data.content;this.loading = false;}).catch(error => {this.$message.error("数据获取失败! ! !");});},

3.条件查询( 访问的是同一个接口 )

<el-button @click="handleFilter()">查询</el-button>

// 条件查询handleFilter() {this.loadCourses();},

2.2 新建课程

2.2.1 功能分析

点击新建,由路由导航到 CourseItem.vue

<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建课程</elbutton>

//新建课程 路由跳转handleAdd() {//路由的传参 new表示新建this.$router.push({name: "CourseItem", params: {courseId: "new" } });},

router.js

{path: "/courses/:courseId",name: "CourseItem",meta: {requireAuth: true, title: "课程详情" },component: () =>import(/* webpackChunkName: 'courses' */ "../views/CourseManage/CourseItem.vue")},

CourseItem组件使用ElementUI中的表单来提交课程数据 /#/zh-CN/component/form

2.2.2 JS代码编写

<el-button type="primary" @click="handleSave">保存</el-button>

//保存修改课程信息handleSave() {//校验是否符合规则this.$refs.form.validate(valid => {if (!valid) return false;axios.post("/course/saveOrUpdateCourse", this.course).then(res => {//退回到上个页面this.$router.back();}).catch(error => {this.$message.error("保存课程信息失败! ! !");});});},

2.3 课程图片上传

2.3.1 功能分析

在SSM前端项目中,图片上传功能使用的是公共的通用组件 UploadImage.vue

在CourseItem.vue,引入了该组件

import UploadImage from "@/components/UploadImage.vue";

<el-form-item label="课程封面" prop="courseImgUrl"><!-- 使用图片上传组件,完成图片上传 --><upload-image:content="course.courseImgUrl && [course.courseImgUrl]":get-urls="getCourseImgUrl"uploadUrl="/course/courseUpload"ref="courseCoverRef"max="10M"tipInfo="建议尺寸:230*300px,JPG、PNG格式,图片小于10M"></upload-image></el-form-item>

2.3.2 案例演示

为了更好的理解 图片上传组件的使用,我们创建一个Vue项目来演示图片上传组件的使用方式

导入准备好的Vue 基础项目 my-ssmweb

在components目录下创建一个 UploadImage.vue组件

查看ElementUI文档,复制代码到 UploadImage.vue /#/zh-CN/component/upload

<template><div><el-uploadaction="/posts/"list-type="picture-card":on-preview="handlePictureCardPreview":on-remove="handleRemove"><i class="el-icon-plus"></i></el-upload><el-dialog :visible.sync="dialogVisible"><img width="100%" :src="dialogImageUrl" alt /></el-dialog></div></template><script>export default {data() {return {dialogImageUrl: "",dialogVisible: false};},methods: {handleRemove(file, fileList) {console.log(file, fileList);},handlePictureCardPreview(file) {this.dialogImageUrl = file.url;this.dialogVisible = true;}}};</script><style scoped></style>

配置路由

//布局路由{path: "/index",name: "index",component: Index,//添加子路由,使用 children属性 来表示子路由children: [//图片上传子路由{path: "/upload",name: "upload",component: UploadImage,},],},

在Index.vue 导航菜单位置添加一个图片上传选项

<el-menu-item-group><!-- 修改 index的路由地址 --><el-menu-item index="/upload"><i class="el-icon-menu"></i>图片上传</el-menu-item></el-menu-item-group>

访问页面进行测试

2.3.3 属性说明
2.3.4 组件的引入

怎么将一个组件引入另一个组件? 接下来演示一下 引入图片组件

创建一个TestUplopad.vue组件

<template><div><!-- 使用组件,注意使用短横线连接 --><upload-image></upload-image></div></template><script>//1.导入组件import UploadImage from "@/components/UploadImage";export default {//2.注册组件components: {UploadImage}};</script><style scoped></style>

2.3.5 组件的传参

UploadImage.vue

/*组件传参uploadUrl:图片上传路径,getUrl: 函数*/props: ["uploadUrl", "getUrl"],data() {return {uploadAction: this.uploadUrl};},//上传成功后的回调函数uploadSuccess(res, file) {this.getUrl(file);}

<el-upload:action="uploadAction"list-type="picture-card":on-preview="handlePictureCardPreview":on-remove="handleRemove":on-success="uploadSuccess">

TestUpload.vue

<template><div><!-- 使用组件,注意使用短横线连接 ,向父组件传递了两个参数uploadUrl: 图片上传地址:get-url:传递了一个函数--><upload-imageuploadUrl="/posts/":get-url="show"></upload-image></div></template>

methods: {show(file) {console.log(file.name);}}

2.3.6 课程模块图片上传

CourseItem.vue

引入图片上传组件,并使用

<el-form-item label="课程封面" prop="courseImgUrl"><!-- 使用图片上传组件,完成图片上传 --><upload-image:content="course.courseImgUrl && [course.courseImgUrl]":get-urls="getCourseImgUrl"uploadUrl="/course/courseUpload"ref="courseCoverRef"max="10M"tipInfo="建议尺寸:230*300px,JPG、PNG格式,图片小于10M"></upload-image></el-form-item>

import UploadImage from "@/components/UploadImage.vue";export default {name: "CourseItem",title: "营销信息",components: {Editor, UploadImage },}

2.4 修改课程

点击编辑携带当前数据的id,导航到CourseItem.vue

<el-button size="mini" @click="handleNavigate('CourseItem', scope.row.id)">编辑</el-button>

//课程编辑&内容管理路由handleNavigate(name, id) {this.$router.push({name, params: {courseId: id } });},

在CourseItem组件的钩子函数中,会进行判断,如果是修改会先获取对应课程数据,进行回显

//钩子函数created() {//获取课程idconst id = this.$route.params.courseId;if (!id) return this.redirectToError();//判断是新建还是修改if (id === "new") {this.pathTitle = "新增课程";this.$breadcrumbs = [{name: "Courses", text: "课程管理" },{text: "新增课程" }];} else {this.$breadcrumbs = [{name: "Courses", text: "课程管理" },{text: "营销信息" }];this.loadCourse(id);}},

//回显课程信息loadCourse(id) {this.loading = true;return axios.get("/course/findCourseById?id=" + id).then(resp => {console.log(resp);this.pathTitle = resp.data.content.courseName;this.course = Object.assign(this.course, resp.data.content);this.course.id = id;this.loading = false;}).catch(error => {this.$message.error("回显数据失败! !");});},

修改课程与添加课程走的都是同一个后台接口,区别是修改操作必须要携带ID

2.5 课程状态管理

点击上架或者下架完成课程状态的切换

<el-button size="mini" type="danger" v-if="scope.row.status === 1" @click="handleToggleStatus(scope.row)">下架</el-button><el-button size="mini" type="success" v-else-if="scope.row.status === 0"@click="handleToggleStatus(scope.row)">上架</el-button>

//切换课程状态handleToggleStatus(item) {//设置最新状态const toggledStatus = 1 - item.status;//请求后台接口axios.get("/course/updateCourseStatus", {params: {status: toggledStatus,id: item.id}}).then(res => {debugger;//设置最新的值item.status = toggledStatus;console.log(item);//重新加载页面window.location.reload;}).catch(error => {this.$message.error("状态修改失败! ! !");});},

2.6 课程内容管理

2.6.1 获取课程内容数据

课程内容数据包括章节与课时信息,根据课程ID 查询课程包含的章节与课时信息

<el-button size="mini" @click="handleNavigate('CourseSections', scope.row.id)">内容管理</el-button>

created() {//1.显示当前页面在网站中的位置this.$breadcrumbs = [{name: "Courses", text: "课程管理" },{text: "课程结构" }];//2.从路由中获取传递的参数 课程idconst id = this.$route.params.courseId;if (!id) return this.redirectToError();this.loading = true;//3.加载课程信息this.loadCourse(id);//4.加载课程内容this.loadSections(id);},

//加载课程信息loadCourse(id) {axios.get("/courseContent/findCourseByCourseId?courseId=" + id).then(res => {const course = res.data.content;//将数据保存到章节表单对象中this.addSectionForm.courseId = course.id;this.addSectionForm.courseName = course.courseName;//将数据保存到课时表单对象中this.addLessonForm.courseId = course.id;this.addLessonForm.courseName = course.courseName;}).catch(error => {this.$message.error("数据获取失败! ! !");});},

//加载课程内容(树形结构)loadSections(courseId) {this.loading = true;axios.get("/courseContent/findSectionAndLesson?courseId=" + courseId).then(res => {this.sections = res.data.content;console.log(res.data.content);this.loading = false;}).catch(error => {this.$message.error("数据获取失败! ! !");});},

2.6.2 章节管理

新建章节

<el-button type="primary" icon="el-icon-plus" @click="handleShowAddSection">添加章节</el-button>

新增章节,需要回显章节对应的课程名称

//显示新增章节表单handleShowAddSection() {this.addSectionForm = {courseId: this.addSectionForm.courseId,courseName: this.addSectionForm.courseName};this.showAddSection = true;},

修改章节

<el-button size="small" @click.stop="handleEditSection(data)">编辑</el-button>

//编辑章节(回显)handleEditSection(section) {this.addSectionForm = Object.assign(this.addSectionForm, section);this.showAddSection = true;},

添加与修改章节访问的都是同一个接口

//添加&修改章节handleAddSection() {axios.post("/courseContent/saveOrUpdateSection", this.addSectionForm).then(res => {this.showAddSection = false;//重新加载列表return this.loadSections(this.addSectionForm.courseId);}).then(() => {//重置表单内容this.addSectionForm.sectionName = "";this.addSectionForm.description = "";this.addSectionForm.orderNum = 0;this.reload();}).catch(error => {this.showAddSection = false;this.$message.error("操作执行失败! ! !");});},

章节状态

章节状态有3种

//状态信息const statusMapping = {0: "已隐藏",1: "待更新",2: "已更新"};

选择状态,点击确定修改状态

<el-button type="primary" @click="handleToggleStatus">确 定</el-button>

//修改章节状态handleToggleStatus() {//判断要修改的状态if (this.toggleStatusForm.data.sectionName) {//修改章节状态axios.get("/courseContent/updateSectionStatus", {params: {id: this.toggleStatusForm.id,status: this.toggleStatusForm.status}}).then(resp => {this.toggleStatusForm.data.status = this.toggleStatusForm.status;this.toggleStatusForm = {};this.showStatusForm = false;this.reload();}).catch(error => {this.showStatusForm = false;this.$message.error("修改状态失败! ! !");});} else {//修改课时状态}},

2.6.3 课时管理

课时管理包括:课时新增、课时修改、课时状态管理。与章节管理基本相同

3.广告模块

3.1 广告位管理

3.1 广告位展示

AdvertiseSpaces.vue 组件,为广告位页面

JS部分

data() {return {list: null,listLoading: false};},created() {//加载广告位数据this.loadPromotionSpace();},

//方法1: 加载广告位信息loadPromotionSpace() {this.listLoading = true;// 请求后台接口 获取广告位列表数据axios.get("/PromotionSpace/findAllPromotionSpace").then(res => {this.list = res.data.content;this.listLoading = false;}).catch(error => {this.$message("加载数据失败!!!");});},

3.2 添加广告位

1)点击添加按钮,通过路由导航到指定组件

<el-button size="mini" class="btn-add" @click="handleAdd()">添加广告位</el-button>

//添加广告位跳转handleAdd() {this.$router.push({path: "/addAdvertiseSpace" });},

2) 查看路由 router.js,跳转到的是 AddAdvertiseSpace.vue

{path: "addAdvertiseSpace",name: "AddAdvertiseSpace",component: () => import("../views/AdvertiseManage/AddAdvertiseSpace"),meta: {requireAuth: true, title: "添加广告位" }},

3) 查看AddAdvertiseSpace.vue

<template><home-advertise-detail :isEdit="false"></home-advertise-detail></template><script>import HomeAdvertiseDetail from "./AdvertiseSpaceDetail";export default {name: "addHomeAdvertise",title: "添加广告位",components: {HomeAdvertiseDetail }};</script><style></style>

4)真正显示的组件是 AdvertiseSpaceDetail.vue

​ 首先判断要进行 新增还是修改操作,根据isEdit,true 为修改,false为新增

//钩子函数created() {// 判断时添加还是修改操作if(this.isEdit){// 修改const id = this.$route.query.id;this.loadPromotionSpace(id);}else{// 新增this.homeAdvertise = {};}},

新增

//方法1: 保存广告位信息handleSave() {this.$refs.form.validate(valid => {if(!valid) return false;// 请求后台axios.post("/PromotionSpace/saveOrUpdatePromotionSpace",this.homeAdvertise).then(res => {// 返回上个页面this.$router.back();}).catch(error =>{this.$message("数据处理失败!!");});});},

3.3 修改广告位

需要请求后台接口,进行广告位信息回显

//方法2: 回显广告位信息loadPromotionSpace(id) {return axios.get("/PromotionSpace/findPromotionSpaceById?id=" + id).then(res => {Object.assign(this.homeAdvertise,res.data.content);this.homeAdvertise.id = id;}).catch(error =>{this.$message("数据处理失败!!");});}

3.2 广告管理

3.2.1 ElementUI 分页组件

Advertises.vue 组件,为广告列表页面

广告列表的展示,使用到了分页组件,接下来通过一个案例演示一下分页插件的使用

/#/zh-CN/component/pagination

1. 快速使用
在测试项目中,创建一个PageList.vue,复制代码如下

<template><div><div class="block"><span class="demonstration">完整功能</span><!-- current-change 当前页page-sizes="[100, 200, 300, 400]" 每页显示条数选项page-size 每页显示条数total 总条数--><el-pagination@size-change="handleSizeChange"@current-change="handleCurrentChange":current-page="currentPage4":page-sizes="[10, 20, 30, 40]":page-size="100"layout="total, sizes, prev, pager, next, jumper":total="40"></el-pagination></div></div></template><script>export default {methods: {// 显示条数变化时触发handleSizeChange(val) {console.log(`每页 ${val} 条`);},// 当前页发生变化时触发handleCurrentChange(val) {console.log(`当前页: ${val}`);}},data() {return {currentPage4: 4};}}</script>

2.属性介绍

分析:

​ page-size 与 current-page 是需要前端传给后端的数据

​ total 和 列表数据 是需要后端返回给前端的.

3.事件介绍
4.案例演示
复制下面代码到 PageList

<template><div class="app-container"><div class="table-container"><el-tableref="homeAdvertiseTable":data="list"style="width: 100%;"v-loading="listLoading"border><el-table-column label="id" width="120" align="center"><template slot-scope="scope">{{scope.row.id}}</template></el-table-column><el-table-column label="广告名称" align="center"><template slot-scope="scope">{{scope.row.name}}</template></el-table-column><el-table-column label="广告图片" width="120" align="center"><template slot-scope="scope"><img style="height: 80px" :src="scope.row.img" /></template></el-table-column></el-table></div><!-- 分页 --><div class="pagination-container"><el-paginationbackground@size-change="handlePageSizeChange"@current-change="handleCurrentPageChange"layout="total, sizes,prev, pager, next,jumper":current-page="page":page-sizes="[5,10, 20]":page-size="size":total="total"></el-pagination></div></div></template>

编写JS部分代码

<script>export default {//数据部分data() {return {list:[], //广澳数据 后台page: 1, //当前页前端size: 5, //每页显示条数 前端total: 0, //总条数 后台listLoading:true};},// 钩子函数created() {this.loadList();},methods: {// 获取广告数据loadList(){return this.axios.get("http://192.168.3.5:8080/ssm_web/PromotionAd/findAllPromotionAdByPage",{params: {currentPage:this.page,pageSize:this.size}}).then(res =>{this.list = res.data.content.list;this.total = res.data.content.total;this.listLoading = false;}).catch(error =>{this.$message("数据获取失败!");});},// 显示条数变化时触发handlePageSizeChange(size) {this.size = size;this.loadList();},// 当前页发生变化时触发handleCurrentPageChange(page) {this.page = page;this.loadList();}},}</script>

3.2.2 广告列表展示
1.需求分析

我们已经解决了分页问题,接下来再看一下广告页面要展示哪些内容:

广告列表的展示数据来源于两张表:

​ promotion_ad 广告表

​ promotion_space 广告位表

2.功能实现
数据部分

//数据部分data() {return {typeOptions:[], //下拉列表展示typeMap: {}, //保存广告位对象信息total: 0, //总条数size: 5, //每页显示条数page: 1, //当前页list: [], //广告数据listLoading: false};},

钩子函数

//钩子函数created() {//获取广告列表数据this.loadPromotionAd();//获取广告位数据this.loadPromotionSpace();},

函数部分

//方法1; 获取广告列表数据loadPromotionAd() {this.listLoading = true;return axios.get("http://192.168.3.5:8080/ssm_web/PromotionAd/findAllPromotionAdByPage",{params:{currentPage:this.page,pageSize:this.size}}).then(res =>{this.list = res.data.content.list;this.total = res.data.content.total;this.listLoading = false;}).catch(error =>{});},//方法2: 获取广告位置数据loadPromotionSpace() {this.listLoading = true;return axios.get("/PromotionSpace/findAllPromotionSpace").then(res =>{// 使用map进行遍历res.data.content.map(item => {// 将数据保存到typeMap key就是id,value就是广告位对象this.typeMap[item.id] = item;});this.listLoading = false;});},//方法3: 获取广告位置名称getSpaceName(spaceId) {if(!spaceId){return "";}return this.typeMap[spaceId] && this.typeMap[spaceId].name;},

3.2.3 广告状态修改

需求分析:点击按钮实现 状态修改,0 下线,1 上线

功能实现

页面部分,使用的是 el-switch 组件

​ active-value: switch 打开时的值

​ inactive-value : switch 关闭时的值

<!-- 上线与下线 --><el-table-column label="上线/下线" width="120" align="center"><template slot-scope="scope"><el-switch@change="handleUpdateStatus(scope.row)":active-value="1":inactive-value="0"v-model="scope.row.status"></el-switch></template></el-table-column>

JS部分

//方法4: 修改状态handleUpdateStatus(row) {this.$confirm('是否要修改上限线/下线状态?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() =>{axios.get("/PromotionAd/updatePromotionAdStatus",{params:{id:row.id,status:row.status}}).then(res =>{this.loadPromotionAd();}).catch(error =>{this.$message("修改状态失败!!!");});})},

3.3.4 广告新增&修改
1. 需求分析
点击添加广告,触发事件

<el-button size="mini" class="btn-add" @click="handleAdd()">添加广告</el-button>

路由导航到指定组件

//跳转到新增handleAdd() {this.$router.push({path: "/addAdvertise" });},

查看路由信息,跳转到的是 AddAdvertise.vue组件

{path: "addAdvertise",name: "AddAdvertise",component: () => import("../views/AdvertiseManage/AddAdvertise"),meta: {requireAuth: true, title: "添加广告" }},

AddAdvertise.vue组件

在AddAdvertise组件中,引入了 AdvertiseDetail组件,真正的操作是在这个组件中完成的

:isEdit=“false”: false表示是新增操作

<template><home-advertise-detail :isEdit="false"></home-advertise-detail></template><script>import HomeAdvertiseDetail from './AdvertiseDetail'export default {name: 'addHomeAdvertise',title: '添加广告',components: {HomeAdvertiseDetail }}</script><style></style>

AdvertiseDetail.vue 组件

该组件是进行 新增和修改广告的页面

2.功能实现

数据部分

//数据部分data() {return {homeAdvertise, //广告表单对象typeOptions: [], //广告位下拉列表rules};},

钩子函数

//钩子函数created() {//判断是新增还是修改if (this.isEdit) {//修改const id = this.$route.query.id;this.loadPromotion(id);} else {//新增this.homeAdvertise = {};}//获取广告位数据this.loadPromotionSpace();},

方法

//方法1: 获取广告位置数据loadPromotionSpace() {return axios.get("/PromotionSpace/findAllPromotionSpace").then(res =>{//使用map函数进行遍历,获取广告位id与name,保存到typeOptionsthis.typeOptions = res.data.content.map(item =>{return {label:item.name,value:item.id};})})},//方法2: 保存广告信息handleSave() {this.$refs.form.validate(valid =>{if(!valid) return false;axios.post("/PromotionAd/saveOrUpdatePromotionAd",this.homeAdvertise).then(res =>{// 返回上个页面 并刷新this.$router.back();}).catch(error =>{});});},//方法3: 修改回显广告信息loadPromotion(id) {return axios.get("/PromotionAd/findPromotionAdById?id=" +id).then(res =>{Object.assign(this.homeAdvertise,res.data.content);this.homeAdvertise.id = id;}).catch(error =>{});},

任务二 权限管理模块前端开发

1.用户管理

1.1 分页&条件查询用户数据

查询条件:

1. 用户手机号2. 注册时间,包含开始日期和结束日期

1.1.1 日期选择器组件

在查询条件中使用了 ElementUI中的日期选择器,我们一起来简单学习一下日期选择器的使用

/#/zh-CN/component/date-picker#mo-ren-xian-shi-ri-qi

在测试项目中,创建一个 TestDate.vue组件,复制代码到页面

<template><div class="block"><span class="demonstration">带快捷选项</span><el-date-pickerv-model="dateTime"type="daterange"align="right"unlink-panelsrange-separator="至"start-placeholder="开始日期"end-placeholder="结束日期":picker-options="pickerOptions"></el-date-picker><el-button type="primary" @click="getDate">查询</el-button></div></template><script>export default {data() {return {pickerOptions: {shortcuts: [{text: '最近一周',onClick(picker) {const end = new Date();const start = new Date();start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);picker.$emit('pick', [start, end]);}}, {text: '最近一个月',onClick(picker) {const end = new Date();const start = new Date();start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);picker.$emit('pick', [start, end]);}}, {text: '最近三个月',onClick(picker) {const end = new Date();const start = new Date();start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);picker.$emit('pick', [start, end]);}}]},dateTime: ''};},methods: {getDate(){const params = {};params.startCreateTime = this.dateTime[0];params.startCreateTime.setHours(0);params.startCreateTime.setMinutes(0);params.startCreateTime.setSeconds(0);params.endCreateTime = this.dateTime[1];params.endCreateTime.setHours(23);params.endCreateTime.setMinutes(59);params.endCreateTime.setSeconds(59);console.log(params);}},};</script>

1.1.2 功能实现

数据部分

//数据部分return {pickerOptions, //日期选择器选项设置total: 0, //总条数size: 10, //每页显示条数page: 1, //当前页filter,users: [], //用户数据loading: false,allocAdminId: "",allocDialogVisible: false,allocRoleIds: [],allRoleList: []};

JS部分

created() {//初始化用户数据this.loadUsers();},//方法1: 加载用户数据loadUsers() {this.loading = true;// 设置参数const params = {currentPage:this.page,pageSize:this.size};// 过滤条件if(this.filter.username) params.username = this.filter.username;//设置日期参数if (this.filter.resTime) {params.startCreateTime = this.filter.resTime[0];params.startCreateTime.setHours(0);params.startCreateTime.setMinutes(0);params.startCreateTime.setSeconds(0);params.endCreateTime = this.filter.resTime[1];params.endCreateTime.setHours(23);params.endCreateTime.setMinutes(59);params.endCreateTime.setSeconds(59);}// 请求后台接口return axios.post("/user/findAllUserByPage",params).then(res =>{this.users = res.data.content.list; // 用户数据this.total = res.data.content.total;this.loading = false;}).catch(error =>{this.$message("获取数据失败!!!");})},

1.2 用户状态设置

状态按钮

<el-button size="mini" type="text" @click="handleToggleStatus(scope.row)">{{ scope.row.status == "ENABLE" ? "禁用" : "启用" }}</el-button>

JS部分

//方法2: 修改用户状态handleToggleStatus(item) {return axios.get("/user/updateUserStatus",{params:{id:item.id,status:item.status}}).then(res =>{item.status = res.data.content;}).catch(error =>{this.$message("状态修改失败!!!");});},

2.权限管理

2.1 角色管理

2.1.1 展示&查询角色列表

角色组件是 Roles.vue,在该组件中对角色信息进行管理

功能实现

数据部分

// 数据部分data() {return {listQuery: {name: "" },list: null,listLoading: false,dialogVisible: false,role: Object.assign({}, defaultRole),isEdit: false};},

钩子函数,调用loadRoles,获取角色数据

created() {//获取角色列表this.loadRoles();},

//获取角色数据loadRoles() {return axios.post("/role/findAllRole",this.listQuery).then(res =>{this.list = res.data.content;this.listLoading = false;}).catch(error =>{this.$message("获取数据失败!!!");});},

请求携带的参数是:listQuery

<el-input v-model="listQuery.name" class="input-width" placeholder="角色名称" clearable></el-input>

//条件查询handleSearchList() {this.loadRoles();},

2.1.2 添加&修改角色
页面部分

<el-button size="mini" class="btn-add" @click="handleAdd()" style="margin-left:20px">添加角色</el-button>

打开添加角色窗口的方法

//添加角色弹窗handleAdd() {this.dialogVisible = true; //打开对话框this.isEdit = false; //false 添加操作this.role = Object.assign({}, defaultRole); // 创建role对象},

添加角色对话,使用v-model 进行双向数据绑定

<!-- 添加&修改 角色对话框 --><el-dialog :title="isEdit?'编辑角色':'添加角色'" :visible.sync="dialogVisible" width="40%"><el-form :model="role" label-width="150px" size="small"><el-form-item label="角色名称:"><el-input v-model="role.name" style="width: 250px"></el-input></el-form-item><el-form-item label="角色编码:"><el-input v-model="role.code" style="width: 250px"></el-input></el-form-item><el-form-item label="描述:"><el-input v-model="role.description" type="textarea" :rows="5" style="width: 250px"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false" size="small">取 消</el-button><el-button type="primary" @click="handleSave()" size="small">确 定</el-button></span></el-dialog>

添加角色方法

//添加&修改角色handleSave() {axios.post("/role/saveOrUpdateRole",this.role).then(res =>{this.dialogVisible = false;this.loadRoles();}).catch(error =>{this.$message("保存角色失败!!!");});},

修改角色的方法

修改按钮,点击传递当前行数据对象

<el-button size="mini" type="text" @click="handleUpdate( scope.row)">编辑</elbutton>

显示对话框,回显数据

//修改角色弹窗handleUpdate(row) {this.dialogVisible = true;this.isEdit = true;//回显数据this.role = Object.assign({}, row);},

修改角色,还是调用的handleSave 方法

2.1.3 删除角色

<el-button size="mini" type="text" @click="handleDelete(scope.row)">删除</elbutton>

这里使用到了ElementIUI中的 MessageBox 弹框

/#/zh-CN/component/message-box#options

//删除角色handleDelete(row) {this.$confirm('是否要删除该角色', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {// 请求后台axios.get("/role/deleteRole?id=" + row.id).then(res =>{this.loadRoles();}).catch(error =>{this.$message("操作失败!!!");});});},

2.1.4 为角色分配菜单
1.需求分析

​ 为角色分配菜单,一个角色可以拥有多个菜单权限

​ 一个菜单权限也可以被多个角色拥有

​ 角色与菜单之间的关系 是多对多

​ 点击分配菜单,页面展示效果

​ 前端要实现的效果

​ 第一步: 获取到所有的菜单数据,在树形控件中进行展示

​ 第二步: 将当前角色拥有的菜单权限,勾选上

2. 菜单展示功能实现
分配菜单按钮,点击传递当前行数据

<el-button size="mini" type="text" @click="handleSelectMenu(scope.row)">分配菜单</el-button>

路由导航到 allocMenu

//为角色分配菜单handleSelectMenu(row) {this.$router.push({path: "/allocMenu", query: {roleId: row.id } });},

routes.js

{path: "allocMenu",name: "AllocMenu",component: () =>import(/* webpackChunkName: 'allocMenu' */ "../views/PermissionManage/AllocMenu"),meta: {requireAuth: true, title: "角色菜单管理" }},

在AllocMenu.vue组件中完成 为角色分配菜单操作

数据部分

data() {return {menuTreeList: [], //菜单数据checkedMenuId: [], //被选中的菜单//树形结构子节点设置defaultProps: {children: "subMenuList",label: "name"},roleId: null};},

钩子函数

//钩子函数created() {//获取路由携带的idthis.roleId = this.$route.query.roleId;//获取菜单列表this.treeList();//获取角色所拥有的菜单信息this.getRoleMenu(this.roleId);},

//方法1: 获取菜单列表,使用树形控件展示treeList() {axios.get("/role/findAllMenu").then(res =>{console.log(res.data.content);// 获取树形控件所需的数据this.menuTreeList = res.data.content.parentMenuList;});},//方法2: 获取当前角色所拥有菜单列表idgetRoleMenu(roleId) {axios.get("/role/findMenuByRoleId?roleId=" + roleId).then(res =>{console.log(res.data.content);// 将角色已有的菜单权限进行勾选this.$refs.tree.setCheckedKeys(res.data.content);});},

3.分配菜单功能实现

分配菜单按钮

<div style="margin-top: 20px" align="center"><el-button type="primary" @click="handleSave()">保存</el-button><el-button @click="handleClear()">清空</el-button></div>

方法

//方法3: 修改角色所拥有的菜单列表handleSave() {// 1.获取所有被选中的节点const checkedNodes = this.$refs.tree.getCheckedNodes();// 2.定义数组 保存菜单idconst checkedMenuIds = [];// 3.遍历获取菜单idif(checkedNodes != null && checkedNodes.length > 0){//遍历获取节点对象for(let i = 0; i < checkedNodes.length; i++){const checkedNode = checkedNodes[i];// 保存菜单idcheckedMenuIds.push(checkedNode.id);// 判断 如果当前节点是子节点 && 该子节点的父节点的id没有被保存if(checkedNode.parentId !== -1 && checkedMenuIds.filter(item => checkedNode.parentId).length === 0){checkedMenuIds.push(checkedNode.parentId);}}}console.log(checkedMenuIds);// 4.向后台发送请求,完成为角色分配菜单的操作this.$confirm('是否分配菜单', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {// 准备参数const params = {roleId:this.roleId, //角色idmenuIdList:checkedMenuIds // 菜单id数组};// 请求后台axios.post("/role/RoleContextMenu",params).then(res =>{this.$router.back();}).catch(error =>{this.$message("菜单权限分配失败!!!");});});},

2.2 菜单管理

菜单组件是 Menus.vue,在该组件中对菜单信息进行管理

2.2.1 展示菜单列表

需求分析:菜单列表的展示是带有分页的

功能实现

数据部分

data() {return {total: 0, //总条数size: 10, //每页显示条数page: 1, //当前页list: [], //广告数据listLoading: false};},

钩子函数

created() {//获取菜单列表this.loadMenuList();},

//方法1: 加载菜单列表数据loadMenuList() {this.listLoading = true;return axios.get("/menu/findAllMenu",{params:{currentPage:this.page,pageSize:this.size}}).then(res =>{this.list = res.data.content.list;this.total = res.data.content.total;this.listLoading = false;}).catch(error => {this.$message.error("数据获取失败! ! !");});},

2.2.2 新增&修改菜单
1. 路由跳转流程
新增按钮,点击跳转

<el-button class="btn-add" @click="handleAddMenu()" size="mini">添加菜单</elbutton>

//新增菜单跳转handleAddMenu() {this.$router.push("/addMenu");},

2)AddMenu.vue 组件中引入了MenuDetail

<template><menu-detail :is-edit='false'></menu-detail></template><script>import MenuDetail from './MenuDetail'export default {name: 'addMenu',title: '添加菜单',components: {MenuDetail }}</script><style></style>

MenuDetail.vue 中完成菜单的新增与修改操作

2.需求分析

在打开新增菜单页面后,需要展示一个下拉框,下拉框中的数据是所有的顶级父菜单

3.功能实现
数据部分

data() {return {menu, //菜单对象selectMenuList: [], //下拉列表数据rules};},

钩子函数

在钩子函数中会进行判断,如果是修改操作,就根据ID 查询当前菜单信息,以及父菜单信息

如果是新增操作,则只查询父类菜单信息即可

// 钩子函数created() {if(this.isEdit){// 修改 回显菜单信息const id = this.$route.query.id;//获取当前菜单和父菜单信息this.findMenuInfoById(id);}else{// 添加this.menu = Object.assign({},menu);// 获取父菜单信息this.findMenuInfoById(-1);}},

//方法1: 添加或修改 下拉父菜单回显findMenuInfoById(id) {axios.get("/menu/findMenuInfoById?id=" + id).then(res =>{console.log(res.data);// 判断menuInfo是否为空if(res.data.content.menuInfo != null){// 不为空 修改操作,进行回显this.menu = res.data.content.menuInfo;}// 获取下拉列表中需要的父菜单信息this.selectMenuList = res.data.content.parentMenuList.map(item => {return {id: item.id , title: item.name};});// -1 显示无上级菜单this.selectMenuList.unshift({id: -1,title: "无上级菜单"});}).catch(err => {this.$message.error("数据获取失败! ! !");});},

点击保存

<el-button type="primary" @click="handleSave()">提交</el-button>

//保存菜单handleSave() {this.$refs.form.validate(valid => {if (!valid) return false;axios.post("/menu/saveOrUpdateMenu",this.menu).then(res =>{this.$router.back();}).catch(error =>{this.$message("保存菜单信息失败! ! !");});});}

2.3 资源管理

资源组件是 Resources.vue ,在该组件中对资源信息进行管理

2.3.1 展示&查询资源列表

展示资源数据 带有分页

查询资源数据,查询条件有三个

​ 资源名称

​ 资源路径

资源分类信息: 下拉列表

数据部分

//查询条件const listQuery = {currentPage: 1,pageSize: 5,name: null,url: null,categoryId: null};//资源对象const defaultResource = {id: null,name: null,url: null,categoryId: null,description: ""};

data() {return {listQuery, //查询条件total: 0,list: [], //资源数据cateList: [], //资源分类数据listLoading: false,dialogVisible: false,resource: Object.assign({}, defaultResource),isEdit: false,categoryOptions: [],defaultCategoryId: null};},

钩子函数

在钩子函数中,需要获取资源,以及资源分类的数据

//钩子函数created() {//获取资源数据this.getResourceList();//获取资源分类数据this.getResourceCateList();},

getResourceList() 方法获取的是资源信息

//方法1: 获取资源数据getResourceList() {this.listLoading = true;axios.post("/resource/findAllResource",this.listQuery).then(res =>{this.list = res.data.content.list;this.total = res.data.content.total;this.listLoading = false;}).catch(error =>{this.$message.error("数据获取失败! ! !");});},

getResourceCateList() 方法获取的是资源分类信息,在下拉框中展示

//方法2: 获取资源分类数据getResourceCateList() {axios.get("/ResourceCategory/findAllResourceCategory").then(res =>{// 保存资源分类this.cateList = res.data.content;// 遍历获取资源分类信息 展示下拉列表for(let i = 0; i < this.cateList.length; i++){// 取出分类对象const cate = this.cateList[i];// 保存资源分类的名称和id categoryOptionsthis.categoryOptions.push({label:cate.name, value:cate.id});}}).catch(error =>{this.$message.error("数据获取失败! ! !");});},

查询

<el-button style="float:right" type="primary" @click="handleSearchList()" size="small">查询搜索</el-button>

//查询按钮handleSearchList() {this.getResourceList();},

2.3.2 新增&修改资源
添加按钮

<el-button size="mini" class="btn-add" @click="handleAdd()" style="margin-left: 20px">添加</el-button>

显示添加资源表单的对话框

//添加资源对话框回显handleAdd() {this.dialogVisible = true;this.isEdit = false;this.resource = Object.assign({},defaultResource);},

资源分类信息使用下拉菜单进行展示:

v-model 的值为当前被选中的 el-option 的 value 属性值

<el-form-item label="资源分类:"><el-select v-model="resource.categoryId" placeholder="全部" clearable style="width: 250px"><el-optionv-for="item in categoryOptions":key="item.value":label="item.label":value="item.value"></el-option></el-select></el-form-item>

点击保存

<el-button type="primary" @click="handleSave()" size="small">确 定</el-button>

//添加&修改资源handleSave() {axios.post("/resource/saveOrUpdateResource",this.resource).then(res =>{this.dialogVisible =false;this.getResourceList();}).catch(error =>{this.$$message("操作失败! ! !");})},

修改操作,参数是当前行数据

<el-button size="mini" type="text" @click="handleUpdate(scope.row)">编辑</elbutton>

回显操作

//编辑资源 回显handleUpdate(row) {this.dialogVisible = true;this.isEdit = false;this.resource = Object.assign({},row);},

任务三 项目打包发布

1.用户权限控制

1.1 用户登录

用户登录界面,需要输入手机号密码

登录组件 login.vue

登录按钮

<el-button type="primary" :loading="loading" @click="submit('login-form')">{{ loading ? 'Loading...' : '登录' }}</el-button>

提交表的方法

//提交登录表单submit (ref) {// 校验this.$refs[ref].validate(valid => {if (!valid) return false// validatedthis.error = nullthis.loading = true// create token from remote//发送登录请求this.$store.dispatch('createToken', this.model).then(res => {if (res.state !== 1) {this.error = {title: 'Error occurred',message: 'Abnormal, please try again later!'};switch (res.state) {case 201:this.error.message = '请输入正确的手机号'break;case 202:this.error.message = '请输入密码'break;case 203:this.error.message = '密码错误'break;case 204:this.error.message = '验证码过期'break;case 205:this.error.message = '帐号错误或密码错误'break;case 206:this.error.message = '帐号错误或密码错误'break;case 207:this.error.message = '验证码错误'breakcase 500:this.error.message ='Internal server error, please try again later!'break;}this.loading = falsereturn;}// 登录成功this.$router.replace({path: this.$route.query.redirect || '/' })this.loading = false}).catch(err => {console.error(err)this.error = {title: 'Error occurred',message: 'Abnormal, please try again later!'};switch (err.response && err.response.status) {case 401:this.error.message = 'Incorrect username or password!'break;case 500:this.error.message ='Internal server error, please try again later!'break;}this.loading = false;})})}

this.$store.dispatch(“createToken”, this.model)

​ 这段代码的意思是调用 store仓库的actions.js中的createToken方法

发送登录请求,进行登录的代码

/*** 用户登录*/createToken: async ({commit }, {username, password }) => {//请求后台登录接口const res = await TokenService.userLogin({phone: username.trim(),password: password.trim()});console.log(res);//debugger;//判断结果不等于1,登录失败if (res.state !== 1) {return Promise.resolve(res);}//获取到contentconst result = res.content;//将token保存在 sessioncommit(CHANGE_SESSION, {accessToken: result.access_token});//将用户信息保存commit(CHANGE_SESSION, {user: result.user });return res;},

TokenService

import {TokenService, UserService } from "../services";

//登录请求 async ES6语法, 作用: 发送异步请求export const userLogin = async (data) => {//await 表示等待接收返回的数据return await PostRequest(`${process.env.VUE_APP_API_FAKE}/user/login${Serialize(data)}`)}

1.2 动态获取用户菜单

在我们登录成功后,会立即发送第二个请求,来获取用户的菜单权限列表

在actions.js 中完成请求后台接口 获取数据的操作

/*** 获取当前登录用户权限*/getUserPermissions: async ({commit }) => {//1.请求后台 获取当前用户的权限const res = await UserService.getUserPermissions();//2.判断if (!res.success) {//获取失败直接返回 falsereturn res.success;}//debugger;//3.获取数据成功,取出菜单 与 资源列表const {menuList, resourceList } = res.content;//4.下面的代码 就是在生成树形结构的菜单let menus = [];const formatMenu = treeData => {if (treeData.length > 0) {return treeData.map(item => formatMenu(item));}const result = {};//shown等于表示可以显示,将内容保存if (treeData.shown == 1) {result.id = treeData.id;result.text = treeData.name;result.label = treeData.name;result.name = treeData.href;result.icon = treeData.icon;result.shown = treeData.shown;} else {return "";}//获取子节点if (treeData.subMenuList) {result.children = [];treeData.subMenuList.forEach(item => {formatMenu(item) && result.children.push(formatMenu(item));});if (result.children.length === 0) {delete result.children;}}return result;};const memusMap = {};const splapMenu = treeData => {if (treeData.length > 0) {return treeData.map(item => splapMenu(item));}const result = {};result.id = treeData.id;result.text = treeData.name;result.label = treeData.name;result.name = treeData.href;result.icon = treeData.icon;result.shown = treeData.shown;result.name && (memusMap[result.name] = result);if (treeData.subMenuList) {result.children = [];treeData.subMenuList.forEach(item => {result.children.push(splapMenu(item));});}return result;};splapMenu(menuList);menus = formatMenu(menuList);commit(CHANGE_SIDERBAR_MENU, menus);return {menus, resourceList, menuList, memusMap };},

1.3 验证Token

1.3.1 导航守卫

​ 在执行路由之前先执行的一些钩子函数,比如验证用户是否有权限之类的操作,就需要使用

authorize.js 中配置了导航守卫,来对用户的登录进行限制

//导航守卫 to要访问的url, from从哪个路径跳转过来, next() 放行// Authorize (Make sure that is the first hook.)router.beforeHooks.unshift((to, from, next) => {// don't need authorizeif (!to.meta.requireAuth) return next();// check login statestore.dispatch("checkToken").then(valid => {// authorizedif (valid) {store.dispatch('getUserPermissions').then(res => {const {memusMap } = resif (memusMap.Courses && to.name === 'Home') {return next()} else if (memusMap[to.name]) {return next()} else if (Object.keys(memusMap).length > 0) {return next({name: memusMap[Object.keys(memusMap)[0]].name })} else {next({name: 'PermissionDenied' })}})return next();}// unauthorizedconsole.log("Unauthorized");next({name: "Login", query: {redirect: to.fullPath } });});});

在actions.js 中检查token是否可用

/*** 检查客户端令牌是否可用*/checkToken: async ({commit, getters }) => {//取出tokenconst token = getters.session.accessToken;if (!token) {//不可用return Promise.resolve(false);}return Promise.resolve(true);},

1.4 用户角色分配

1.4.1 流程分析

点击分配角色按钮

分配角色对话框

1.4.2 代码部分

显示对话框

//分配角色handleSelectRole(index, row) {//保存用户IDthis.allocAdminId = row.id;//获取角色列表this.getRoleList();//获取当前用户拥有的角色this.getUserRoleById(row.id);//打开对话框this.allocDialogVisible = true;},

获取角色列表,在下拉菜单中展示

//获取角色列表,在下拉菜单中展示getRoleList(id) {return axios.post("/role/findAllRole", this.listQuery).then(res => {console.log(res.data.content);this.allRoleList = res.data.content.map(item => {return {id: item.id, name: item.name};})}).catch(err => {this.$message("获取角色列表失败!!!");});},

获取当前用户拥有的角色,回显默认选中

//获取当前用户拥有的角色,默认选中getUserRoleById(id) {axios.get("/user/findUserRoleById?id=" + id).then(res => {// 取出数据const allocRoleList = res.data.content;// 取出数据中的角色idthis.allocRoleIds = [];if (allocRoleList != null && allocRoleList.length > 0) {for (let i = 0; i < allocRoleList.length; i++) {// 保存idthis.allocRoleIds.push(allocRoleList[i].id);}}}).catch(error =>{this.$message("操作失败!!!");});},

为用户分配角色

//为用户分配角色handleAllocRole() {// 准备参数const params = {userId: this.allocAdminId,roleIdList: this.allocRoleIds};axios.post("/user/userContextRole", params).then(res => {this.allocDialogVisible = false;});},

2. nginx

2.1 什么是nginx?

​ Nginx(发音同 engine x)是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev(伊戈尔·西索夫)所开发,供俄国大型的入口网站及搜索引擎Rambler(漫步者)(俄文:Рамблер)使用。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:新浪、网易、 腾讯等。

优点:

​ 1.占用内存少,并发能力强

​ 2.Nginx专为性能优化而开发,在高连接并发的情况下,能够支持高达 50000 个并发连接数的响应

​ 3.Nginx支持热部署,可以在不间断服务的情况下,对软件版本进行升级

2.2 应用场景

​ 1.http服务器:Nginx是一个http服务可以独立提供http服务。可以做网页静态服务器。

​ 2.虚拟主机:可以实现在一台服务器虚拟出多个网站。例如个人网站使用的虚拟主机。

​ 3.反向代理,负载均衡:当网站的访问量达到一定程度后,单台服务器不能满足用户的请求时,需要用多台服务器集群可以使用nginx做反向代理。并且多台服务器可以平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。

2.3 Nginx安装

下载nginx,官方网站:/

我们使用的版本是1.17.8版本。

Nginx在Linux下安装,只提供了源代码,所以我们需要进行编译

2.3.1 安装环境配置

1.因为Nginx是C语言编写的,所以需要配置C语言编译环境 (一定要在联网状态下安装)

需要安装gcc的环境。执行命令: yum install gcc-c++

注意:如果执行命令出现这样的提示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6lEtWkYI-1630919053125)(E:\MarkDown\拉勾笔记\Nginx 安装环境配置 问题)]

解决办法:

问题是 yum在锁定状态中,强制关掉yum进程即可rm -f /var/run/yum.pid

2.第三方的开发包,在编译之前需要安装这些第三方包。

PCRE

​ nginx的http模块使用pcre来解析正则表达式,所以需要在linux上安装pcre库

安装命令:yum install -y pcre pcre-devel

zlib

​ nginx使用zlib对http包的内容进行gzip,所以需要在linux上安装zlib库。

安装命令:yum install -y zlib zlib-devel

openssl

​ OpenSSL 是一个强大的安全套接字层密码库,nginx不仅支持http协议,还支持https,所以需要在linux安装openssl库。

安装命令:yum install -y openssl openssl-devel

2.3.2 安装Nginx 步骤

1.将Nginx的源码包上传到 Linux

2.解压Nginx

tar -xvf nginx-1.17.8.tar

3.进入到解压之后的目录 nginx-1.17.8

4.执行命令 configure,生成 Mikefile 文件

./configure \--prefix=/usr/local/nginx \--pid-path=/var/run/nginx/nginx.pid \--lock-path=/var/lock/nginx.lock \--error-log-path=/var/log/nginx/error.log \--http-log-path=/var/log/nginx/access.log \--with-http_gzip_static_module \--http-client-body-temp-path=/var/temp/nginx/client \--http-proxy-temp-path=/var/temp/nginx/proxy \--http-fastcgi-temp-path=/var/temp/nginx/fastcgi \--http-uwsgi-temp-path=/var/temp/nginx/uwsgi \--http-scgi-temp-path=/var/temp/nginx/scgi

执行命令后,生成了MakeFile文件

5.创建临时文件目录

mkdir /var/temp/nginx/client -p

6.执行make命令,进行编译

make

7.安装

make install

2.3.3 启动并访问 Nginx

1.进入到nginx 安装目录

cd /usr/local/nginx/

2.进入到 sbin目录,执行nginx命令

./nginx 启动./nginx -s stop 关闭ps aux | grep nginx 查看进程

3.通过浏览器进行访问,默认端口 80 (注意:是否关闭防火墙。)

2.4 配置虚拟主机

虚拟主机,指的是在一台服务器中,我们使用Nginx,来配置多个网站

如何区分不同的网站:

端口不同域名不同

2.4.1 通过端口区分不同的虚拟主机
Nginx配置文件

1.Nginx配置文件的位置

cd /usr/local/nginx/confnginx.conf 就是Nginx的配置文件

2.Nginx核心配置文件说明

worker_processes 1; #work的进程数,默认为1#配置 影响nginx服务器与用户的网络连接events {worker_connections 1024; #单个work 最大并发连接数}# http块是配置最频繁的部分 可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能http {# 引入mime类型定义文件include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65; # 超时时间#server 配置虚拟主机的相关参数 可以有多个,一个server就是一个虚拟主机server {# 监听的端口listen 80; #监听地址server_name localhost; # 默认请求配置location / {root html; # 默认网站根目录index index.html index.htm; # 欢迎页}# 错误提示页面error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}}}

使用Notpad,连接Linux

​ 使用notepad++来连接linux,好处是使用notepad++来编辑linux中文件的批量文字,会比直接在linux中操作方便快捷很多

1.Notepad 插件中安装NppFTP

2.打开NppFTP

3.选择设置

4.配置连接信息

5.连接

配置nginx.conf

1.使用Notpad 在nginx.conf 中添加一个 新的server

http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;server {listen 80;server_name localhost;location / {root html;index index.html index.htm;}}# 配置新的serverserver {listen 81; # 修改端口server_name localhost;location / {root html81; # 重新制定一个目录index index.html index.htm;}}}

2.复制一份 html目录

cp -r html html81

3.重新加载配置文件

sbin/nginx -s reload

4.访问

http://192.168.44.128 访问第一个serverhttp://192.168.44.128:81/ 访问第二个server

2.4.2 通过域名区分不同的虚拟主机
什么是域名

网址就是域名,是一个网站的地址,由域名提供商提供,一般需要购买

域名级别

一级域名

​ 比如 .com .org .cn

二级域名

​ 二级域名是在一级域名前加一级

​ 二级域名: ,

三级域名

域名绑定

​ 一个域名对应一个ip地址,一个ip地址可以被多个域名绑定。

​ 通过 DNS服务器去解析域名

配置域名映射

1.本地测试可以修改hosts文件。修改window的hosts文件:(C:\Windows\System32\drivers\etc)

可以配置域名和ip的映射关系,如果hosts文件中配置了域名和ip的对应关系,不需要走dns服务器。

配置一下nginx的映射192.168.44.128

2.使用SwitchHosts,修改hosts

​ 解压

​ 右键 以管理员身份运行

​ 配置IP与域名的映射

配置nginx.conf

#通过域名区分虚拟主机server {listen 80;server_name ;location / {root html-t1;index index.html index.htm;}}server {listen 80;server_name ;location / {root html-t2;index index.html index.htm;}}

创建 html-t1和 html-t2 目录

cp -r html html-t1cp -r html html-t2

修改一下index.html 中,刷新

sbin/nginx -s reload

访问

虽然只有一台服务器,但是这台服务器上运行着多个网站,访问不同的域名就可访问到不同的网站内容

2.5 反向代理

2.5.1 什么是代理

​ 代理其实就是一个中介,A和B本来可以直连,中间插入一个C,C就是中介。刚开始的时候,代理多数是帮助内网client访问外网server用的

客户机在发送请求时,不会直接发送给目的主机,而是先发送给代理服务器,代理服务接受客户机请求之后,再向主机发出,并接收目的主机返回的数据再发送给客户机。

2.5.2 正向代理

​ 比如我们国内访问谷歌,直接访问访问不到,我们可以通过一个正向代理服务器,先将请求发送到到代理服,代理服务器能够访问谷歌,这样由代理去谷歌取到返回数据,再返回给我们,这样我们就能访问谷歌了

正向代理代理的是客户端,服务端不知道实际发起请求的客户端.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fB1RfKTv-1630919053130)(E:\MarkDown\拉勾笔记\Nginx 正向代理)]

2.5.3 反向代理

​ 反向代理和正向代理的区别就是:正向代理代理客户端,反向代理代理服务器。

​ 反向代理是指用代理服务器接收客户端的请求,然后将请求转发给网站内部应用服务器,并将从服务器上得到的结果返回给客户端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VF8nc5wP-1630919053136)(E:\MarkDown\拉勾笔记\Nginx 反向代理)]

2.5.4 Nginx实现反向代理

Nginx作为反向代理服务器安装在服务端,Nginx的功能就是把请求转发给后面的应用服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RnH8SEVn-1630919053140)(E:\MarkDown\拉勾笔记\Nginx 实现反向代理)]

配置步骤

​ 第一步:简单的使用2个tomcat实例模拟两台http服务器,分别将tomcat的端口改为8080和8081

​ 第二步:启动两个tomcat。

./bin/startup.sh 访问两个tomcathttp://192.168.44.128:8081/http://192.168.44.128:8082/

​ 第三步:反向代理服务器的配置

#反向代理配置 #upstream中的server是真正处理请求的应用服务器地址upstream lagou1{#用server定义HTTP地址server 192.168.44.128:8081;}server {listen 80;server_name ;location / {# 利用 proxy_ pass可以将请求代理到upstream命名的HTTP服务proxy_pass http://lagou1; #转发到的地址index index.html index.htm;}}upstream lagou2{#用server定义HTTP地址server 192.168.44.128:8082;}server {listen 80;server_name ;location / {proxy_pass http://lagou2; index index.html index.htm;}}

​ 第四步:nginx重新加载配置文件

nginx -s reload

​ 第五步:配置域名, 在hosts文件中添加域名和ip的映射关系

192.168.44.128 192.168.44.128

​ 通过浏览器输入域名,访问Nginx代理服务器,Nginx根据域名将请求转发给对应的目标服务器,作为用户我们看到的是服务器的响应结果页面,在整个过程中目标服务器相对于客户端是不可见的,服务端向外暴露的就是Nginx的地址

2.6 负载均衡

2.6.1 什么是负载均衡

​ 当一个请求发送过来的时候,Nginx作为反向代理服务器,会根据请求找到后面的目标服务器去处理请求,这就是反向代理。那么,如果目标服务器有多台的话,找哪一个服务器去处理当前请求呢 ? 这个合理分配请求到服务器的过程就叫做负载均衡

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TsnwUVLu-1630919053144)(E:\MarkDown\拉勾笔记\负载均衡)]

2.6.2 为什么用负载均衡

​ 当系统面临大量用户访问,负载过高的时候,通常会使用增加服务器数量来进行横向扩展,负载均衡主要是为了分担访问量,将请求合理分发给不同的服务器,避免临时的网络堵塞

2.6.3 负载均衡策略
2.6.3.1 轮询

默认策略,每个请求按照时间顺序逐一分配到不同的服务器,如果某一个服务器下线,能自动剔除

配置方式

#负载均衡upstream lagouedu{# 用server定义 HTTP地址server 192.168.44.128:8081;server 192.168.44.128:8082;}server {listen 80;server_name ;location / {proxy_pass http://lagouedu;#转发的地址index index.html index.htm;}}

2.6.3.2 weight

可以根据服务器的实际情况调整服务器权重。权重越高分配的请求越多,权重越低,请求越少。默认是都是1

#负载均衡upstream lagouServer{# 用server定义 HTTP地址server 192.168.44.128:8081 weight=1;server 192.168.44.128:8082 weight=10;}

3.项目部署与发布

3.1 后台项目部署

3.1.1 Linux环境准备
需要安装的软件

关闭防火墙

使用SQLYog连接Linux上的MySQL,导入SQL脚本 创建项目所需的数据库

3.1.2 项目打包发布

​ 在平常开发的过程中,不同的环境中项目的相关配置也会有相关的不同,我们在不同的环境中部署就要手动修改为对应环境的配置,这样太麻烦了以及这样也会很容易出错。

接下来我们就通过maven的相关配置来在打包时指定各个环境对应配置文件

1. 修改ssm_dao 子模块
2. 第一步: 创建配置文件

在项目的src/main/resources 下面创建filter目录, 再创建development.properties,product.properties两个文件

development是开发配置内容。

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql:///ssm_lagou_edu?characterEncoding=UTF-8jdbc.username=rootjdbc.password=123456

product是正式配置内容

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://192.168.44.128:3306/ssm_lagou_edu?characterEncoding=UTF-8jdbc.username=Weiweijdbc.password=Weiwei@666

3. 第二步:配置jdbc.properties 文件

jdbc.properties中的内容不再写死,而是从上面两个文件中获取

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=${jdbc.url}jdbc.username=${jdbc.username}jdbc.password=${jdbc.password}

注意:${jdbc.url} 直接对应上面配置的development.properties或product.properties文件中的名称。

4. 第三步: 配置dao模块的的 pom.xml文件

添加如下配置

<profiles><profile><id>dev</id><properties><!-- 开发环境 --><env>development</env></properties><activation><activeByDefault>true</activeByDefault></activation></profile><profile><id>prod</id><properties><!-- 正式环境 --><env>product</env></properties></profile></profiles><build><finalName>web</finalName><filters><filter>src/main/resources/filter/${env}.properties</filter></filters><resources><resource><directory>src/main/resources</directory><excludes><exclude>filter/*.properties</exclude></excludes><filtering>true</filtering></resource></resources></build>

5. profile说明

​ profile可以让我们定义一系列的配置信息,然后指定其激活条件。这样我们就可以定义多个profile,然后每个profile对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果

默认启用的是dev环境配置:

<profiles><profile><id>dev</id><properties><!-- 开发环境 --><env>development</env></properties><activation><activeByDefault>true</activeByDefault></activation></profile><profile><id>prod</id><properties><!-- 正式环境 --><env>product</env></properties></profile></profiles>

指定数据库配置文件路径,此路径可以自定义:

<filters><filter>src/main/resources/filter/${env}.properties</filter></filters>

指定资源目录路径

<resources><resource><directory>src/main/resources</directory><!-- 资源根目录排除各环境的配置 --><excludes><exclude>filter/*.properties</exclude></excludes><filtering>true</filtering></resource></resources>

6.第四步: 打包

命令打包

打本地包 mvn -Pdev install 或者mvn install(因为本例activeByDefault配的为true)打产品包 mvn -Pprod install结果:src/main/resources/config/jdbc.properties根据 mvn -P 参数决定值

使用idea打包

7.打包后的文件

使用生产环境的配置文件,进行打包

打开这个war包,我们会发现 其他子模块都已经被打成jar包,放到了lib文件夹下

8. 发布

修改一下项目名称

上传到tomcat中,启动测试

在部署tomcat的 webapps目录下创建一个 upload文件夹,保存图片

mkdir upload

访问

http://192.168.44.128:8080/ssm_web/user/login?phone=18211111111&password=123456

获取到响应的JSON,发布成功

3.2 前端项目部署

3.2.1 修改配置文件

生产环境配置文件,配置后台URL

VUE_APP_NAME = Edu BossVUE_APP_TITLE = Lagou Edu Boss (Dev)VUE_APP_STORAGE_PREFIX = lagou_edu_boss_dev#VUE_APP_API_FAKE = /frontVUE_APP_API_FAKE = http://192.168.44.128:8080/ssm_web#VUE_APP_API_BASE = /bossVUE_APP_API_BASE = http://192.168.44.128:8080/ssm_web

自定义配置文件,配置打包相关信息

将下面内容拷贝到 vue.config.js

module.exports = {publicPath: process.env.NODE_ENV === "production" ? "/edu-boss/" : "/",indexPath: "index.html",assetsDir: "static",lintOnSave: process.env.NODE_ENV !== "production",productionSourceMap: false,devServer: {open: true,port: 8081}};

3.2.2 打包测试操作

打包命令

npm run build

在项目下会生成一个 dist 目录

在本地tomcat的webapps目录下,创建一个edu-boss文件夹,将dist目录中的文件拷贝到里面

测试:启动本地tomcat,访问前端项目 路径为:

http://localhost:8081/edu-boss/

3.2.3 发布前端项目

1.解压一个新的tomcat,修改端口号

#解压tar xvf apache-tomcat-8.5.50.tar #改名mv apache-tomcat-8.5.50 ui-tomcat

#修改端口号cd ui-tomcat/conf/vim server.xml

2.上传前端项目到 webapps

//上传 edu-boss.zip ,并解压unzip edu-boss.zip //删除edu-boss.ziprm -rf edu-boss.zip

3.运行前端项目,并访问

./bin/startup.sh //动态查看日志tail -f logs/catalina.out //访问http://192.168.44.128:8081/edu-boss/

3.2.4 修改tomcat默认访问项目

1.使用notpad打开前端tomcat的配置文件 server.xml,找到Host标签

2.在Host标签内加入

<Context path="" docBase="edu-boss" reloadable="true" debug="0" privileged="true"></Context>

3.重新启动 并访问前端项目,这个时候只需要直接访问 8081即可

http://192.168.44.128:8081/

3.2.5 配置反向代理

1.使用notpad打开nginx的配置文件 nginx.conf

2.配置反向代理

#配置ssm项目 反向代理upstream lagouedu{server 192.168.44.128:8081;}server {listen 80;server_name www.edu-;location / {proxy_pass http://lagouedu; #转发的地址index index.html index.htm;}}

3.修改本地hosts,配置域名映射

4.访问 www.edu-

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。