2022年最新《谷粒学院开发教程》:7 - 渲染前台页面

资料
资料地址
后台管理系统目录前台展示系统目录
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、最后

讲师、课程的增删改接口也需要添加对应注解,否则就会出现操作了主页却数据不变的情况,自行修改

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laptoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值