资料 |
---|
资料地址 |
后台管理系统目录 | 前台展示系统目录 |
---|---|
1 - 构建工程篇 | 7 - 渲染前台篇 |
2 - 前后交互篇 | 8 - 前台登录篇 |
3 - 文件上传篇 | 9 - 前台课程篇 |
4 - 课程管理篇 | 10 - 前台支付篇 |
5 - 章节管理篇 | 11 - 统计分析篇 |
6 - 微服务治理 | 12 - 项目完结篇 |
一、服务端渲染技术NUXT
1.1、简介
-
服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据。
-
服务器端渲染(SSR)的优势主要在于:更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
-
如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再进行页面内容的抓取。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染(SSR)解决此问题。
-
另外,使用服务器端渲染,我们可以获得更快的内容到达时间(time-to-content),无需等待所有的JavaScript 都完成下载并执行,产生更好的用户体验,对于那些「内容到达时间(time-to-content)与转化率直接相关」的应用程序而言,服务器端渲染(SSR)至关重要。
-
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
1.2、初始化环境
1、下载压缩包
2、解压压缩包并提取压缩包里的 template 更名为 gulicollege_front
3、新建文件夹 gulicollege_admin 存放 后台管理系统ui、前台渲染系统front
4、修改front的package.json
"name": "gulicollege_front",
"version": "1.0.0",
"description": "谷粒学院前台网站",
"author": "Laptoy",
5、修改nuxt.config.js
head: {
title: '谷粒学院 - Java视频|HTML5视频|前端视频|Python视频|大数据视频-自学拿1万+月薪的IT在线视频课程,谷粉力挺,老学员为你推荐',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'keywords', name: 'keywords', content: '谷粒学院,IT在线视频教程,Java视频,HTML5视频,前端视频,Python视频,大数据视频' },
{ hid: 'description', name: 'description', content: '谷粒学院是国内领先的IT在线视频学习平台、职业教育平台。截止目前,谷粒学院线上、线下学习人次数以万计!会同上百个知名开发团队联合制定的Java、HTML5前端、大数据、Python等视频课程,被广大学习者及IT工程师誉为:业界最适合自学、代码量最大、案例最多、实战性最强、技术最前沿的IT系列视频课程!' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
6、进入 gulicollege_front 目录执行 npm install
7、测试运行 npm run dev
8、访问
二、整合项目主页面
2.1、幻灯片插件
1、安装插件
npm install swiper@4.5.1
npm install vue-awesome-swiper@3.1.3
2、配置幻灯片插件 - 在 plugins 文件夹下新建文件 nuxt-swiper-plugin.js
import Vue from 'vue'
import VueAwesomeSwiper from '../node_modules/vue-awesome-swiper/dist/ssr'
Vue.use(VueAwesomeSwiper)
3、在 nuxt.config.js 文件中配置插件
module.exports = {
// some nuxt config...
plugins: [
{ src: '~/plugins/nuxt-swiper-plugin.js', ssr: false }
],
css: [
'swiper/dist/css/swiper.css'
]
}
2.2、页面布局
1、将前端资源的 assets目录的所有文件 放到 本项目assets目录
2、添加 layouts目录下default.vue(主页面上下布局)
在资料(3-前端资源\nuxt讲师及课程初始化)里复制该页面
2.3、定义首页面
1、pages/index.vue
在资料(3-前端资源\nuxt讲师及课程初始化)里复制该页面
2.4、路由
1、固定路由
默认会去/page下找course文件夹下的index.vue文件夹显示
<router-link to="/course" tag="li" active-class="current">
<a>课程</a>
</router-link>
在page目录创建文件夹course ,在course目录创建 index.vue 进行测试
<template>
<div>
<h1>课程列表</h1>
</div>
</template>
2、动态路由
如果我们需要根据id查询一条记录,就需要使用动态路由。NUXT的动态路由是以下划线开头的vue文件,参数名为下划线后边的文件名
在pages下的course目录下创建 _id.vue
<template>
<div>
讲师详情
</div>
</template>
2.5、封装axios
1、安装 npm install axios
2、创建utils文件夹,utils下创建request.js
import axios from 'axios'
// 创建axios实例
const service = axios.create({
baseURL: 'http://localhost:9001', // api的base_url
timeout: 20000 // 请求超时时间
})
export default service
三、讲师页面初始化
3.1、列表页面
1、创建 pages/teacher/index.vue
在资料(3-前端资源\nuxt讲师及课程初始化)里复制该页面
3.2、详情页面
创建 pages/teacher/_id.vue
在资料(3-前端资源\nuxt讲师及课程初始化)里复制该页面
四、课程页面初始化
4.1、列表页面
创建 pages/course/index.vue
在资料(3-前端资源\nuxt讲师及课程初始化)里复制该页面
4.2、详情页面
创建 pages/course/_id.vue
在资料(3-前端资源\nuxt讲师及课程初始化)里复制该页面
五、搭建banner微服务
1、在service模块下创建子模块 service_cms
2、使用 sql脚本 生成数据
3、代码生成器逆向生成
gc.setOutputDir("D:\\MyCode\\IdeaCode\\project\\gulicollege\\guli_parent\\service\\service_cms" + "/src/main/java"); //输出目录
pc.setModuleName("cmsservice"); //模块名
strategy.setInclude("crm_banner");//根据数据库哪张表生成,有多张表就加逗号继续填写
4、配置文件
# 服务端口
server.port=8004
# 服务名
spring.application.name=service-cms
# nacos注册中心
spring.cloud.nacos.discovery.server-addr=120.76.55.55:8848
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?useSSL=false&useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
# 返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# 配置mapper xml文件的路径
mybatis-plus.mapper-locations=/mapper/*.xml
# mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
5、启动类
@SpringBootApplication
@ComponentScan("com.laptoy")
@MapperScan("com.laptoy.cmsservice.mapper")
public class Service_cms_Main8004 {
public static void main(String[] args) {
SpringApplication.run(Service_cms_Main8004.class, args);
}
}
六、创建服务接口
6.1、banner后台管理系统接口后端
1、BannerQuery - 模糊查询条件
@Data
public class BannerQuery implements Serializable {
private static final long serialVersionUID = 1L;
//幻灯片名字
private String name;
private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
private String end;
}
2、控制层
@Api(tags = "后台banner管理接口")
@RestController
@RequestMapping("/cmsservice/bannerAdmin")
@CrossOrigin //解决跨域问题
public class BannerAdminController {
@Autowired
private CrmBannerService crmBannerService;
//条件分页查询banner
@PostMapping("/pageBanner/{page}/{limit}")
public R pageBanner(@PathVariable Long page, @PathVariable Long limit, @RequestBody(required = false) BannerQuery bannerQuery) {
Page<CrmBanner> bannerPage = new Page<>(page, limit);
crmBannerService.pageQuery(bannerPage, bannerQuery);
//获取数据
List<CrmBanner> list = bannerPage.getRecords();
//获取总记录数
long total = bannerPage.getTotal();
return R.ok().data("data", list).data("total", total);
}
//添加banner
@PostMapping("/addBanner")
public R addBanner(@RequestBody CrmBanner crmBanner) {
boolean flag = crmBannerService.save(crmBanner);
if (flag) {
return R.ok();
} else {
return R.error();
}
}
//修改banner
@PostMapping("/updateBanner")
public R updateBanner(@RequestBody CrmBanner crmBanner) {
boolean flag = crmBannerService.updateById(crmBanner);
if (flag) {
return R.ok();
} else {
return R.error();
}
}
//根据id删除banner
@DeleteMapping("/deleteBannerById/{id}")
public R deleteBannerById(@PathVariable String id) {
boolean flag = crmBannerService.removeById(id);
if (flag) {
return R.ok();
} else {
return R.error();
}
}
//根据id查询banner
@GetMapping("/getBannerById/{id}")
public R getBannerById(@PathVariable String id) {
CrmBanner crmBanner = crmBannerService.getById(id);
return R.ok().data("item", crmBanner);
}
}
3、业务层
//多条件带分页查询
@Override
public void pageQuery(Page<CrmBanner> bannerPage, BannerQuery bannerQuery) {
QueryWrapper<CrmBanner> wrapper = new QueryWrapper<>();
if (bannerQuery != null) {
String name = bannerQuery.getName();
String begin = bannerQuery.getBegin();
String end = bannerQuery.getEnd();
if (!StringUtils.isEmpty(name)) {
wrapper.like("title", name);
}
if (!StringUtils.isEmpty(begin)) {
wrapper.ge("gmt_create", begin);
}
if (!StringUtils.isEmpty(end)) {
wrapper.le("gmt_modified", end);
}
}
//排序
wrapper.orderByDesc("gmt_create");
//带上门判断后的条件进行分页查询
baseMapper.selectPage(bannerPage, wrapper);
}
6.2、banner后台管理系统接口前端
1、gulicollege_ui\src\api\banner\banner.js
import request from '@/utils/request' //引入已经封装好的axios 和 拦截器
export default {
//1、幻灯片列表(多条件分页查询)
//page:当前页,limit:每页记录数,teacherQuery:条件对象
getBannerListPage(page, limit, bannerQuery) {
return request({
url: `/cmsservice/bannerAdmin/pageBanner/${page}/${limit}`,
method: 'post',
//teacherQuery条件对象,如果后端使用RequestBody获取数据
//data表示把对象转换成json对象进行传递到接口里
data: bannerQuery
})
},
//根据id删除幻灯片
removeById(id) {
return request({
url: `/cmsservice/bannerAdmin/deleteBannerById/${id}`,
method: 'delete',
})
},
//新增幻灯片
saveBanner(banner) {
return request({
url: `/cmsservice/bannerAdmin/addBanner`,
method: `post`,
data: banner
})
},
//根据id查询幻灯片
updateById(id) {
return request({
url: `/cmsservice/bannerAdmin/getBannerById/${id}`,
method: `get`,
})
},
//修改幻灯片信息
updateBannerInfo(crmBanner) {
return request({
url: `/cmsservice/bannerAdmin/updateBanner`,
method: `post`,
data: crmBanner
})
},
}
2、路由
// 幻灯片路由
{
path: '/banner',
component: Layout,
//redirect:重定向地址
redirect: '/banner/list',
name: '幻灯片列表',
//title:显示标签 , icon:显示图标
meta: { title: '幻灯片管理', icon: 'example' },
children: [
{
path: 'list',
name: '幻灯片列表',
component: () => import('@/views/edu/banner/list.vue'),
meta: { title: '幻灯片列表', icon: 'table' }
},
{
path: 'save',
name: '添加跑马灯',
component: () => import('@/views/edu/banner/save.vue'),
meta: { title: '添加幻灯片', icon: 'tree' }
},
{
path: 'edit/:id',
name: '幻灯片编辑',
component: () => import('@/views/edu/banner/save.vue'),
meta: { title: '幻灯片编辑', noCache: true },
hidden: true
}
]
},
3、list.vue
.<template>
<div>
<!--多条件查询表单-->
<el-form :inline="true" class="demo-form-inline" style="margin-left: 20px; margin-top: 12px">
<el-form-item label="名称">
<el-input v-model="bannerQuery.name" placeholder="请输入名称"></el-input>
</el-form-item>
<el-form-item label="添加时间">
<el-time-picker placeholder="选择开始时间" v-model="bannerQuery.begin" value-format="yyyy-MM-dd HH:mm:ss" default-time="00:00:00" type="datetime"></el-time-picker>
</el-form-item>
<el-form-item>
<el-time-picker placeholder="选择截止时间" v-model="bannerQuery.end" value-format="yyyy-MM-dd HH:mm:ss" default-time="00:00:00" type="datetime"></el-time-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button>
<el-button type="default" @click="resetData()">清空</el-button>
</el-form-item>
</el-form>
<!--数据显示的表格-->
<el-table :data="list" style="width: 100%" border fit highlight-current-row element-loading-text="数据加载中">
<el-table-column prop="date" label="序号" width="70" align="center">
<template slot-scope="scope">
{{ (page - 1) * limit + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="title" label="名称" width="80"> </el-table-column>
<el-table-column prop="imageUrl" label="图片url" />
<el-table-column prop="linkUrl" label="链接地址" />
<el-table-column prop="gmtCreate" label="添加时间" width="160" />
<el-table-column prop="sort" label="排序" width="60" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<router-link :to="'/banner/edit/' + scope.row.id">
<el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
</router-link>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="removeById(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--分页组件-->
<el-pagination background layout="prev, pager, next,total,jumper" :total="total" :page-size="limit" style="padding: 30px 0; text-align: center" :current-page="page" @current-change="getList">
</el-pagination>
</div>
</template>
<script>
//引入要调用的方法,teacher.js文件
import banner from "@/api/banner/banner.js";
export default {
//写核心代码位置
data() {
//1、定义变量和初始值
return {
list: null, //查询之后给接口返回的数据装的集合
page: 1, //当前页
limit: 10, //每页显示记录数
bannerQuery: {}, //条件封装对象
total: 0, //总记录数
};
},
created() {
//页面渲染之前执行,调用method定义的方法
//调用
this.getList();
},
methods: {
//调用具体的方法,调用teacher.js定义的方法
//讲师列表的方法
getList(page = 1) {
this.page = page;
banner
.getBannerListPage(this.page, this.limit, this.bannerQuery)
.then((resp) => {
//resp接口返回的数据
// console.log(resp);
this.list = resp.data.data;
this.total = resp.data.total;
}) //请求成功
.catch((err) => {
console.log(err);
}); //请求失败
},
//清空方法
resetData() {
//表单输入项数据清空
this.bannerQuery = {};
//查询所有讲师数据
this.getList();
},
removeById(id) {
this.$confirm("此操作将永久删除该讲师记录, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
//点击确定,执行then方法
banner.removeById(id).then(resp => {
//删除成功
//提示信息
this.$message({
type: "success",
message: "删除成功!",
});
//刷新页面
this.getList();
});
});
},
},
};
</script>
4、save.vue
<template>
<div class="app-container">
<el-form label-width="120px">
<el-form-item label="幻灯片名称">
<el-input v-model="banner.title" />
</el-form-item>
<el-form-item label="幻灯片排序">
<el-input-number v-model="banner.sort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="幻灯片url">
<el-input v-model="banner.imageUrl" />
</el-form-item>
<el-form-item label="link_url">
<el-input v-model="banner.linkUrl" :rows="10" type="textarea" />
</el-form-item>
<el-form-item>
<el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
//引入对应的新增api方法
import banner from "@/api/banner/banner.js";
export default {
data() {
return {
banner: {
title: "",
sort: 0,
imageUrl: "",
linkUrl: "",
},
//上传弹框组件是否显示
imagecropperShow: false,
//上传组件key值
imagecropperKey: 0,
//获取dev.env.js里面的ip:端口号地址
BASE_API: process.env.BASE_API,
saveBtnDisabled: false, // 保存按钮是否禁用,
};
},
methods: {
saveOrUpdate() {
//判断修改还是新增操作
//根据teacher对象是否有id值来判断
if (!this.banner.id) {
//没有id值,做【新增操作】
this.saveBtnDisabled = true;
this.saveData();
} else {
//有id值,做【修改操作】
this.updateBanner();
}
},
//修改幻灯片的方法
updateBanner() {
banner.updateBannerInfo(this.banner).then((resp) => {
//提示信息
this.$message({
message: "修改幻灯片记录成功",
type: "success",
});
//回到幻灯片列表 路由跳转
this.$router.push({ path: "/banner/list" });
});
},
// 保存
saveData() {
banner.saveBanner(this.banner).then((resp) => {
//添加成功
//提示信息
this.$message({
message: "添加幻灯片记录成功",
type: "success",
});
//回到幻灯片列表 路由跳转
this.$router.push({ path: "/banner/list" });
});
},
//根据id查询,数据回显
getInfoById(id) {
banner.updateById(id).then((resp) => {
this.banner = resp.data.item;
});
},
init() {
//判断路径中是否有id值
if (this.$route.params && this.$route.params.id) {
//从路径中获取id值
const id = this.$route.params.id;
//调用根据id查询的方法
this.getInfoById(id);
} else {
this.banner = {};
}
},
},
created() {
//在页面渲染之前
this.init();
},
watch: {
$route(to, from) {
//路由变化方式,当路由发送变化,方法就执行
console.log("watch $route");
this.init();
},
},
};
</script>
6.3、banner前台查询接口后端
首页获取banner数据接口
@CrossOrigin //解决跨域问题
@RestController
@RequestMapping("/cmsservice/bannerFront")
public class BannerFrontController {
@Autowired
private CrmBannerService crmBannerService;
//查询所有幻灯片
@GetMapping("getAll")
public R getAll(){
List<CrmBanner> list = crmBannerService.getAllBanner();
return R.ok().data("list",list);
}
}
@Service
public class CrmBannerServiceImpl extends ServiceImpl<CrmBannerMapper, CrmBanner> implements CrmBannerService {
@Override
public List<CrmBanner> getAllBanner() {
List<CrmBanner> list = this.list(null);
return list;
}
}
6.4、热门课程前台接口后端
1、在service-edu模块创建controller
- 查询最新前4条讲师数据
- 查询最新前8条课程数据
@RestController
@CrossOrigin
@RequestMapping("/eduservice/indexFront")
public class IndexFrontController {
@Autowired
private EduCourseService eduCourseService;
@Autowired
private EduTeacherService eduTeacherService;
//查询前8条热门课程,查询前4个名师
@GetMapping("/index")
public R index() {
//调用查询前8热门课程的方法
List<EduCourse> courseList = eduCourseService.selectHotCourse();
//查询前4张热门讲师
List<EduTeacher> teacherList = eduTeacherService.selectHotTeacher();
return R.ok().data("courseList", courseList).data("teacherList", teacherList);
}
}
//查询前4个热门讲师
@Override
public List<EduTeacher> selectHotTeacher() {
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
wrapper.last("limit 4");
return baseMapper.selectList(wrapper);
}
//查询8个热门课程
@Override
public List<EduCourse> selectHotCourse() {
//查询前8条热门课程
QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("view_count");
wrapper.last("limit 8");
return baseMapper.selectList(wrapper);
}
七、前台主页展示
7.1、前台banner数据展示
1、新建 api/banner.js
import request from '@/utils/request'
export default {
//查询前两条banner数据
getListBanner() {
return request({
url: '/cmsservice/bannerFront/getAll/',
method: 'get'
})
}
}
2、pages/index.vue
<!-- 幻灯片 开始 -->
<div v-swiper:mySwiper="swiperOption">
<div class="swiper-wrapper">
<div v-for='banner in bannerList' :key='banner.id' class="swiper-slide" style="background: #040b1b">
<a target="_blank" :href="banner.linkUrl">
<img :src="banner.imageUrl" :alt="banner.title" />
</a>
</div>
</div>
<div class="swiper-pagination swiper-pagination-white"></div>
<div class="swiper-button-prev swiper-button-white" slot="button-prev"></div>
<div class="swiper-button-next swiper-button-white" slot="button-next"></div>
</div>
<!-- 幻灯片 结束 -->
<script>
import banner from '@/api/banner';
export default {
data() {
return {
...
bannerList: [],
};
},
methods: {
getBannerList() {
banner.getListBanner().then(resp => {
this.bannerList = resp.data.data.list
})
}
},
created() {
//获取两条banner数据
this.getBannerList();
},
};
</script>
7.2、前台热门课程数据展示
1、新建 api/index.js
import request from '@/utils/request'
export default {
//查询热门课程和名师
getIndexData() {
return request({
url: '/eduservice/indexFront/index/',
method: 'get'
})
}
}
2、页面
<!-- 课程和名师模块 -->
<div id="aCoursesList">
<!-- 网校课程 开始 -->
<div>
<section class="container">
<header class="comm-title">
<h2 class="tac">
<span class="c-333">热门课程</span>
</h2>
</header>
<div>
<article class="comm-course-list">
<ul class="of" id="bna">
<li v-for="course in courseList" :key="course.id">
<div class="cc-l-wrap">
<section class="course-img">
<img :src="course.cover" class="img-responsive" :alt="course.title" />
<div class="cc-mask">
<a href="#" title="开始学习" class="comm-btn c-btn-1">开始 学习</a>
</div>
</section>
<h3 class="hLh30 txtOf mt10">
<a href="#" :title="course.title" class="course-title fsize18 c- 333">{{ course.title }}</a>
</h3>
<section class="mt10 hLh20 of">
<span class="fr jgTag bg-green" v-if="Number(course.price) === 0">
<i class="c-fff fsize12 f-fA">免费</i>
</span>
<span class="fl jgAttr c-ccc f-fA">
<i class="c-999 f-fA">9634人学习</i>
|
<i class="c-999 f-fA">9634评论</i>
</span>
</section>
</div>
</li>
</ul>
<div class="clear"></div>
</article>
<section class="tac pt20">
<a href="#" title="全部课程" class="comm-btn c-btn-2">全部课程</a>
</section>
</div>
</section>
</div>
<!-- 网校课程 结束 -->
<!-- 网校名师 开始 -->
<div>
<section class="container">
<header class="comm-title">
<h2 class="tac">
<span class="c-333">名师大咖</span>
</h2>
</header>
<div>
<article class="i-teacher-list">
<ul class="of">
<li v-for="teacher in teacherList" :key="teacher.id">
<section class="i-teach-wrap">
<div class="i-teach-pic">
<a href="/teacher/1" :title="teacher.name">
<img :alt="teacher.name" :src="teacher.avatar" />
</a>
</div>
<div class="mt10 hLh30 txtOf tac">
<a href="/teacher/1" :title="teacher.name" class="fsize18 c-666">{{teacher.name}}</a>
</div>
<div class="hLh30 txtOf tac">
<span class="fsize14 c-999">{{teacher.career}}</span>
</div>
<div class="mt15 i-q-txt">
<p class="c-999 f-fA">
{{teacher.intro}}
</p>
</div>
</section>
</li>
</ul>
<div class="clear"></div>
</article>
<section class="tac pt20">
<a href="#" title="全部讲师" class="comm-btn c-btn-2">全部讲师</a>
</section>
</div>
</section>
</div>
<!-- 网校名师 结束 -->
</div>
import banner from "@/api/banner";
import index from "@/api/index";
export default {
data() {
return {
swiperOption: {
//配置分页
pagination: {
el: ".swiper-pagination", //分页的dom节点
},
//配置导航
navigation: {
nextEl: ".swiper-button-next", //下一页dom节点
prevEl: ".swiper-button-prev", //前一页dom节点
},
},
bannerList: [],
teacherList: [],
courseList: [],
};
},
methods: {
// 查询幻灯片
getBannerList() {
banner.getListBanner().then((resp) => {
this.bannerList = resp.data.data.list;
});
},
// 查询热门课程和讲师
getHotCourseAndTeacher() {
index.getIndexData().then((resp) => {
this.courseList = resp.data.data.courseList;
this.teacherList = resp.data.data.teacherList;
});
},
},
created() {
//获取两条banner数据
this.getBannerList();
//获取热门课程和讲师
this.getHotCourseAndTeacher();
},
};
八、首页数据添加Redis缓存
8.1、启动Redis
1、拷贝一份redis.conf到宿主机目录(作为容器的映射,放在/mydata/guli/redis/conf/redis.conf
)
mkdir -p /mydata/guli/redis/conf
cp source /mydata/guli/redis/conf/redis.conf
2、修改配置文件
- 禁用仅本地连接
# bind 127.0.0.1
- 关闭守护进程后台启动
daemonize no
- 关闭保护模式
protected-mode no
3、运行容器
docker run -p 6379:6379 --name guli_redis \
-v /mydata/guli/redis/data:/data \
-v /mydata/guli/redis/conf/redis.conf:/etc/redis/redis.conf \
--restart=always \
--network guli \
-d redis redis-server /etc/redis/redis.conf
4、进入客户端
1、docker exec -it guli_redis bash
2、redis-cli
8.2、注解介绍
1、@Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不
存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
2、@CachePut
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存
中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
3、@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
8.3、整合Redis
1、common模块添加依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
2、配置文件 - service-cms模块 和 service-edu模块
# redis
spring.redis.host=120.76.55.55
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#spring.redis.password=你设置的redis密码,没有可以不写
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
3、在service-base模块添加redis配置类
@Configuration //配置类
@EnableCaching //开启缓存
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600)) //设置缓存存在的时间 600s
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
3、改造查询幻灯片业务 service-cms
@Service
public class CrmBannerServiceImpl extends ServiceImpl<CrmBannerMapper, CrmBanner> implements CrmBannerService {
@Override
@Cacheable(key = "selectIndexList", value = "banner")
public List<CrmBanner> getAllBanner() {
QueryWrapper<CrmBanner> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
wrapper.last("limit 2");
List<CrmBanner> list = this.list(wrapper);
return list;
}
}
4、改造查询名师和课程业务 service-edu
//查询前4个热门讲师
@Override
@Cacheable(key = "'selectHotTeacher'", value = "teacher")
public List<EduTeacher> selectHotTeacher() {
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
wrapper.last("limit 4");
return baseMapper.selectList(wrapper);
}
//查询8个热门课程
@Override
@Cacheable(key = "'selectHotCourse'", value = "course")
public List<EduCourse> selectHotCourse() {
//查询前8条热门课程
QueryWrapper<EduCourse> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("view_count");
wrapper.last("limit 8");
return baseMapper.selectList(wrapper);
}
5、启动测试刷新首页
6、最后
讲师、课程的增删改接口也需要添加对应注解,否则就会出现操作了主页却数据不变的情况,自行修改