【easy视频 | day02】管理端登录校验 + 分类管理 + 文件上传

news/2025/3/4 20:20:04/

文章目录

  • 前言
  • 回顾
  • 完成任务
    • 1. 管理端登录
      • 登录校验
    • 2. 分类管理
      • 2.1 分类列表
      • 2.2 保存分类
      • 2.3 删除分类
      • 2.4 改变排序
      • 2.5 刷新缓存
    • 3. 文件上传
      • 3.1 上传图片
      • 3.2 获取图片资源

在这里插入图片描述

前言

本项目非原创,我只是个小小白,跟随 b 站脚步,找到老罗的这个项目,视频来源于:
高仿B站(单服务版) springboot项目实战 easylive

本人不分享项目源码,支持项目付费!!!

回顾

昨天完成了用户端的登录注册功能。成功登入界面后,界面上应该会出现视频的分类,而视频的分类是由管理员来处理的,所以要先进行管理端的相关操作。

完成任务

1. 管理端登录

controller 层:
在这里插入图片描述
管理端登录其实与客户端相差不大,都需要将 token 存入 Redis,将 token 存入 Cookie,成功登录后也同样需要删除 Redis 中的验证码(管理端获取验证码操作与客户端一样),删除 Redis 中的 token。
在这里插入图片描述
注意:管理端和客户端不能使用相同的 token 字段,要区分开。

登录校验

要进入管理后台,进行一系列操作的前提都是必须先登录。如果没有登录,就直接访问管理端后台的地址,这是不允许的。所以,需要使用拦截器
– 客户端的登录校验的差异性比较大,直接访问页面可能不需要登录,点赞、评论这些才需要登录,所以,到时候用 AOP 来实现。

设计一个配置类来配置 Spring MVC 的相关行为:
在这里插入图片描述

实现了 WebMvcConfigurer 接口,该接口提供了多种用于配置Spring MVC的回调方法。
appIntercepter 实例为拦截器,拦截所有路径下的请求。

AppIntercepter 类,用于拦截请求并执行预处理逻辑:
在这里插入图片描述

  • HandlerInterceptor 接口:是Spring MVC提供用于处理请求拦截的。
  • if (!(handler instanceof HandlerMethod)) 条件为真的话,为什么返回 true ?——> 检查handler是否为HandlerMethod的实例,如果不是,允许请求继续处理。意味着请求不是针对Controller方法的,可能是静态资源请求
  • 在前面的配置类中,我们要求的拦截的是所有路径,但实际上,一开始的获取验证码和登录都不应该拦截。所以,对于 URI 中包含 “/account” 的请求,要允许继续处理。
  • 针对文件的请求,可能需要从 Cookie 中获取 token,因为文件请求可能不带 head 头,无法通过 getHeader 获取 token。
  • 如果 Redis 根据 token 获取不到用户信息,说明 token 失效(登录超时)。

当页面关闭后,再次打开管理端登录页面,要保证需要进行重新登录,而不是还能继续访问关闭之前的页面。对 token 保存到 Cookie 中时,设置过期时间为 -1
在这里插入图片描述
为什么设置为 -1 ? 因为这样设置,token 的过期时间会表示为 “会话”。也就是说只有在当前会话中才能继续访问,换一个会话(页面),token 就会失效:
在这里插入图片描述

2. 分类管理

2.1 分类列表

controller 层:
在这里插入图片描述
根据排序号 sort 升序排序,并需要将查询到的扁平化的分类数据转为树的形式。因为分类由一级分类和二级分类。
service 层:
在这里插入图片描述
通过递归调用 convertLine2Tree() 方法,逐层构建每个分类的子树,更好地表示层级关系。
例如,原本查询的数据应该为:

[{"categoryId": 1, "pCategoryId": 0, "name": "一级分类A", "children": null},{"categoryId": 2, "pCategoryId": 1, "name": "二级分类A-1", "children": null},{"categoryId": 3, "pCategoryId": 1, "name": "二级分类A-2", "children": null},
]

转为树形结构:

[{"categoryId": 1,"pCategoryId": 0,"name": "一级分类A","children": [{"categoryId": 2,"pCategoryId": 1,"name": "二级分类A-1","children": []},{"categoryId": 3,"pCategoryId": 1,"name": "二级分类A-2","children": []}]}
]

2.2 保存分类

