在线学习平台-项目技术点-前台
报错解决方法
1、P166-尚硅谷_在线教育_Nuxt整合错误_nuxt friendly-errors-CSDN博客
2、P168
3、P170
4、P173
npm remove axios npm install axios@0.18.0
1、服务端渲染技术NUXT
1.1服务端渲染SSR
服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容,而不是在客户端通过AJAX获取数据。
服务器端渲染(SSR)的优势主要在于:更好的 SEO(搜索引擎优化)
另外,使用服务器端渲染,我们可以获得更快的内容到达时间(time-to-content),无需等待所有的 JavaScript 都完成下载并执行,产生更好的用户体验,对于那些「内容到达时间(time-to-content)与转化率直接相关」的应用程序而言,服务器端渲染(SSR)至关重要。
1.2Nuxt.js
Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。
1)NUXT目录结构
(1)资源目录 assets
用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。
(2)组件目录 components
用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性。
(3)布局目录 layouts
用于组织应用的布局组件。
(4)页面目录 pages
用于组织应用的路由及视图。Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。
(5)插件目录 plugins
用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。
(6)nuxt.config.js 文件
nuxt.config.js 文件用于组织Nuxt.js 应用的个性化配置,以便覆盖默认配置。
2)安装幻灯片插件(轮播图)
-安卓
npm install vue-awesome-swiper@3.1.3
-在 plugins 文件夹下新建文件 nuxt-swiper-plugin.js
//nuxt-swiper-plugin.js
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'
Vue.use(VueAwesomeSwiper)
-在 nuxt.config.js 文件中配置插件
module.exports = {
// some nuxt config...
plugins: [
{ src: '~/plugins/nuxt-swiper-plugin.js', ssr: false }
],
css: [
'swiper/dist/css/swiper.css'
]
}
1.3Nuxt路由
1)固定路由
<router-link to="/course" tag="li" active-class="current">
<a>课程</a>
</router-link>
路径是固定地址,不发生变化
to属性设置路由跳转地址:/course
做法:在pages里面创建文件夹course->在course文件夹创建index. vue
2)动态路由
每次生成路由地址不一样,比如课程详情页面,每个课程id不一样
- 如果我们需要根据id查询一条记录,就需要使用动态路由。
- NUXT的动态路由是以下划线开头的vue文件,参数名为下划线后边的文件名
-在pages下的course目录下创建_id.vue
2.Redis
2.1介绍
- 读取速度快,存储访问量大,改变少的数据;
- NOSQL系统之一;
- key-value存储系统(区别于MySQL的二维表格的形式存储;
- Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据,
- Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化;
- 支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合);
- 支持过期时间,支持事务,消息订阅;
2.2首页接口数据添加redis缓存
由于首页数据变化不是很频繁,而且首页访问量相对较大,所以我们有必要把首页接口数据缓存到redis缓存中,减少数据库压力和提高访问速度。
1)Spring Boot缓存注解
-缓存@Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
-缓存@CachePut
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
-缓存@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
2)引入springboot整合redis相关依赖
在common的pom.xml
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
在service-cms的application.properties
#redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database= 4 #数据库4
spring.redis.password=123456
spring.redis.timeout=1800000
3)创建redis缓存配置类,配置插件
在common创建RedisConfig.java
package com.atguigu.servicebase.handler;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* Redis插件配置类
*/
@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;
}
}
4)测试-@Cacheabl
在查询方法上添加redis缓存注解
//查询所有banner
@Cacheable(value = "banner",key = "'selectIndexList'")
@Override
public List<CrmBanner> selectAllBanner() {
//根据id进行降序排列,显示排列之后前两条记录
QueryWrapper<CrmBanner> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
//last方法,拼接sql语句
wrapper.last("limit 2");
List<CrmBanner> list = baseMapper.selectList(null);
return list;
}
访问首页后,查看Redis客户端
3.单点登录
3.1单点登录(SSO)-single sign on
1)介绍
用户登录一个模块,其他子模块自动登录。
优点 :
用户身份信息独立管理,更好的分布式管理。
可以自己扩展安全策略
缺点:
认证服务器访问压力较大。
2)实现单点登录的3种方式
第一种:session广播机制实现:session复制
缺点:模块少的话,可以一个一个复制,多个模块效率低。(session默认过期时间为30min)
第二种:使用cookie + redis实现
1.在项目中任何一个模块进行登录,登录之后,把数据放到redis和cookie种
- (1) redis:在key:生成唯一随机值(ip、用户id等等),在value:存放用户数据
- (2) cookie:把redis里面生成key值放到cookie里面
2.访间项目中其他模块,发送请求带着cookie进行发送,获取cookie值
(1)把cookie获取值,到redis进行查询,根据key进行查询,如果查询数据就是登录
第三种:使用token实现
token是按照一定规则生成字符串,字符串可以包含用户信息。
1.在项目某个模块进行登录,登录之后,按照规则生成字符串,把登录之后用户包含到生成字符串里面,把字符串返回
- (1)可以把字符串通过cookie返回
- (2)把字符串通过地址栏返回
2.再去访问项目其他模块,每次访问在地址栏带着生成的字符串,在访问模块里面获取地址栏字符串,根据字符串获取用户信息。如果可以获取到,就是登录。
3.2JWT
1)JWT介绍-Json web token
JWT就是给我们规定好了规则,使用jwt规则可以生成字符串,包含用户信息
JWT生成字符串包含三部分:
- 第一部分:jwt头信息
- 第二部分:有效载荷,包含主体信息(用户信息)
- 第三部分:签名,哈希防伪标志
2)JWT用法
JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。
服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。当跨域时,也可以将JWT被放置于POST请求的数据主体中。
3)整合JWT
-在common_utils模块中添加jwt工具依赖
<dependencies>
<!-- JWT-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
-创建JWT工具类
package com.atguigu.commonutils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
/**
* JWT 工具类
*
*/
public class JwtUtils {
//常量
public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥
//生成token字符串的方法
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id) //设置token主体部分 ,存储用户信息
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token字符串获取会员id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
4)实际使用
-第一次登录时,调用JwtUtils.getJwtToken,将用户信息存储在token
//登录的方法
@Override
public String login(UcenterMember member) {
//获取登录手机号和密码
String mobile = member.getMobile();
String password = member.getPassword();
//手机号和密码非空判断
if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
throw new GuliException(20001,"手机号和密码不能为空!");
}
//判断手机号是否正确
QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
wrapper.eq("mobile",mobile);
UcenterMember mobileMember = baseMapper.selectOne(wrapper);
//判断查询对象是否为空
if(mobileMember == null) {//没有这个手机号
throw new GuliException(20001,"手机号未注册!");
}
//判断密码
//因为存储到数据库密码肯定加密的
//把输入的密码进行 MD5加密 ,再和数据库密码进行比较
if(!MD5.encrypt(password).equals(mobileMember.getPassword())) {
throw new GuliException(20001,"密码错误!");
}
//判断用户是否禁用
if(mobileMember.getIsDisabled()) {
throw new GuliException(20001,"用户已禁用!");
}
//登录成功
//生成token字符串,使用jwt工具类
String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
return jwtToken;
}
-再次登录时,可在request获取用户信息
//根据token获取用户信息
@GetMapping("getMemberInfo")
public R getMemberInfo(HttpServletRequest request) {
//调用jwt工具类的方法。根据request对象获取头信息,返回用户id
String memberId = JwtUtils.getMemberIdByJwtToken(request);
//查询数据库根据用户id获取用户信息
UcenterMember member = memberService.getById(memberId);
return R.ok().data("userInfo",member);
}
5)登录的前端依赖
npm install element-ui
npm install vue-qriously
npm install js-cookie