因为项目是B/C模式,所以之前的前后端针对的是管理员也就是商家。接下来的前后端开发针对的是普通用户也就是客户。
内容:搭nuxt环境→前台页面→轮播图→导入页面数据→把首页数据缓存
搭建前台环境
1、服务端渲染技术NUXT
服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据。
服务器端渲染(SSR)的优势主要在于:更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
通过 Ajax 获取内容,抓取工具并不会等待异步完成后再进行页面内容的抓取,使用服务器端渲染,我们可以获得更快的内容到达时间(time-to-content),无需等待所有的 JavaScript 都完成下载并执行,这就是服务端渲染有更好的SEO的原因
什么是SEO
搜索引擎优化(Search Engine optimization)。简而言之就是让当前网页更容易被搜索到。
通过研究各类搜索引擎如何抓取互联网页面和文件以及研究搜索引擎进行排序的规则,来对当前网页进行相关的优化,使其有更多的内容被搜索引擎收录,并针对不同的关键字获得搜索引擎的左边更高的排名,从而提高网站访问量,最终提升网站的销售能力及宣传效果。
什么是Nuxt
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
2、NUXT环境初始化
(1)下载压缩包
https://github.com/nuxt-community/starter-template/archive/master.zip
(2)解压
将template中的内容复制到 guli(项目文件夹)
(3)安装ESLint
将guli-admin项目下的.eslintrc.js配置文件复制到当前项目下
(4)修改package.json
name、description、author(必须修改这里,否则项目无法安装)
"name": "guli-admin",
"version": "3.8.0",
"description": "谷粒学院后台管理系统",
"author": "ruid <869771468@qq.com",
(5) 修改nuxt.config.js 这里的设置最后会显示在页面标题栏和meta数据中
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)安装依赖、测试
npm install
npm run dev
Nuxt的目录结构
CV整合静态页面
- 安装轮播图插件 ```bash npm install vue-awesome-swiper
轮播图有问题的来看这里,建议先删掉 node_modules 中的 vue-awesome-swiper 这个文件夹,再指定版本安装 npm i vue-awesome-swiper@3.1.3
2. 在 plugins 文件夹下新建文件 nuxt-swiper-plugin.js
```bash
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'
Vue.use(VueAwesomeSwiper)
在 nuxt.config.js 文件中配置插件
将 plugins 和 css节点 复制到 module.exports节点下module.exports = { // some nuxt config... plugins: [ { src: '~/plugins/nuxt-swiper-plugin.js', ssr: false } ], css: [ 'swiper/dist/css/swiper.css' ], }
复制静态资源assets 通常美工写好,直接复制
- 复制布局页面,layouts目录下default.vue 通常是美工写好
- 复制首页面:pages/index.vue,引入的页面
轮播图
复制轮播图插件
<!-- 幻灯片 开始 ,此时为手动切换,不是自动播放--> <div v-swiper:mySwiper="swiperOption"> <div class="swiper-wrapper"> <div class="swiper-slide" style="background: #040B1B;"> <a target="_blank" href="/"> <img src="~/assets/photo/banner/1525939573202.jpg" alt="首页banner"> </a> </div> <div class="swiper-slide" style="background: #040B1B;"> <a target="_blank" href="/"> <img src="~/assets/photo/banner/153525d0ef15459596.jpg" alt="首页banner"> </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> <!-- 幻灯片 结束 -->
轮播图变换js
<script> export default { data () { return { swiperOption: { //配置分页 pagination: { el: '.swiper-pagination'//分页的dom节点 }, //配置导航 navigation: { nextEl: '.swiper-button-next',//下一页dom节点 prevEl: '.swiper-button-prev'//前一页dom节点 } } } } } </script>
配置路由
固定路由
使用router-link构建路由,地址是/course。
- 固定规则:在default.vue中设置,之后nuxt会找pages/course/index.vue
- 在page目录创建文件夹course ,在course目录创建课程页面index.vue,这样固定路由就可以访问到了,这个路由是写死的,固定的
<template> <div> 课程列表 </div> </template>
动态路由
如果我们需要根据id查询一条记录,就需要使用动态路由。根据每次点击的具体内容,动态的匹配成要查询的路由。例如有3个课程,每当点击其中一个,路由就会根据其课程id动态变化。
固定规则:NUXT的动态路由是以下划线开头的vue文件,参数名为下划线后边的文件名
在pages下的course目录下创建:_id.vue
最终整合页面
- 课程列表页面:pages/course/index.vue : index.vue.txt
- 课程详情页面: pages/course/_id.vue: _id.vue.txt
- 讲师列表: pages/teacher/index.vue: index.vue.txt
讲师详情页面: pages/teacher/_id.vue : _id.vue.txt
Banner、课程和讲师
在数据库中创建轮播图表,调用数据,发布最新信息。相当于一个模块。service_cms
在service模块下创建子模块service-cms
- 使用代码生成器生成banner代码,crm_banner
mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456
返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8
配置mapper xml文件的路径 //扫描的xml路径
mybatis-plus.mapper-locations=classpath:com/kuang/educms/mapper/xml/*.xml
mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4. 配置启动类
```java
@SpringBootApplication
@ComponentScan({"com.kuang"})//指定配置扫描位置
@MapperScan("com.kuang.educms.mapper") //xml扫描地址
public class CmsApplication {
public static void main(String[] args) {
SpringApplication.run(CmsApplication.class, args);
}
}
后端
Banner模块分为前台和后台。对于后台有其前后端代码,相当于之前的讲师模块,可以对banner进行CURD操作。前台也有其前后端代码,但是只提供查询操作,进行banner的播放
编写后台的后端接口 ```java @Api(description = “后台轮播图接口”) @RestController @RequestMapping(“/educms/banneradmin”) @CrossOrigin public class BannerAdminController {
@Autowired private CrmBannerService bannerService;
@ApiOperation(value = “获取Banner分页列表”) @GetMapping(“pageBanner/{page}/{limit}”) public R pageBanner(@PathVariable long page,@PathVariable long limit) {
Page<CrmBanner> pageBanner = new Page<>(page,limit); bannerService.page(pageBanner,null); return R.ok().data("items",pageBanner.getRecords()).data("total",pageBanner.getTotal());
}
@ApiOperation(value = “添加Banner”) @PostMapping(“addBanner”) public R addBanner(@RequestBody CrmBanner crmBanner) {
bannerService.save(crmBanner); return R.ok();
}
@ApiOperation(value = “获取banner”) @GetMapping(“get/{id}”) public R get(@PathVariable String id) {
CrmBanner banner = bannerService.getById(id); return R.ok().data("item",banner);
}
@ApiOperation(value = “更新Banner”) @PutMapping(“updateBanner”) public R updateBanner(@RequestBody CrmBanner crmBanner) {
bannerService.updateById(crmBanner); return R.ok();
}
@ApiOperation(value = “删除Banner”) @DeleteMapping(“removeBanner/{id}”) public R removeBanner(@PathVariable String id) {
bannerService.removeById(id); return R.ok();
}
}
---
1. 编写前台的banner后端接口,仅有查询功能
```java
@Api(description = "后台轮播图接口")
@RestController
@RequestMapping("/educms/bannerfront")
@CrossOrigin
public class BannerFrontController {
@Autowired
private CrmBannerService bannerService;
@ApiOperation(value = "查询所有banner")
@GetMapping("getAllBanner")
public R getAllBanner() {
List<CrmBanner> list = bannerService.selectBanner();
return R.ok().data("list",list);
}
}
编写前台的课程和名师的后端接口
- 在service_edu 模块的Controller包下创建front包的IndexFrontController 类
通过字段view-count大小排序显示前八个课程—-热门课程 ```java @Api(description = “前台查询课程名师”) @RestController @RequestMapping(“/eduservice/indexfront”) @CrossOrigin public class IndexFrontController {
@Autowired private EduCourseService courseService;
@Autowired private EduTeacherService teacherService;
@ApiOperation(value = “查询前8条热门课程,查询前4条讲师”) @GetMapping(“index”) public R index() { //根据id进行降序排列,显示列表之后前8条热门课程记录 QueryWrapper
wrapperCourse = new QueryWrapper<>(); wrapperCourse.orderByDesc(“id”); wrapperCourse.last(“limit 8”); //在sql语句后拼接 List courseList = courseService.list(wrapperCourse); //根据id进行降序排列,显示列表之后前4条讲师 QueryWrapper
wrapperTeacher = new QueryWrapper<>(); wrapperTeacher.orderByDesc(“id”); wrapperTeacher.last(“limit 4”); List teacherList = teacherService.list(wrapperTeacher); return R.ok().data(“courseList”,courseList).data(“teacherList”,teacherList); }
}
<a name="FHiIs"></a>
## 前端
![09 首页数据显示(前端).png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1632275571871-b6164783-9fc2-4439-a7f5-13445ff3b675.png#clientId=ue12f6a8a-e060-4&from=paste&height=986&id=sfJ3E&margin=%5Bobject%20Object%5D&name=09%20%E9%A6%96%E9%A1%B5%E6%95%B0%E6%8D%AE%E6%98%BE%E7%A4%BA%EF%BC%88%E5%89%8D%E7%AB%AF%EF%BC%89.png&originHeight=986&originWidth=1357&originalType=binary&ratio=1&size=37992&status=done&style=none&taskId=u4ea51ff5-d15b-452d-9521-22c5d1097e2&width=1357)
1. banner后台的前端页面,添加修改删除和分页查询操作,和其他后台管理模块类似,此处不在编写
<a name="qucw8"></a>
### 编写前台的前端页面
<a name="BO6vM"></a>
#### Banner
1. 下载axios
```bash
npm install axios
- 封装axios需要自己封装,方便后续调用
import axios from 'axios'
// 创建axios实例
const service = axios.create({
baseURL: 'http://localhost:9001', // api的base_url
timeout: 20000 // 请求超时时间
})
export default service
- 前台接口 ```javascript import request from ‘@/utils/request’
export default {
//查询前两条banner数据
getListBanner() {
return request({
url: /educms/bannerfront/getAllBanner
,
method: ‘get’
})
}
}
4. 编写首页面中的js: index.vue , 展示banner
```javascript
<script>
import banner from '@/api/banner'
export default {
data () {
return {
swiperOption: {
//配置分页
pagination: {
el: '.swiper-pagination'//分页的dom节点
},
//配置导航
navigation: {
nextEl: '.swiper-button-next',//下一页dom节点
prevEl: '.swiper-button-prev'//前一页dom节点
}
},
//banner数组
bannerList:[]
}
},
created() {
//调用查询banner的方法
this.getBannerList()
},
methods:{
getBannerList() {
banner.getListBanner()
.then(response => {
this.bannerList = response.data.data.list
})
}
}
}
</script>
修改轮播图展示页面
之前的轮播图是固定的地址
<!-- 幻灯片 开始 --> <div v-swiper:mySwiper="swiperOption"> <div class="swiper-wrapper"> <div class="swiper-slide" style="background: #040B1B;"> <a target="_blank" href="/"> <img src="~/assets/photo/banner/1525939573202.jpg" alt="首页banner"> </a> </div> <div class="swiper-slide" style="background: #040B1B;"> <a target="_blank" href="/"> <img src="~/assets/photo/banner/153525d0ef15459596.jpg" alt="首页banner"> </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> <!-- 幻灯片 结束 -->
修改过的是根据js获得的动态的地址
<!-- 幻灯片 开始 --> <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> <!-- 幻灯片 结束 -->
加入nginx配置
课程和名师
- 前端api ```javascript import request from ‘@/utils/request’
export default {
//查询热门课程和讲师
getListBanner() {
return request({
url: /eduservice/indexfront/index
,
method: ‘get’
})
}
}
2. 页面中的js,调用课程和名师的数据
```javascript
data () {
return {
//....
eduList:[],
teacherList:[]
}
},
created() {
//.....
//调用查询热门课程和名师
this.getHotCourseTeacher()
},
methods:{
//查询热门课程和名师
getHotCourseTeacher() {
index.getListBanner()
.then(response => {
this.eduList = response.data.data.courseList
this.teacherList = response.data.data.teacherList
})
},
//....
}
- 页面中课程和名师显示
- 之前是固定的前八条信息
- 修改之后根据数据库中view-counts的大小动态变化
- 把原先的8个li简化为1个li,用for显示 ```html
{{teacher.intro}}
启动banner和edu服务,进行页面测试
<a name="ZgdEs"></a>
# Redis缓存首页数据
<a name="gIk3t"></a>
## Redis介绍
Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储)。Redis数据都是缓存在计算机内存中,而且Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化。
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1632314341075-b32352fb-8d3a-4b8e-aff4-b5c7581cf3ef.png#clientId=u809edd01-198d-4&from=paste&height=167&id=uc1a2ab34&margin=%5Bobject%20Object%5D&name=image.png&originHeight=334&originWidth=1640&originalType=binary&ratio=1&size=51431&status=done&style=none&taskId=u858762a9-8dc7-48ef-814b-b64157949bb&width=820)
<a name="rvst6"></a>
## Redis配置步骤
![image.png](https://cdn.nlark.com/yuque/0/2021/png/22137958/1632314390393-838f303f-01e0-420f-b3c1-17d467de24c1.png#clientId=u809edd01-198d-4&from=paste&height=606&id=u3e0fa49e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1211&originWidth=1855&originalType=binary&ratio=1&size=306191&status=done&style=none&taskId=uf7b5b773-568e-4ade-be2a-0a3ceb080de&width=927.5)
<a name="Hh6qr"></a>
## 具体代码
1. 在common模板中添加依赖
```xml
<!-- 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>
在service_base模块添加redis配置文件:RedisConfig.java
@EnableCaching //开启缓存 @Configuration //配置类 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)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
在接口中添加redis缓存注解
- 由于首页数据变化不是很频繁,而且首页访问量相对较大,所以我们有必要把首页接口数据缓存到redis缓存中,减少数据库压力和提高访问速度。
- 改造service-cms模块首页banner:CrmBannerServiceImpl 前台轮播图的后端接口的实现类 ```java @Cacheable(value = “banner”,key = “‘selectIndexList’”) //接口上加的注解,把数据放到缓存中
@Service
public class CrmBannerServiceImpl extends ServiceImpl
//@Cacheable用于查询
@Cacheable(value = "banner", key = "'selectIndexList'")
@Override
public List<CrmBanner> selectIndexList() {
List<CrmBanner> list = baseMapper.selectList(new
QueryWrapper<CrmBanner>().orderByDesc("sort"));
return list;
}
@Override
public void pageBanner(Page<CrmBanner> pageParam, Object o) {
baseMapper.selectPage(pageParam,null);
}
@Override
public CrmBanner getBannerById(String id) {
return baseMapper.selectById(id);
}
@CacheEvict(value = "banner", allEntries=true)
@Override
public void saveBanner(CrmBanner banner) {
baseMapper.insert(banner);
}
//CacheEvict用于更新或删除,allEntries属性清楚缓存
@CacheEvict(value = "banner", allEntries=true)
@Override
public void updateBannerById(CrmBanner banner) {
baseMapper.updateById(banner);
}
//CacheEvict用于更新或删除,allEntries属性清楚缓存
@CacheEvict(value = "banner", allEntries=true)
@Override
public void removeBannerById(String id) {
baseMapper.deleteById(id);
}
4. 重写接口以及其中方法,修改实现类并加上Redis注解
```java
@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);
}
}
----------------------------------------------------
重写接口中定义的方法
----------------------------------------------------
EduCourseServiceImpl
//查询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);
}
---------------------------------------------
EduTeacherServiceImpl
//查询前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);
}
Redis注解说明
- @Cacheable:根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
- @CachePut:使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
@CacheEvict:使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
启动Redis服务
按照谷粒商城利用docker安装redis,并启动
- 在service-cms模块配置文件添加redis配置
- 修改redis地址,加入配置文件 ```bash spring.redis.host=156.168.56.10 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.lettuce.pool.max-idle=5 spring.redis.lettuce.pool.min-idle=0 ```
- 启动后redis中有了banner缓存