controller 层:
在这里插入图片描述
service 层:
在这里插入图片描述
根据分类编号查询数据库,分析分类编号已存在的情况(分类编号必须是唯一的),并抛出异常。
分类 ID 如果不存在,查询表中最大的排序号,将这个最大的排序号+1后作为该分类的排序号;如果分类 ID 已经存在,说明是进行分类的修改。

2.3 删除分类

controller 层:直接调用 service 层实现
在这里插入图片描述
service 层:
在这里插入图片描述
删除的时候,因为分类是有两级的,如果一级分类下的 A 删除,那么 A 下的二级分类 B、C … 也应该删除。
所以,某分类的父级分类的 ID 是 categoryId 的话,也需要进行对其进行删除。

2.4 改变排序

controller 层:
在这里插入图片描述
service 层:
在这里插入图片描述
获取的分类 ID 参数 categoryIds 是 String 类型,为每个对象设置一个排序号,调用 mapper 中的方法批量更新每个分类对应的排序。

2.5 刷新缓存

在每次完成对数据的修改,无论是新增分类、删除分类、还是改变排序,最后都会进行 save2Redis() 的操作,这个操作是用来刷新缓存。将最新的数据同步到 Redis 缓存中。
在这里插入图片描述

3. 文件上传

3.1 上传图片

在保存分类的时候,有两个可选项是上传图标和背景图:
在这里插入图片描述
这里就涉及文件上传。

controller 层,上传图片:
在这里插入图片描述
区分月份来保存文件,将要上传的文件通过 transferTo() 方法保存到新的文件路径下。
根据传递的参数,判断是否要生成缩略图,如果为 true,就要通过 ffmpegUtils 工具类的 createImageThumbnail() 方法生成缩略图:
在这里插入图片描述
前提是,电脑上必须下载配置 FFmpeg。配置成功在电脑中 cmd,输入 “ffmpeg -version” ,应该会出现如下样式:
在这里插入图片描述

生成缩略图的方法中还需要 ProcessUtils 工具类来执行 executeCommand 方法,安全地在不同操作系统上执行外部命令(特别是 FFmpeg)。这里我直接粘贴这个类,便于以后使用:

java">public class ProcessUtils {private static final Logger logger = LoggerFactory.getLogger(ProcessUtils.class);private static final String osName = System.getProperty("os.name").toLowerCase();public static String executeCommand(String cmd, Boolean showLog) throws BusinessException {if (StringTools.isEmpty(cmd)) {return null;}Runtime runtime = Runtime.getRuntime();Process process = null;try {//判断操作系统if (osName.contains("win")) {process = Runtime.getRuntime().exec(cmd);} else {process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});}// 执行ffmpeg指令// 取出输出流和错误流的信息// 注意:必须要取出ffmpeg在执行命令过程中产生的输出信息,如果不取的话当输出流信息填满jvm存储输出留信息的缓冲区时,线程就回阻塞住PrintStream errorStream = new PrintStream(process.getErrorStream());PrintStream inputStream = new PrintStream(process.getInputStream());errorStream.start();inputStream.start();// 等待ffmpeg命令执行完process.waitFor();// 获取执行结果字符串String result = errorStream.stringBuffer.append(inputStream.stringBuffer + "\n").toString();// 输出执行的命令信息if (showLog) {logger.info("执行命令{}结果{}", cmd, result);}return result;} catch (Exception e) {logger.error("执行命令失败cmd{}失败:{} ", cmd, e.getMessage());throw new BusinessException("视频转换失败");} finally {if (null != process) {ProcessKiller ffmpegKiller = new ProcessKiller(process);runtime.addShutdownHook(ffmpegKiller);}}}/*** 在程序退出前结束已有的FFmpeg进程*/private static class ProcessKiller extends Thread {private Process process;public ProcessKiller(Process process) {this.process = process;}@Overridepublic void run() {this.process.destroy();}}/*** 用于取出ffmpeg线程执行过程中产生的各种输出和错误流的信息*/static class PrintStream extends Thread {InputStream inputStream = null;BufferedReader bufferedReader = null;StringBuffer stringBuffer = new StringBuffer();public PrintStream(InputStream inputStream) {this.inputStream = inputStream;}@Overridepublic void run() {try {if (null == inputStream) {return;}bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String line = null;while ((line = bufferedReader.readLine()) != null) {stringBuffer.append(line);}} catch (Exception e) {logger.error("读取输入流出错了!错误信息:" + e.getMessage());} finally {try {if (null != bufferedReader) {bufferedReader.close();}if (null != inputStream) {inputStream.close();}} catch (IOException e) {logger.error("调用PrintStream读取输出流后,关闭流时出错!");}}}}
}

