1.项目结构
这个在created里面进行初始化,我们就可以进行选择。
这个后端方法我们在课程分类里面也就已经实现了。
①.首先喃我们要进行判断我们的路径里面是有有courseID 因为这样我们才知道你究竟是回显还是添加来做出不同 的逻辑
②.然后就是根据我们couseId 查询出对应的课程信息,包括一级分类id和二级分类id这都是我们需要的
然后在查询所有的学科分类(里面包含了我们一级分类和二级分类)我们需要根据courseID查询出来的数据进行 对比然后就可以得到我们回显数据里面那个一级分类所对应的二级分类。
①先看我们的vue封装的组件
②.删除视频就是根据我们的id来进行删除,删除多个也是如此(但是删除多个后端实现的方法有所不同)
③.进行路由跳转
这个前端没有什么新的技术,就是后端写了sql语句
①.这个其实就是基础的业务逻辑代码,我在课程分类那个笔记里面已经写了,就不重新进行赘述
为什么上传视频服务要使用微服务
首先我们上传视频的接口是写在新的模板里的,不是写在service-edu模板里面的,那么我们要想调用就只有使用微服务把他们都弄在一个微服务里面进行注册
那么我们nacos自然是不可少的
首先要引入依赖这里
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
然后就可以在我们要使用上传视频那个模块创建一个client
启动类的配置
下面就是调用,我们写的一个服务的接口
①.阿里云上是有基本的sdk的就是根据阿里云的帮助文档就可以实现,而且基本上都是固定的,就是需要你的keyID和你的keySercet 然后修改小部分进行实现
②.这里就是将我们上传后,然后就可以得到我们视频的id然后返回给前端
public String uploadAlyVideo(MultipartFile file) {
// MultipartFile 表示的是文件
// 得到文件的原始名称
String originalFilename = file.getOriginalFilename();
// title 在阿里云显示的名称(自己定义)
// 这个就是根据我们这个本身的文件名进行截取的
String title = originalFilename.substring(0, originalFilename.lastIndexOf("."));
// fileName 上传文件原始名称
String fileName = file.getOriginalFilename();
// inputStream 上传视频到阿里云里面
InputStream inputStream = null;
try {
// 得到输入流
inputStream = file.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
UploadStreamRequest request = new UploadStreamRequest(
keyid,
keysecret,
title, fileName, inputStream);
UploadVideoImpl uploader = new UploadVideoImpl();
UploadStreamResponse response = uploader.uploadStream(request);
//如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。
// 其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因
// 得到视频的id
String videoId = response.getVideoId();
if (!response.isSuccess()) {
String errorMessage = "阿里云上传错误:" + "code:" + response.getCode() + ", message:" + response.getMessage();
log.warn(errorMessage);
if(StringUtils.isEmpty(videoId)){
throw new GuliException(20001, errorMessage);
}
}
// 返回视频id
return videoId;
}
我们由于使用了nginx来实现调用的,但是nginx有一个限制,就是你上传的东西的内存,会经过nginx但是东西过大就不能到达后台,就给你拦截了,所以上传视频会受到限制,因此我们要对nginx的配置文件进行修改。
①.首先是删除单个视频的
那什么时候是删除多个视频喃 就是等你想要删除章节或者这个课程的话就是删除多个视频
public R removeAlyVideo(@PathVariable String id){
try {
// 初始化对象
DefaultAcsClient client = InitObject.initVodClient("LTAI5tQF4MgKKFNSbKbciX2j", "EScpFOFDJFHdQlL2YFQ35KFlLumK5S");
// 创建删除视频request对象
DeleteVideoRequest request = new DeleteVideoRequest();
// 向request设置id值
// 这个设置的id 就是我们删除视频的id 这个我们前端会传给我们的
// 这个
request.setVideoIds(id);
// 调用初始化实现的方法
// 实现删除
client.getAcsResponse(request);
return R.ok();
}catch (Exception e){
e.printStackTrace();
throw new GuliException(20001,"视频删除失败");
}
}
那么删除多个视频肯定区别不到就是我们不止一个id就是了但是 request.setVideoIds(id);有一个上传多个视频的格式
固定路由:就是一个很大概的意思,一般就是固定不变的访问地址 就比如首页,全部讲师,全部课程 这样的访问路径不会改变的。
动态路由:就是针对某一个东西,就比如查询每个老师的详情,那每个老师都有自己的详情,所以每个老师所对应的路径是有差别的,就相当于有自己的id。然后根据id来查询就可以了。
npm install axios
然后根据vue的特性,自己写一个request.js来实现我们的axios的封装
注意的是我们自己封装的与原来vue-element-tempate是有一点差别的是什么喃
就是我们不是后面都会使用箭头函数把 resp => { resp.data.data} 这个是nuxt的框架 我们原来vue自带的框架是自动帮助我们封装了一层data,所以原来我们写的vue的箭头函数就是 resp.data.这样,但是我们这个需要自己进行封装,但是不想封装的话就要使用两个data
然后就可以进行正常的调用了
三种登录方式
这个就相当于我们的steam令牌,需要这个令牌才能获取你的信息,然后生成对应的token来实现
至于怎么写出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;
/**
* @author helen
* @since 2019/10/16
*/
public class JwtUtils {
// 常量 过期时间
public static final long EXPIRE = 1000 * 60 * 60 * 24;
// 这个是我们密钥
// 这个一般都是公司会给的
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";
// 生成token的方法
// 根据用户id 和 用户名称生成的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) // 设置主体部分 存储用户信息
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET) // 声明hash
.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个方法我们直接使用就可以了。
我们将用户的密码存入数据库是不可能以一种明文的形式存入数据库的不然,会存在极大的安全隐患。
因此我们就可以使用MD5进行加密(md5只能加密,不能解密)
也是一个工具类,我们直接拿来用就可以了
首先我们需要开通腾讯云的短信服务,得到我们的id 和 key
然后根据腾讯云提供的sdk文档进行操作
首先你要自己写一个生成几位数的验证码的工具类
然后就是根据腾讯云提供的api进行复制然后修改为你的就行
@Override
public boolean sendSms(String phone, String code) {
try {
// 整合腾讯云短信服务发送
Credential cred = new Credential(secretID, secretKey);
// 实例化一个http选项,可选,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setReqMethod("POST");
/* SDK有默认的超时时间,非必要请不要进行调整
* 如有需要请在代码中查阅以获取最新的默认值 */
httpProfile.setConnTimeout(60);
/* 指定接入地域域名,默认就近地域接入域名为 sms.tencentcloudapi.com ,也支持指定地域域名访问,例如广州地域的域名为 sms.ap-guangzhou.tencentcloudapi.com */
httpProfile.setEndpoint("sms.tencentcloudapi.com");
/* 非必要步骤:
* 实例化一个客户端配置对象,可以指定超时时间等配置 */
ClientProfile clientProfile = new ClientProfile();
/* SDK默认用TC3-HMAC-SHA256进行签名
* 非必要请不要修改这个字段 */
clientProfile.setSignMethod("HmacSHA256");
clientProfile.setHttpProfile(httpProfile);
SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile);
/* 实例化一个请求对象,根据调用的接口和实际情况,可以进一步设置请求参数
* 你可以直接查询SDK源码确定接口有哪些属性可以设置
* 属性可能是基本类型,也可能引用了另一个数据结构
* 推荐使用IDE进行开发,可以方便的跳转查阅各个接口和数据结构的文档说明 */
SendSmsRequest req = new SendSmsRequest();
/* 填充请求参数,这里request对象的成员变量即对应接口的入参
* 你可以通过官网接口文档或跳转到request对象的定义处查看请求参数的定义
* 基本类型的设置:
* 帮助链接:
* 短信控制台: https://console.cloud.tencent.com/smsv2
* sms helper: https://cloud.tencent.com/document/product/382/3773 */
/* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */
String sdkAppId = "1400742926";
req.setSmsSdkAppId(sdkAppId);
/* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,签名信息可登录 [短信控制台] 查看 */
String signName = "林谋不萌"; // 这个必须写的
req.setSignName(signName);
/* 国际/港澳台短信 SenderId: 国内短信填空,默认未开通,如需开通请联系 [sms helper] */
String senderid = "";
req.setSenderId(senderid);
/* 用户的 session 内容: 可以携带用户侧 ID 等上下文信息,server 会原样返回 */
String sessionContext = "";
req.setSessionContext(sessionContext);
/* 短信号码扩展号: 默认未开通,如需开通请联系 [sms helper] */
String extendCode = "";
req.setExtendCode(extendCode);
/* 模板 ID: 必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看 */
String templateId = "1557106";
req.setTemplateId(templateId);
/* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
* 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机
号,最多不要超过200个手机号 */
//String[] phoneNumberSet = {"+8618777777777",
"+8615888888888","+8618555555555","+8618333333333","+8613566666666"};
String[] phoneNumberSet = {"+86" + phone};
req.setPhoneNumberSet(phoneNumberSet);
/* 模板参数: 若无模板参数,则设置为空 */
String[] templateParamSet = {code};
req.setTemplateParamSet(templateParamSet);
/* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
* 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */
SendSmsResponse res = client.SendSms(req);
// 输出json格式的字符串回包
System.out.println(SendSmsResponse.toJsonString(res));
// 也可以取出单个值,你可以通过官网接口文档或跳转到response对象的定义处查看返回字段 的定义
System.out.println(res.getSendStatusSet()[0].getCode());
if ("Ok".equals(res.getSendStatusSet()[0].getCode())) {
return true;
}
} catch (TencentCloudSDKException e) {
e.printStackTrace();
return false;
}
return false;
}
就可以发送成功了。
首先我们将phone作为key,验证码作为value然后存入redis里面,并且设置过期时长,等到用户输入验证码的时候,根据用户输入的phone获取里面的value(验证码) 然后进行比对,如果正确了则能进行注册。
这个就要使用到token了,我们的token就存储了他的id 和 昵称的信息
通过前端页面发来请求里面的请求体也就是我们的request 然后通过里面的token得到id
实现查询返回给前端
拦截器就是帮助我们把token设置在请求头上,以后我们每一次的请求里面都会有token
首先是要引入cookie,然后对cookie里面的值进行判断不然就会有读取为空,程序可能就有错
先来简单说明一下微信登录与密码登录的区别,我实现的这个喃就是,如果你是微信登录的话,那么你的token就会显示在首页的路径上面,我们正好通过路径来获取你的token,就不是通过后台返回了。
然后我们就可以得到这token,操作和我们登录的操作基本上是一样的。
然后我们前端获取路径中的值与以往vue获取的方式有一点不一样
将得到的token设置到cookie里面,然后又再次调用那个根据token查询用户信息的方法就可以实现了
首先我们要有微信开放平台的账号,要去申请,得到我们一些基本的密钥、id等
但是值的注意的是,我们这里不能使用restContrloler,因为我们是要重定向到某一个地址,而不是将这个地址返回给谁。
然后访问微信给出来的固定的地址
使用我们c语言的占位符的形式来实现参数的赋值
我们扫描二维码后 原本是该区 http://guli_shop/…什么那个地址
但是我们是没有办法实现的,于是尚硅谷就帮我写了一个程序扫描后是跳转到我们本地的一个地址,
然后我们手动添加逻辑就可以了
还需要将回调的地址与我们下面新的域名请求的地址要一样,这样就能自动执行到我们接口的方法
这个参数有code,和state我们也要加上
// 获取扫描人信息,添加数据
@GetMapping("callback")
public String callback(String code, String state) {
try {
// 1.code 获取code值 临时票据 类似于验证码
// 2.拿着code去请求微信固定的一个地址
// 得到access_token 和 openid
//向认证服务器发送请求换取access_token
String baseAccessTokenUrl =
"https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
String accessTokenUrl = String.format(
baseAccessTokenUrl,
ConstantWxUtils.WX_OPEN_APP_ID,
ConstantWxUtils.WX_OPEN_APP_SECRET,
code);
// accessTokenUrl 就是我们最终访问的地址
// 请求拼接好的值 得到那两个返回值
String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
// System.out.println("accessTokenInfo:" + accessTokenInfo);
// 从accessTokenInfo获取我们的open_id 和 access_token
// 先将我们的字符串转为map集合 根据map的key就可以得到
Gson gson = new Gson();
// 当然huuutoll也是建议使用的
HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
String accessToken = (String) mapAccessToken.get("access_token");
String openid = (String) mapAccessToken.get("openid");
// 判断数据库是否有相同的微信id openId
// 没有的话就查找得到用户信息再进行保存 如果存在了 就直接跳过
UcenterMember ucenterMember = ucenterMemberService.getOpenId(openid);
if(ucenterMember == null) {
// 3.拿着我们的 access_token 和 openid 去请求微信固定的地址 获取扫码人信息
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
// 拼接参数(就是我们的地址了)
String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openid);
// 发送请求
String userInfo = HttpClientUtils.get(userInfoUrl);
// 这个就是我们用户信息
// System.out.println("userInfo:" + userInfo);
// 获取返回userInfo扫码人的信息
HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
String openid1 = (String) userInfoMap.get("openid");
// 这个openid 和上面的openid是一样的
// 昵称
String nickname = (String) userInfoMap.get("nickname");
// 头像
String headimgurl = (String) userInfoMap.get("headimgurl");
// 帮助用户注册
// 表示新用户
// 进行添加
ucenterMember = new UcenterMember();
ucenterMember.setOpenid(openid);
ucenterMember.setNickname(nickname);
ucenterMember.setAvatar(headimgurl);
ucenterMemberService.save(ucenterMember);
}
// 使用jwt 根据member对象生成一个token字符串
String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(),
ucenterMember.getNickname());
// 然后就跳转到我们的前端的页面
// 所以说为什么我们前端是query.token喃
// 将token放在路径里面,然后进行重定向
return "redirect:http://localhost:3000?token=" + jwtToken;
} catch (Exception e) {
e.printStackTrace();
throw new GuliException(20001,"登陆失败");
}
}
这样我们扫码登录也就实现了