当前位置: 首页 > article >正文

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

文章目录

  • 前言
  • 回顾
  • 完成任务
    • 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)。这里我直接粘贴这个类,便于以后使用:

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;
        }

        @Override
        public 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;
        }

        @Override
        public 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 发送给客户端。
    ·这个读取文件的操作过程,我也直接粘贴,便于以后使用。(主要就是通过文件输入流和文件输出流)
    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.kler.cn/a/572353.html

相关文章:

  • ubuntu24.04-系统重装
  • 无人机陀螺仪原理与算法详解!
  • Android中使用Robolectric测试点击事件(不需要手机)
  • 智慧隧道:城市升级改造的地下动脉——塔能物联运维的核心驱动
  • 物联网数据中台 数据采集器 边缘盒子三者之间应用思考点
  • 【嵌入式】MQTT
  • python GUI之实现一个自定义的范围滑块控件:QRangeSlider
  • C++ 变量的输入输出教程
  • 进阶篇——深入解析数据库事务与锁机制:从原理到实战优化
  • 16.1STM32_ADC
  • C/C++跨平台SDK开发的注意事项
  • C# Unity 唐老狮 No.4 模拟面试题
  • C# 基础知识总结(持续更新中...)
  • 【线性代数的理解】 为什么说线性代数研究的是空间变换?旋转矩阵坐标转换矩阵
  • Dify部署-(零基础)(个人体验)(Linux)(白嫖)(可部署大模型)
  • MongoDB 查询语句详解:以 `db.fs.files.find().sort({ _id: -1 }).limit(10)` 为例
  • 期权适合什么类型的投资者交易?
  • Stable Diffusion模型高清算法模型类详解
  • 碰一碰发视频系统技术开发,支持OEM
  • es检索elasticsearch检索curl实现