3.2 获取图片资源

在这里插入图片描述

  • 先判断 sourceName 是否是一个有效的路径,通过 pathIsOk() :这里主要是为了阻止访问比当前路径层级更高的目录
    在这里插入图片描述
  • 设置正确的响应内容类型缓存控制头,这里的缓存时间设置为 30 天,意味着客户端可以缓存该资源30天,不需要每次都向服务器请求
  • 调用 readFile 方法,根据 sourceName 找到对应的文件,并将其内容读取出来,然后通过 HttpServletResponse 发送给客户端。
    ·这个读取文件的操作过程,我也直接粘贴,便于以后使用。(主要就是通过文件输入流和文件输出流)
    java">protected void readFile(HttpServletResponse response, String filePath) {File file = new File(appConfig.getProjectFolder() + Constants.FILE_FOLDER + filePath);if (!file.exists()) {return;}try (OutputStream out = response.getOutputStream(); FileInputStream in = new FileInputStream(file)) {byte[] byteData = new byte[1024];int len = 0;while ((len = in.read(byteData)) != -1) {out.write(byteData, 0, len);}out.flush();} catch (Exception e) {log.error("读取文件异常", e);}}
    

http://www.ppmy.cn/news/1576633.html

相关文章

共轭梯度法笔记

一、梯度下降法 x k 1 x k − α ∇ f ( x k ) x_{k1} x_k - \alpha \nabla f(x_k) xk1​xk​−α∇f(xk​) 这是普通的梯度下降公式,有两个量是关键,步长 α \alpha α和方向 ∇ f ( x k ) \nabla f(x_k) ∇f(xk​)。这里的方向直接选择了梯度方向&…

004build在设计页面上的使用

004_a_StatefulWidget和State的语法结构_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1RZ421p7BL?spm_id_from333.788.videopod.episodes&vd_source68aea1c1d33b45ca3285a52d4ef7365f&p152 StatelessWidget无状态小部件不能setState刷新build import packag…

CentOS 7 中安装 Docker和Docker Compose

本文介绍CentOS7系统安装Docker和Docker Compose的完整操作步骤 一、环境准备‌ 验证系统内核版本:uname -r(需≥3.10) 更新系统软件包:sudo yum update -y‌ 二、卸载旧版本 sudo yum remove docker \ docker-client \ docke…

赛博算命之 ”梅花易数“ 的 “JAVA“ 实现 ——从玄学到科学的探索

hello~朋友们!好久不见! 今天给大家带来赛博算命第三期——梅花易数的java实现 赛博算命系列文章: 周易六十四卦 掐指一算——小六壬 更多优质文章:个人主页 JAVA系列:JAVA 大佬们互三哦~互三必回!&#xf…

爬虫系列之发送请求与响应《一》

一、请求组成 1.1 请求方式:GET和POST请求 GET:从服务器获取,请求参数直接附在URL之后,便于查看和分享,常用于获取数据和查询操作 POST:用于向服务器提交数据,其参数不会显示在URL中,而是包含在…

迷你世界脚本玩家接口:Player

玩家接口:Player 彼得兔 更新时间: 2024-07-28 17:49:05 继承自 Actor 具体函数名及描述如下: 序号 函数名 函数描述 1 getAttr(...) 玩家属性获取 2 setAttr(...) 玩家属性设置 3 getHostUin(...) 获取房主uin 4 isMainPlayer(...) …

【AI深度学习基础】NumPy完全指南进阶篇:核心功能与工程实践(含完整代码)

NumPy系列文章 入门篇进阶篇终极篇 一、引言 在掌握NumPy基础操作后,开发者常面临真实工程场景中的三大挑战:如何优雅地处理高维数据交互?如何在大规模计算中实现内存与性能的平衡?怎样与深度学习框架实现高效协同?…

行为型模式 - 观察者模式 (Publish/Subscribe)

行为型模式 - 观察者模式 (Publish/Subscribe) 又称作为订阅发布模式(Publish-Subscribe Pattern)是一种消息传递模式,在该模式中,发送者(发布者)不会直接将消息发送给特定的接收者(订阅者&…