1. 先从菜单栏下的视频转码list页面开始。
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <th:block th:include="include :: header('视频转码列表')" />
</head>
<body class="gray-bg">
     <div class="container-div">
        <div class="row">
            <div class="col-sm-12 search-collapse">
                <form id="formId">
                    <div class="select-list">
                        <ul>
                            <li>
                                <p>标题:</p>
                                <input type="text" name="videoTitle"/>
                            </li>
                            <li>
                                <p>上传者:</p>
                                <input type="text" name="videoWriter"/>
                            </li>
                            <li>
                                <p>文化程度:</p>
                                <select name="videoGrade" th:with="type=${@dict.getType('zyk_video_grade')}">
                                    <option value="">所有</option>
                                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
                                </select>
                            </li>
                            <li>
                                <p>视频地址:</p>
                                <input type="text" name="videoUrl"/>
                            </li>
                            <li class="select-time">
                                <p>上传时间:</p>
                                <input type="text" class="time-input" id="startTime" placeholder="开始时间" name="params[beginVideoTime]"/>
                                <span>-</span>
                                <input type="text" class="time-input" id="endTime" placeholder="结束时间" name="params[endVideoTime]"/>
                            </li>
                            <li>
                                <p>视频封面地址:</p>
                                <input type="text" name="videoPicurl"/>
                            </li>
                            <li>
                                <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i> 搜索</a>
                                <a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i> 重置</a>
                            </li>
                        </ul>
                    </div>
                </form>
            </div>

            <div class="btn-group-sm" id="toolbar" role="group">

                <a class="btn btn-success" onclick="add()" shiro:hasPermission="zyk:video:add">
                    <i class="fa fa-plus"></i> 添加
                </a>
                <a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="zyk:video:edit">
                    <i class="fa fa-edit"></i> 修改
                </a>
                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="zyk:video:remove">
                    <i class="fa fa-remove"></i> 删除
                </a>
                <a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="zyk:video:export">
                    <i class="fa fa-download"></i> 导出
                 </a>
            </div>
            <div class="col-sm-12 select-table table-striped">
                <table id="bootstrap-table"></table>
            </div>
        </div>
    </div>
    <th:block th:include="include :: footer" />
    <script th:inline="javascript">
        var editFlag = [[${@permission.hasPermi('zyk:video:edit')}]];
        var removeFlag = [[${@permission.hasPermi('zyk:video:remove')}]];
        var videoGradeDatas = [[${@dict.getType('zyk_video_grade')}]];
        var prefix = ctx + "zyk/video";

        $(function() {

            var options = {
                url: prefix + "/list",
                createUrl: prefix + "/add",
                updateUrl: prefix + "/edit/{id}",
                removeUrl: prefix + "/remove",
                exportUrl: prefix + "/export",
                modalName: "视频转码",
                columns: [{
                    checkbox: true
                },
                {
                    field : 'videoId', 
                    title : '主键id',
                    visible: false
                },
                {
                    field : 'videoTitle', 
                    title : '标题'
                },
                {
                    field : 'videoWriter', 
                    title : '上传者'
                },
                {
                    field : 'videoGrade', 
                    title : '文化程度',
                    formatter: function(value, row, index) {
                       return $.table.selectDictLabel(videoGradeDatas, value);
                    }
                },
                {
                    field : 'videoUrl', 
                    title : '视频地址',
                    width :200,
                    height:150,
                    formatter: function(value, row, index) {
                        if(value==''||value ==null||value =="other_default.jpg"){

                            return '<img src="/img/other_default.jpg" width="50" height="50">';
                        }else{
                            return  '<video  src="http://192.168.2.195:8091/profile/'+value+'" width="100" height="50"  controls/>';

                        }
                    }
                },
                {
                        field : 'videoTimecount',
                        title : '视频时长'
                },
                {
                    field : 'videoTime', 
                    title : '上传时间'
                },
                {
                    field : 'videoPicurl', 
                    title : '视频封面地址',
                    formatter: function(value, row, index) {
                        if(value==''||value ==null||value =="other_default.jpg"){
                            return '<img src="/img/other_default.jpg" width="50" height="50">';
                        }else{
                            return '<img src="/profile/' + value + '" width="50" height="50">';

                        }
                    }
                },
                {
                    title: '操作',
                    align: 'center',
                    formatter: function(value, row, index) {
                        var actions = [];
                        actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.videoId + '\')"><i class="fa fa-edit"></i>编辑</a> ');
                        actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.videoId + '\')"><i class="fa fa-remove"></i>删除</a>');
                        return actions.join('');
                    }
                }]
            };
            $.table.init(options);
        });

        /**
         * 预览资源
         */
        function add() {
                        var index = layer.open({
                            type: 2,
                            title: '视频上传转码:',
                            area: ['1200px', '650px'], //宽高
                            fix: false, //不固定
                            maxmin: true,
                            content: '/zyk/video/add1'
                        });
                        this.layerIndex = index;
               };
    </script>
</body>
</html>

以上是首页列表的页面代码,你可以分析一下并借鉴对你的项目有用的代码!

  1. 现在开始重点说一下添加视频并后台转码
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
    <th:block th:include="include :: header('文件上传')" />
    <th:block th:include="include :: bootstrap-fileinput-css" />
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content animated fadeInRight">
    <div class="row">
        <div class="col-sm-12">
            <div class="ibox float-e-margins">
                <div class="ibox-title">
                    <h5>新增资源视频转码</h5>
                </div>
                <div class="ibox-content">

                    <div class="container-fluid">
                        <form id="form" action="/zyk/video/upload" method="post" enctype="multipart/form-data">
                            <div class="row form-group">
                                <label class="col-md-4">上传视频:</label>
                                <div class="col-sm-12">
                                    <input id="input-id" name="file" multiple type="file" data-show-caption="true">
                                </div>

                            </div>
                        </form>
                        <button type="button" class="btn btn-danger" icon="fa-eraser" onclick="ds()"><i class="fa fa-close"></i>取消</button>
                    </div>
                    <hr>
                </div>
            </div>
        </div>
    </div>
</div>
<th:block th:include="include :: footer" />
<th:block th:include="include :: bootstrap-fileinput-js" />
<script>
    /**
     * 关闭此对话框
     */
    function ds() {
        var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
        parent.layer.close(index); //再执行关闭
        window.location.href="/zyk/video";
    }
</script>
<script type="text/javascript">
    $(function () {
        initFileInput("input-id");

    })

    function initFileInput(ctrlName) {
        var control = $('#' + ctrlName);
        control.fileinput({
            language: 'zh', //设置语言
            uploadUrl: "/zyk/video/upload", //上传的地址
            allowedFileExtensions: ['jpg', 'gif', 'png','mp4'],//接收的文件后缀
            //uploadExtraData:{"id": 1, "fileName":'123.mp3'},
            uploadAsync: true, //默认异步上传
            showUpload: true, //是否显示上传按钮
            showRemove : true, //显示移除按钮
            showPreview : true, //是否显示预览
            showCaption: false,//是否显示标题
            browseClass: "btn btn-primary", //按钮样式
            dropZoneEnabled: true,//是否显示拖拽区域
            //minImageWidth: 50, //图片的最小宽度
            //minImageHeight: 50,//图片的最小高度
            //maxImageWidth: 1000,//图片的最大宽度
            //maxImageHeight: 1000,//图片的最大高度
            //maxFileSize: 0,//单位为kb,如果为0表示不限制文件大小
            //minFileCount: 0,
            //maxFileCount: 10, //表示允许同时上传的最大文件个数
            enctype: 'multipart/form-data',
            validateInitialCount:true,
            previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
            msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}!",

        }).on('filepreupload', function(event, data, previewId, index) {
            //上传中
            var form = data.form,
                files = data.files,
                extra = data.extra,
                response = data.response,
                reader = data.reader;
            console.log('文件正在上传');
        }).on("fileuploaded", function (event, data, previewId, index) {
            //一个文件上传成功
            console.log('文件上传成功!'+data);

        }).on('fileerror', function(event, data, msg) {
            //一个文件上传失败
            console.log('文件上传失败!'+data);
        })
    }
    function initFileInput1(ctrlName) {
        var control = $('#' + ctrlName);
        control.fileinput({
            language: 'zh', //设置语言
            uploadUrl: "/zyk/video/upload", //上传的地址
            allowedFileExtensions: ['jpg', 'gif', 'png','mp4'],//接收的文件后缀
            //uploadExtraData:{"id": 1, "fileName":'123.mp3'},
            uploadAsync: true, //默认异步上传
            showUpload: true, //是否显示上传按钮
            showRemove : true, //显示移除按钮
            showPreview : true, //是否显示预览
            showCaption: false,//是否显示标题
            browseClass: "btn btn-primary", //按钮样式
            dropZoneEnabled: true,//是否显示拖拽区域
            //minImageWidth: 50, //图片的最小宽度
            //minImageHeight: 50,//图片的最小高度
            //maxImageWidth: 1000,//图片的最大宽度
            //maxImageHeight: 1000,//图片的最大高度
            //maxFileSize: 0,//单位为kb,如果为0表示不限制文件大小
            //minFileCount: 0,
            //maxFileCount: 10, //表示允许同时上传的最大文件个数
            enctype: 'multipart/form-data',
            validateInitialCount:true,
            previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
            msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}!",

        }).on('filepreupload', function(event, data, previewId, index) {
            //上传中
            var form = data.form,
                files = data.files,
                extra = data.extra,
                response = data.response,
                reader = data.reader;
            console.log('文件正在上传');
        }).on("fileuploaded", function (event, data, previewId, index) {
            //一个文件上传成功
            console.log('文件上传成功!'+data);

        }).on('fileerror', function(event, data, msg) {
            //一个文件上传失败
            console.log('文件上传失败!'+data);
        })
    }
    function initFileInput2(ctrlName) {
        var control = $('#' + ctrlName);
        control.fileinput({
            language: 'zh', //设置语言
            uploadUrl: "/zyk/video/upload", //上传的地址
            allowedFileExtensions: ['jpg', 'gif', 'png','mp4'],//接收的文件后缀
            //uploadExtraData:{"id": 1, "fileName":'123.mp3'},
            uploadAsync: true, //默认异步上传
            showUpload: true, //是否显示上传按钮
            showRemove : true, //显示移除按钮
            showPreview : true, //是否显示预览
            showCaption: false,//是否显示标题
            browseClass: "btn btn-primary", //按钮样式
            dropZoneEnabled: true,//是否显示拖拽区域
            //minImageWidth: 50, //图片的最小宽度
            //minImageHeight: 50,//图片的最小高度
            //maxImageWidth: 1000,//图片的最大宽度
            //maxImageHeight: 1000,//图片的最大高度
            //maxFileSize: 0,//单位为kb,如果为0表示不限制文件大小
            //minFileCount: 0,
            //maxFileCount: 10, //表示允许同时上传的最大文件个数
            enctype: 'multipart/form-data',
            validateInitialCount:true,
            previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
            msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}!",

        }).on('filepreupload', function(event, data, previewId, index) {
            //上传中
            var form = data.form,
                files = data.files,
                extra = data.extra,
                response = data.response,
                reader = data.reader;
            console.log('文件正在上传');
        }).on("fileuploaded", function (event, data, previewId, index) {
            //一个文件上传成功
            console.log('文件上传成功!'+data);

        }).on('fileerror', function(event, data, msg) {
            //一个文件上传失败
            console.log('文件上传失败!'+data);
        })
    }
</script>
</body>
</html>

上面是添加视频页面,这里面有webuploader的百度上传图片或者视频插件,需要引入与它有关的js,不知道可以百度一下就知道了,这个用法在上面的注释中是不是已经很详细了。

  1. 看到上面的add页面中有上传的路径,那就说明在页面上添加完视频后就要提交的,那下面就是controller中接口的实现。
package com.ruoyi.system.controller;

import java.io.*;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSONObject;
import com.ruoyi.system.tool.ConverVideoTest;
import com.ruoyi.system.tool.FfmpegTest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.ZykVideo;
import com.ruoyi.system.service.IZykVideoService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 视频转码Controller
 * 
 * @author yuliujun
 * @date 2019-10-30
 */
@Controller
@RequestMapping("/zyk/video")
public class ZykVideoController extends BaseController
{
    private String prefix = "zyk/video";

    @Autowired
    private IZykVideoService zykVideoService;
    Map<String,String> map = new HashMap<>();

    /**
     * @Description:(视频资源的单独上传的接收)
     * @param:@param request
     * @param:@param response
     * @param:@param session
     * @param:@return
     * @version:V1.0
     */
    @RequestMapping(value = "/upload",method =RequestMethod.POST)
    @ResponseBody       //@RequestPart("file") MultipartFile file,
    public JSONObject uploadflie_Video(@RequestParam(name = "file",required = false) MultipartFile file, HttpServletResponse req, HttpServletRequest request) {
        System.out.println("进入addVideo视频上传控制层");
        String NewVideopath =null;
        String filename2 =null;
        if (file.getSize() != 0) {
            //上传的多格式的视频文件-作为临时路径保存,转码以后删除-路径不能写//
            String path = "E://javaEdit//temp/";

            File TempFile = new File(path);
            if (TempFile.exists()) {
                if (TempFile.isDirectory()) {
                    System.out.println("该文件夹存在!!!!!!!!!!!!!!!!");
                }else {
                    System.out.println("同名的文件存在,不能创建文件夹。");
                }
            }else {
                System.out.println("文件夹不存在,创建该文件夹。");
                TempFile.mkdir();
            }

            // 获取上传时候的文件名
            String filename = file.getOriginalFilename();

            // 获取文件后缀名
            String filename_extension = filename.substring(filename.lastIndexOf(".") + 1);
            System.out.println("视频的后缀名:"+filename_extension);

            //时间戳做新的文件名,避免中文乱码-重新生成filename
            long filename1 = new  Date().getTime();
            filename = Long.toString(filename1)+"."+filename_extension;

            //去掉后缀的文件名
            filename2 = filename.substring(0, filename.lastIndexOf("."));
            System.out.println("视频名为:"+filename2);

            //源视频地址+重命名后的视频名+视频后缀
            String yuanPATH =(path+filename);

            System.out.println("视频的完整文件名1:"+filename);
            System.out.println("源视频路径为:"+yuanPATH);

            //上传到本地磁盘/服务器
            try {
                System.out.println("写入本地磁盘/服务器");
                InputStream is = file.getInputStream();
                OutputStream os = new FileOutputStream(new File(path, filename));
                int len = 0;
                byte[] buffer = new byte[2048];

                while ((len = is.read(buffer)) != -1) {
                    os.write(buffer, 0, len);
                }
                os.close();
                os.flush();
                is.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println("========上传完成,开始调用转码工具类=======");
            //调用转码机制flv mp4 f4v m3u8 webm ogg放行直接播放,
            //asx,asf,mpg,wmv,3gp,mov,avi,wmv9,rm,rmvb等进行其他转码为mp4

            if (filename_extension.equals("avi") || filename_extension.equals("rm")
                    || filename_extension.equals("rmvb") || filename_extension.equals("wmv")
                    || filename_extension.equals("3gp")  || filename_extension.equals("mov")
                    ||filename_extension.equals("flv")   || filename_extension.equals("ogg")
                    ||filename_extension.equals("mp4")

            ) {
                ConverVideoTest c = new ConverVideoTest();
                c.run(yuanPATH);   //调用转码

                System.out.println("=================转码过程彻底结束=====================");
            }

            //获取转码后的mp4文件名
            String Mp4path = "D://ruoyi/uploadPath//";
            filename2 = filename2+".mp4";
            NewVideopath =Mp4path +filename2;
            System.out.println("新视频的url:"+NewVideopath);
            FfmpegTest ffmpegTest = new FfmpegTest();
            map = ffmpegTest.getVideoTime(NewVideopath);
            System.out.println("视频时间长度======================:"+map.get("time"));
            //删除临时文件
            File file2 = new File(path);
            if (!file2.exists()) {
                System.out.println("没有该文件");
            }
            if (!file2.isDirectory()) {
                System.out.println("没有该文件夹");
            }
            String[] tempList = file2.list();
            File temp = null;
            for (int i = 0; i < tempList.length; i++) {
                if (path.endsWith(File.separator)) {
                    temp = new File(path + tempList[i]);
                } else {
                    temp = new File(path + File.separator + tempList[i]);
                }
                if (temp.isFile() || temp.isDirectory()) {
                    temp.delete();		//删除文件夹里面的文件
                }
            }
            System.out.println("所有的临时视频文件删除成功");
            //已转码后的视频存放地址NewVideopath
        }
        JSONObject json = new JSONObject();
        ZykVideo zykVideo = new ZykVideo();
        zykVideo.setVideoPicurl(filename2.substring(0, filename2.lastIndexOf("."))+".jpg");
        zykVideo.setVideoTimecount(map.get("time"));
        zykVideo.setVideoTitle(file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf(".")));
        zykVideo.setVideoUrl(filename2);
        zykVideoService.insertZykVideo(zykVideo);



        json.put("code",200);
        return json;
    }





    @RequiresPermissions("zyk:video:view")
    @GetMapping()
    public String video()
    {
        return prefix + "/video";
    }

    /**
     * 查询视频转码列表
     */
    @RequiresPermissions("zyk:video:list")
    @PostMapping("/list")
    @ResponseBody
    public TableDataInfo list(ZykVideo zykVideo)
    {
        startPage();
        List<ZykVideo> list = zykVideoService.selectZykVideoList(zykVideo);
        return getDataTable(list);
    }

    /**
     * 导出视频转码列表
     */
    @RequiresPermissions("zyk:video:export")
    @PostMapping("/export")
    @ResponseBody
    public AjaxResult export(ZykVideo zykVideo)
    {
        List<ZykVideo> list = zykVideoService.selectZykVideoList(zykVideo);
        ExcelUtil<ZykVideo> util = new ExcelUtil<ZykVideo>(ZykVideo.class);
        return util.exportExcel(list, "video");
    }

   
    /**
     * 新增视频转码
     */
    @GetMapping("/add1")
    public String add1() {
        System.out.println("请求进入接口中==========================");
        return prefix + "/addvideo";
    }
    /**
     * 新增保存视频转码
     */
    @RequiresPermissions("zyk:video:add")
    @Log(title = "视频转码", businessType = BusinessType.INSERT)
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(ZykVideo zykVideo)
    {
        return toAjax(zykVideoService.insertZykVideo(zykVideo));
    }

    /**
     * 修改视频转码
     */
    @GetMapping("/edit/{videoId}")
    public String edit(@PathVariable("videoId") Long videoId, ModelMap mmap)
    {
        ZykVideo zykVideo = zykVideoService.selectZykVideoById(videoId);
        mmap.put("zykVideo", zykVideo);
        return prefix + "/edit";
    }

    /**
     * 修改保存视频转码
     */
    @RequiresPermissions("zyk:video:edit")
    @Log(title = "视频转码", businessType = BusinessType.UPDATE)
    @PostMapping("/edit")
    @ResponseBody
    public AjaxResult editSave(ZykVideo zykVideo)
    {
        return toAjax(zykVideoService.updateZykVideo(zykVideo));
    }

    /**
     * 删除视频转码
     */
    @RequiresPermissions("zyk:video:remove")
    @Log(title = "视频转码", businessType = BusinessType.DELETE)
    @PostMapping( "/remove")
    @ResponseBody
    public AjaxResult remove(String ids) {
        System.out.println("删除功能接收的ids"+ids);
        String[] arr = ids.split(",");
        for (int i = 0; i < arr.length ; i++) {
            System.out.println("遍历的每个id:=================="+arr[i]);
           ZykVideo zykVideo =  zykVideoService.selectZykVideoById((long)Integer.parseInt(arr[i]));
           File file = new File("D://ruoyi/uploadPath//"+zykVideo.getVideoUrl());
           file.delete();
           File file1 = new File("D://ruoyi/uploadPath//"+zykVideo.getVideoPicurl());
           file1.delete();
        }
        return toAjax(zykVideoService.deleteZykVideoByIds(ids));
    }
}

不要急,上面的代码中第一个接口中upload中需要转码工具类和需要用到ffmpeg转码工具和mencoder转码工具,其实就是两个exe执行文件,关于ffmpeg怎么实现转码,你要去看一下它的用法,主要是参数设置,执行得到的转码结果,我会写一篇ffmpeg的参数用法,你也可以观看一下!下面我就贴出需要在接口用到的转码工具类和转码文件的存放位置工具类。

  1. 首先是转码文件的存放位置工具类。
package com.ruoyi.system.tool;

public class Contants {

    /**
     * @Description:(3.工具类主类)设置转码工具的各个路径
     * @param:@param args
     * @return:void
     * @author:于留俊
     * @date:2019-11-2
     * @version:V1.0
     */

    public static final String ffmpegpath = "E://javaEdit//ffmpeg//bin//ffmpeg.exe";		// ffmpeg工具安装位置
    public static final String mencoderpath = "E://javaEdit//mencoder//mencoder.exe"; 	// mencoder工具安装的位置

    public static final String videofolder = "E://javaEdit//temp1/"; 	// 需要被转换格式的视频目录
    public static final String videoRealPath = "E://javaEdit//temp2/"; 	// 需要被截图的视频目录

    public static final String targetfolder = "D://ruoyi/uploadPath//"; // 转码后视频保存的目录
    public static final String imageRealPath = "D://ruoyi/uploadPath//"; // 截图的存放目录


}

其次是转码工具类:

package com.ruoyi.system.tool;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @Title: ConverVideoUtils.java
 * @Package:com.resource.mytools
 * @Description:(2.转码和截图功能)
 * @see:接收Contants实体的路径
 * @author:于留俊
 * @date:2019-11-02
 * @version:V1.0
 */

public class ConverVideoUtils {
    private String sourceVideoPath;							//源视频路径
    private String filerealname;				 			//文件名不包括后缀名
    private String filename; 								//包括后缀名
    private String videofolder = Contants.videofolder; 		// 别的格式视频的目录
    private String targetfolder = Contants.targetfolder; 	// flv视频的目录
    private String ffmpegpath = Contants.ffmpegpath;		 // ffmpeg.exe的目录
    private String mencoderpath = Contants.mencoderpath; 	// mencoder的目录
    private String imageRealPath = Contants.imageRealPath;   // 截图的存放目录

    public ConverVideoUtils() {
    }

    //重构构造方法,传入源视频
    public ConverVideoUtils(String path) {
        sourceVideoPath = path;
    }

    //set和get方法传递path
    public String getPATH() {
        return sourceVideoPath;
    }

    public void setPATH(String path) {
        sourceVideoPath = path;
    }

    /**
     * 转换视频格式
     * @param String targetExtension 目标视频后缀名 .xxx
     * @param boolean isDelSourseFile 转换完成后是否删除源文件
     * @return
     */
    public boolean beginConver(String targetExtension, boolean isDelSourseFile) {
        File fi = new File(sourceVideoPath);

        filename = fi.getName();      		 //获取文件名+后缀名

        filerealname = filename.substring(0, filename.lastIndexOf(".")); //获取不带后缀的文件名-后面加.toLowerCase()小写

        System.out.println("----接收到文件("+sourceVideoPath+")需要转换-------");

        //检测本地是否存在
		/*if (checkfile(sourceVideoPath)) {
			System.out.println(sourceVideoPath + "========该文件存在哟 ");
			return false;
		}*/

        System.out.println("----开始转文件(" + sourceVideoPath + ")-------------------------- ");

        //执行转码机制
        if (process(targetExtension,isDelSourseFile)) {

            System.out.println("视频转码结束,开始截图================= ");

            //视频转码完成,调用截图功能--zoutao
            if (processImg(sourceVideoPath)) {
                System.out.println("截图成功! ");
            } else {
                System.out.println("截图失败! ");
            }


            //删除原视频+临时视频
			/*if (isDelSourseFile) {
				deleteFile(sourceVideoPath);
			}*/

			/*File file1 = new File(sourceVideoPath);
	         if (file1.exists()){
	        	 System.out.println("删除原文件-可用:"+sourceVideoPath);
	             file1.delete();
	          }*/

            String temppath=videofolder + filerealname + ".avi";
            File file2 = new File(temppath);
            if (file2.exists()){
                System.out.println("删除临时文件:"+temppath);
                file2.delete();
            }

            sourceVideoPath = null;
            return true;
        } else {
            sourceVideoPath = null;
            return false;
        }
    }


    /**
     * 检查文件是否存在-多处都有判断
     * @param path
     * @return
     */

	/*private boolean checkfile(String path) {
		path = sourceVideoPath;
		File file = new File(path);
		try {
			if (file.exists()) {
				System.out.println("视频文件不存在============="+path);
				return true;
			} else {
				System.out.println("视频文件存在"+path);
				return false;
			}
		} catch (Exception e) {
			// TODO: handle exception
			System.out.println("拒绝对文件进行读访问");
		}
		return false;
	}*/


    /**
     * 视频截图功能
     * @param sourceVideoPath 需要被截图的视频路径(包含文件名和后缀名)
     * @return
     */
    public boolean processImg(String sourceVideoPath) {

        //先确保保存截图的文件夹存在
        File TempFile = new File(imageRealPath);
        if (TempFile.exists()) {
            if (TempFile.isDirectory()) {
                System.out.println("该文件夹存在!!!!!!!!");
            }else {
                System.out.println("同名的文件存在,不能创建文件夹。");
            }
        }else {
            System.out.println("文件夹不存在,创建该文件夹。");
            TempFile.mkdir();
        }

        File fi = new File(sourceVideoPath);
        filename = fi.getName();			//获取视频文件的名称。
        filerealname = filename.substring(0, filename.lastIndexOf("."));	//获取视频名+不加后缀名 后面加.toLowerCase()转为小写

        List<String> commend = new ArrayList<String>();
        //第一帧: 00:00:01
        //截图命令:time ffmpeg -ss 00:00:01 -i test1.flv -f image2 -y test1.jpg

        commend.add(ffmpegpath);			//指定ffmpeg工具的路径
        commend.add("-ss");
        commend.add("00:00:01");			//1是代表第1秒的时候截图
        commend.add("-i");
        commend.add(sourceVideoPath);		//截图的视频路径
        commend.add("-f");
        commend.add("image2");
        commend.add("-y");
        commend.add(imageRealPath + filerealname + ".jpg");		//生成截图xxx.jpg

        //打印截图命令--zoutao
        StringBuffer test = new StringBuffer();
        for (int i = 0; i < commend.size(); i++) {
            test.append(commend.get(i) + " ");
        }
        System.out.println("截图命令:"+test);

        //转码后完成截图功能-还是得用线程来解决--zoutao
        try {
			/*ProcessBuilder builder = new ProcessBuilder();
			builder.command(commend);
			Process p =builder.start();*/
            //调用线程处理命令
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commend);
            Process p = builder.start();

            //获取进程的标准输入流
            final InputStream is1 = p.getInputStream();
            //获取进程的错误流
            final InputStream is2 = p.getErrorStream();
            //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
            new Thread() {
                public void run() {
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(is1));
                    try {
                        String lineB = null;
                        while ((lineB = br.readLine()) != null) {
                            if (lineB != null){
                                System.out.println(lineB);    //必须取走线程信息避免堵塞
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //关闭流
                    finally{
                        try {
                            is1.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }.start();
            new Thread() {
                public void run() {
                    BufferedReader br2 = new BufferedReader(
                            new InputStreamReader(is2));
                    try {
                        String lineC = null;
                        while ((lineC = br2.readLine()) != null) {
                            if (lineC != null)   {
                                //System.out.println(lineC);   //必须取走线程信息避免堵塞
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    //关闭流
                    finally{
                        try {
                            is2.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }
            }.start();
            // 等Mencoder进程转换结束,再调用ffmepg进程非常重要!!!
            p.waitFor();
            System.out.println("截图进程结束");
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }



    /**
     * 实际转换视频格式的方法
     * @param targetExtension 目标视频后缀名
     * @param isDelSourseFile 转换完成后是否删除源文件
     * @return
     */
    private boolean process(String targetExtension, boolean isDelSourseFile) {

        //先判断视频的类型-返回状态码
        int type = checkContentType();
        boolean status = false;

        //根据状态码处理
        if (type == 0) {
            System.out.println("ffmpeg可以转换,统一转为mp4文件");

            status = processVideoFormat(sourceVideoPath,targetExtension,isDelSourseFile);//可以指定转换为什么格式的视频

        } else if (type == 1) {
            //如果type为1,将其他文件先转换为avi,然后在用ffmpeg转换为指定格式
            System.out.println("ffmpeg不可以转换,先调用mencoder转码avi");
            String avifilepath = processAVI(type);

            if (avifilepath == null){
                // 转码失败--avi文件没有得到
                System.out.println("mencoder转码失败,未生成AVI文件");
                return false;
            }else {
                System.out.println("生成AVI文件成功,ffmpeg开始转码:");
                status = processVideoFormat(avifilepath,targetExtension,isDelSourseFile);
            }
        }
        return status;   //执行完成返回布尔类型true
    }

    /**
     * 检查文件类型
     * @return
     */
    private int checkContentType() {

        //取得视频后缀-
        String type = sourceVideoPath.substring(sourceVideoPath.lastIndexOf(".") + 1, sourceVideoPath.length()).toLowerCase();
        System.out.println("源视频类型为:"+type);

        // 如果是ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
        if (type.equals("avi")) {
            return 0;
        } else if (type.equals("mpg")) {
            return 0;
        } else if (type.equals("wmv")) {
            return 0;
        } else if (type.equals("3gp")) {
            return 0;
        } else if (type.equals("mov")) {
            return 0;
        } else if (type.equals("mp4")) {
            return 0;
        } else if (type.equals("asf")) {
            return 0;
        } else if (type.equals("asx")) {
            return 0;
        } else if (type.equals("flv")) {
            return 0;
        }else if (type.equals("mkv")) {
            return 0;
        }

        // 如果是ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
        // 就先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
        else if (type.equals("wmv9")) {
            return 1;
        } else if (type.equals("rm")) {
            return 1;
        } else if (type.equals("rmvb")) {
            return 1;
        }
        System.out.println("上传视频格式异常");
        return 9;
    }



    /**
     *  对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
     *  可以先用(mencoder)转换为avi(ffmpeg能解析的)格式.再用ffmpeg解析为指定格式
     * @param type
     * @return
     */
    private String processAVI(int type) {

        System.out.println("调用了mencoder.exe工具");
        List<String> commend = new ArrayList<String>();

        commend.add(mencoderpath);                //指定mencoder.exe工具的位置
        commend.add(sourceVideoPath);             //指定源视频的位置
        commend.add("-oac");
        commend.add("mp3lame");			//lavc 原mp3lame
        commend.add("-lameopts");
        commend.add("preset=64");
        commend.add("-ovc");
        commend.add("xvid"); 		//mpg4(xvid),AVC(h.264/x264),只有h264才是公认的MP4标准编码,如果ck播放不了,就来调整这里
        commend.add("-xvidencopts");  //xvidencopts或x264encopts
        commend.add("bitrate=600");		//600或440
        commend.add("-of");
        commend.add("avi");
        commend.add("-o");

        commend.add(videofolder + filerealname + ".avi");   //存放路径+名称,生成.avi视频

        //打印出转换命令-zoutao
        StringBuffer test = new StringBuffer();
        for (int i = 0; i < commend.size(); i++) {
            test.append(commend.get(i) + " ");
        }
        System.out.println("mencoder输入的命令:"+test);
        // cmd命令:mencoder 1.rmvb -oac mp3lame -lameopts preset=64 -ovc xvid
        // -xvidencopts bitrate=600 -of avi -o rmvb.avi

        try {
            //调用线程命令启动转码
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commend);
            Process p = builder.start();   //多线程处理加快速度-解决数据丢失
            //doWaitFor(p);

            //获取进程的标准输入流
            final InputStream is1 = p.getInputStream();
            //获取进程的错误流
            final InputStream is2 = p.getErrorStream();
            //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
            new Thread() {
                public void run() {
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(is1));
                    try {
                        String lineB = null;
                        while ((lineB = br.readLine()) != null) {
                            if (lineB != null){
                                System.out.println();
                                System.out.println(lineB);    //打印mencoder转换过程代码-可注释
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    //关闭流
                   /* finally{
                        try {
                          is1.close();
                        } catch (IOException e) {
                           e.printStackTrace();
                       }
                     }  */

                }
            }.start();
            new Thread() {
                public void run() {
                    BufferedReader br2 = new BufferedReader(
                            new InputStreamReader(is2));
                    try {
                        String lineC = null;
                        while ((lineC = br2.readLine()) != null) {
                            if (lineC != null)   {
                                System.out.println(lineC);    //打印mencoder转换过程代码
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    //关闭
                   /* finally{
                        try {
                            is2.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                      } */

                }
            }.start();

            // 等Mencoder进程转换结束,再调用ffmepg进程非常重要!!!
            p.waitFor();
            System.out.println("Mencoder进程结束");
            return videofolder + filerealname + ".avi";		//返回转为AVI以后的视频地址

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }



    /**
     * 转换为指定格式--zoutao
     * ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
     * @param oldfilepath
     * @param targetExtension 目标格式后缀名 .xxx
     * @param isDelSourseFile 转换完成后是否删除源文件
     * @return
     */
    private boolean processVideoFormat(String oldfilepath, String targetExtension, boolean isDelSourceFile) {

        System.out.println("调用了ffmpeg.exe工具");

        //先确保保存转码后的视频的文件夹存在
        File TempFile = new File(targetfolder);
        if (TempFile.exists()) {
            if (TempFile.isDirectory()) {
                System.out.println("该文件夹存在。");
            }else {
                System.out.println("同名的文件存在,不能创建文件夹。");
            }
        }else {
            System.out.println("文件夹不存在,创建该文件夹。");
            TempFile.mkdir();
        }

        List<String> commend = new ArrayList<String>();

        commend.add(ffmpegpath);		 //ffmpeg.exe工具地址
        commend.add("-i");
        commend.add(oldfilepath);			//源视频路径

        commend.add("-vcodec");
        commend.add("h263");  //
        commend.add("-ab");		//新增4条
        commend.add("128");      //高品质:128 低品质:64
        commend.add("-acodec");
        commend.add("mp3");      //音频编码器:原libmp3lame
        commend.add("-ac");
        commend.add("2");       //原1
        commend.add("-ar");
        commend.add("22050");   //音频采样率22.05kHz
        commend.add("-r");
        commend.add("29.97");  //高品质:29.97 低品质:15
        commend.add("-c:v");
        commend.add("libx264");	//视频编码器:视频是h.264编码格式
        commend.add("-strict");
        commend.add("-2");
        commend.add(targetfolder + filerealname + targetExtension);  // //转码后的路径+名称,是指定后缀的视频

        //打印命令--zoutao
        StringBuffer test = new StringBuffer();
        for (int i = 0; i < commend.size(); i++) {
            test.append(commend.get(i) + " ");
        }
        System.out.println("ffmpeg输入的命令:"+test);

        try {
            //多线程处理加快速度-解决rmvb数据丢失builder名称要相同
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commend);
            Process p = builder.start();   //多线程处理加快速度-解决数据丢失

            final InputStream is1 = p.getInputStream();
            final InputStream is2 = p.getErrorStream();
            new Thread() {
                public void run() {
                    BufferedReader br = new BufferedReader(new InputStreamReader(is1));
                    try {
                        String lineB = null;
                        while ((lineB = br.readLine()) != null) {
                            if (lineB != null)
                                System.out.println();
                                System.out.println(lineB);    //打印mencoder转换过程代码
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
            new Thread() {
                public void run() {
                    BufferedReader br2 = new BufferedReader(new InputStreamReader(is2));
                    try {
                        String lineC = null;
                        while ((lineC = br2.readLine()) != null) {
                            if (lineC != null)
                                System.out.println(lineC);    //打印mencoder转换过程代码
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();

            p.waitFor();		//进程等待机制,必须要有,否则不生成mp4!!!
            System.out.println("生成mp4视频为:"+videofolder + filerealname + ".mp4");
            return true;
        }
        catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

当你的代码写到这里,你把项目启动测试一下,看一下控制台报的信息,每个人的信息大致一样,少一些文件夹等简单信息,那你手动新建一个就好了。同时如果你是springboot项目要做好配置关于保存文件夹的路径,在项目中配置安全路径开放,不然你保存到数据的视频路径在页面中404,我贴出来我的配置!

  1. 首先是yml文件中配置:
# 项目相关配置
ruoyi:
  # 名称
  name: RuoYi
  # 版本
  version: 4.0.0
  # 版权年份
  copyrightYear: 2019
  # 实例演示开关
  demoEnabled: true
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: D:/ruoyi/uploadPath #
  # 获取ip地址开关
  addressEnabled: true

你可以看到prifile就是视频在页面中保存文件夹路径映射,在页面中展示视频要这样:http://**.**.**.**:8080/profile/****.mp4这样才是播放视频,接下来是映射配置在config中,先在全局参数设置中定义路径名称,从yml配置中可以方便取出,

package com.ruoyi.common.config;

import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.YamlUtil;

/**
 * 全局配置类
 * 
 * @author 于留俊
 */
public class Global
{
    private static final Logger log = LoggerFactory.getLogger(Global.class);

    private static String NAME = "application.yml";

    /**
     * 当前对象实例
     */
    private static Global global;

    /**
     * 保存全局属性值
     */
    private static Map<String, String> map = new HashMap<String, String>();

    private Global(){
    }

    /**
     * 静态工厂方法
     */
    public static synchronized Global getInstance()
    {
        if (global == null)
        {
            global = new Global();
        }
        return global;
    }

    /**
     * 获取配置
     */
    public static String getConfig(String key)
    {
        String value = map.get(key);
        if (value == null)
        {
            Map<?, ?> yamlMap = null;
            try
            {
                yamlMap = YamlUtil.loadYaml(NAME);
                value = String.valueOf(YamlUtil.getProperty(yamlMap, key));
                map.put(key, value != null ? value : StringUtils.EMPTY);
            }
            catch (FileNotFoundException e)
            {
                log.error("获取全局配置异常 {}", key);
            }
        }
        return value;
    }

    /**
     * 获取项目名称
     */
    public static String getName()
    {
        return StringUtils.nvl(getConfig("ruoyi.name"), "RuoYi");
    }

    /**
     * 获取项目版本
     */
    public static String getVersion()
    {
        return StringUtils.nvl(getConfig("ruoyi.version"), "4.0.0");
    }

    /**
     * 获取版权年份
     */
    public static String getCopyrightYear()
    {
        return StringUtils.nvl(getConfig("ruoyi.copyrightYear"), "2019");
    }

    /**
     * 实例演示开关
     */
    public static String isDemoEnabled()
    {
        return StringUtils.nvl(getConfig("ruoyi.demoEnabled"), "true");
    }

    /**
     * 获取ip地址开关
     */
    public static Boolean isAddressEnabled()
    {
        return Boolean.valueOf(getConfig("ruoyi.addressEnabled"));
    }

    /**
     * 获取文件上传路径
     */
    public static String getProfile()
    {
        return getConfig("ruoyi.profile");
    }

    /**
     * 获取头像上传路径
     */
    public static String getAvatarPath()
    {
        return getProfile() + "/avatar";
    }

    /**
     * 获取下载路径
     */
    public static String getDownloadPath()
    {
        return getProfile() + "/download/";
    }

    /**
     * 获取上传路径
     */
    public static String getUploadPath()
    {
        return getProfile() + "/upload";
    }
}

这个通用配置,用过的朋友,肯定知道这是配置文件,接口文档等的配置。

package com.ruoyi.framework.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.ruoyi.common.config.Global;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;

/**
 * 通用配置
 * 
 * @author 于留俊
 */
@Configuration
public class ResourcesConfig implements WebMvcConfigurer {
    /**
     * 首页地址
     */
    @Value("${shiro.user.indexUrl}")
    private String indexUrl;

    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;

    /**
     * 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry)
    {
        registry.addViewController("/").setViewName("forward:" + indexUrl);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry)
    {
        /** 本地文件上传路径 */
        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + Global.getProfile() + "/");

        /** swagger配置 */
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * 自定义拦截规则
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
    }
}

这里的mvc映射文件,都是通用设置,你要是是在不会,就去百度一下,各个设置的作用,其中的

Constants是个工具类,配置全局参数的,贴出来

package com.ruoyi.common.constant;

/**
 * 通用常量信息
 * 
 * @author 于留俊
 */
public class Constants
{
    /**
     * UTF-8 字符集
     */
    public static final String UTF8 = "UTF-8";

    /**
     * 通用成功标识
     */
    public static final String SUCCESS = "0";

    /**
     * 通用失败标识
     */
    public static final String FAIL = "1";

    /**
     * 登录成功
     */
    public static final String LOGIN_SUCCESS = "Success";

    /**
     * 注销
     */
    public static final String LOGOUT = "Logout";

    /**
     * 登录失败
     */
    public static final String LOGIN_FAIL = "Error";

    /**
     * 自动去除表前缀
     */
    public static final String AUTO_REOMVE_PRE = "true";

    /**
     * 当前记录起始索引
     */
    public static final String PAGE_NUM = "pageNum";

    /**
     * 每页显示记录数
     */
    public static final String PAGE_SIZE = "pageSize";

    /**
     * 排序列
     */
    public static final String ORDER_BY_COLUMN = "orderByColumn";

    /**
     * 排序的方向 "desc" 或者 "asc".
     */
    public static final String IS_ASC = "isAsc";

    /**
     * 资源映射路径 前缀
     */
    public static final String RESOURCE_PREFIX = "/profile";
}

还有这里需要在ShiroConfig中配置一下profile不需要验证,我把配置文件贴出,你可以加入项目试一下,好用!

package com.ruoyi.framework.config;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.shiro.realm.UserRealm;
import com.ruoyi.framework.shiro.session.OnlineSessionDAO;
import com.ruoyi.framework.shiro.session.OnlineSessionFactory;
import com.ruoyi.framework.shiro.web.filter.LogoutFilter;
import com.ruoyi.framework.shiro.web.filter.captcha.CaptchaValidateFilter;
import com.ruoyi.framework.shiro.web.filter.kickout.KickoutSessionFilter;
import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter;
import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter;
import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager;
import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

/**
 * 权限配置加载
 * 
 * @author ruoyi
 */
@Configuration
public class ShiroConfig
{
    public static final String PREMISSION_STRING = "perms[\"{0}\"]";

    // Session超时时间,单位为毫秒(默认30分钟)
    @Value("${shiro.session.expireTime}")
    private int expireTime;

    // 相隔多久检查一次session的有效性,单位毫秒,默认就是10分钟
    @Value("${shiro.session.validationInterval}")
    private int validationInterval;

    // 同一个用户最大会话数
    @Value("${shiro.session.maxSession}")
    private int maxSession;

    // 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
    @Value("${shiro.session.kickoutAfter}")
    private boolean kickoutAfter;

    // 验证码开关
    @Value("${shiro.user.captchaEnabled}")
    private boolean captchaEnabled;

    // 验证码类型
    @Value("${shiro.user.captchaType}")
    private String captchaType;

    // 设置Cookie的域名
    @Value("${shiro.cookie.domain}")
    private String domain;

    // 设置cookie的有效访问路径
    @Value("${shiro.cookie.path}")
    private String path;

    // 设置HttpOnly属性
    @Value("${shiro.cookie.httpOnly}")
    private boolean httpOnly;

    // 设置Cookie的过期时间,秒为单位
    @Value("${shiro.cookie.maxAge}")
    private int maxAge;

    // 登录地址
    @Value("${shiro.user.loginUrl}")
    private String loginUrl;

    // 权限认证失败地址
    @Value("${shiro.user.unauthorizedUrl}")
    private String unauthorizedUrl;

    /**
     * 缓存管理器 使用Ehcache实现
     */
    @Bean
    public EhCacheManager getEhCacheManager()
    {
        net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");
        EhCacheManager em = new EhCacheManager();
        if (StringUtils.isNull(cacheManager))
        {
            em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
            return em;
        }
        else
        {
            em.setCacheManager(cacheManager);
            return em;
        }
    }

    /**
     * 返回配置文件流 避免ehcache配置文件一直被占用,无法完全销毁项目重新部署
     */
    protected InputStream getCacheManagerConfigFileInputStream()
    {
        String configFile = "classpath:ehcache/ehcache-shiro.xml";
        InputStream inputStream = null;
        try
        {
            inputStream = ResourceUtils.getInputStreamForPath(configFile);
            byte[] b = IOUtils.toByteArray(inputStream);
            InputStream in = new ByteArrayInputStream(b);
            return in;
        }
        catch (IOException e)
        {
            throw new ConfigurationException(
                    "Unable to obtain input stream for cacheManagerConfigFile [" + configFile + "]", e);
        }
        finally
        {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * 自定义Realm
     */
    @Bean
    public UserRealm userRealm(EhCacheManager cacheManager)
    {
        UserRealm userRealm = new UserRealm();
        userRealm.setCacheManager(cacheManager);
        return userRealm;
    }

    /**
     * 自定义sessionDAO会话
     */
    @Bean
    public OnlineSessionDAO sessionDAO()
    {
        OnlineSessionDAO sessionDAO = new OnlineSessionDAO();
        return sessionDAO;
    }

    /**
     * 自定义sessionFactory会话
     */
    @Bean
    public OnlineSessionFactory sessionFactory()
    {
        OnlineSessionFactory sessionFactory = new OnlineSessionFactory();
        return sessionFactory;
    }

    /**
     * 会话管理器
     */
    @Bean
    public OnlineWebSessionManager sessionManager()
    {
        OnlineWebSessionManager manager = new OnlineWebSessionManager();
        // 加入缓存管理器
        manager.setCacheManager(getEhCacheManager());
        // 删除过期的session
        manager.setDeleteInvalidSessions(true);
        // 设置全局session超时时间
        manager.setGlobalSessionTimeout(expireTime * 60 * 1000);
        // 去掉 JSESSIONID
        manager.setSessionIdUrlRewritingEnabled(false);
        // 定义要使用的无效的Session定时调度器
        manager.setSessionValidationScheduler(SpringUtils.getBean(SpringSessionValidationScheduler.class));
        // 是否定时检查session
        manager.setSessionValidationSchedulerEnabled(true);
        // 自定义SessionDao
        manager.setSessionDAO(sessionDAO());
        // 自定义sessionFactory
        manager.setSessionFactory(sessionFactory());
        return manager;
    }

    /**
     * 安全管理器
     */
    @Bean
    public SecurityManager securityManager(UserRealm userRealm, SpringSessionValidationScheduler springSessionValidationScheduler)
    {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(userRealm);
        // 记住我
        securityManager.setRememberMeManager(rememberMeManager());
        // 注入缓存管理器;
        securityManager.setCacheManager(getEhCacheManager());
        // session管理器
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /**
     * 退出过滤器
     */
    public LogoutFilter logoutFilter()
    {
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setCacheManager(getEhCacheManager());
        logoutFilter.setLoginUrl(loginUrl);
        return logoutFilter;
    }

    /**
     * Shiro过滤器配置
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
    {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 身份认证失败,则跳转到登录页面的配置
        shiroFilterFactoryBean.setLoginUrl(loginUrl);
        // 权限认证失败,则跳转到指定页面
        shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
        // Shiro连接约束配置,即过滤链的定义
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 对静态资源设置匿名访问
        filterChainDefinitionMap.put("/favicon.ico**", "anon");
        filterChainDefinitionMap.put("/ruoyi.png**", "anon");
        filterChainDefinitionMap.put("/profile/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/docs/**", "anon");
        filterChainDefinitionMap.put("/fonts/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        filterChainDefinitionMap.put("/ajax/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/ruoyi/**", "anon");
        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
        // 退出 logout地址,shiro去清除session
        filterChainDefinitionMap.put("/logout", "logout");
        // 不需要拦截的访问
        filterChainDefinitionMap.put("/login", "anon,captchaValidate");
        // 系统权限列表
        // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());

        Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
        filters.put("onlineSession", onlineSessionFilter());
        filters.put("syncOnlineSession", syncOnlineSessionFilter());
        filters.put("captchaValidate", captchaValidateFilter());
        filters.put("kickout", kickoutSessionFilter());
        // 注销成功,则跳转到指定页面
        filters.put("logout", logoutFilter());
        shiroFilterFactoryBean.setFilters(filters);

        // 所有请求需要认证
        filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**
     * 自定义在线用户处理过滤器
     */
    @Bean
    public OnlineSessionFilter onlineSessionFilter()
    {
        OnlineSessionFilter onlineSessionFilter = new OnlineSessionFilter();
        onlineSessionFilter.setLoginUrl(loginUrl);
        return onlineSessionFilter;
    }

    /**
     * 自定义在线用户同步过滤器
     */
    @Bean
    public SyncOnlineSessionFilter syncOnlineSessionFilter()
    {
        SyncOnlineSessionFilter syncOnlineSessionFilter = new SyncOnlineSessionFilter();
        return syncOnlineSessionFilter;
    }

    /**
     * 自定义验证码过滤器
     */
    @Bean
    public CaptchaValidateFilter captchaValidateFilter()
    {
        CaptchaValidateFilter captchaValidateFilter = new CaptchaValidateFilter();
        captchaValidateFilter.setCaptchaEnabled(captchaEnabled);
        captchaValidateFilter.setCaptchaType(captchaType);
        return captchaValidateFilter;
    }

    /**
     * cookie 属性设置
     */
    public SimpleCookie rememberMeCookie()
    {
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setDomain(domain);
        cookie.setPath(path);
        cookie.setHttpOnly(httpOnly);
        cookie.setMaxAge(maxAge * 24 * 60 * 60);
        return cookie;
    }

    /**
     * 记住我
     */
    public CookieRememberMeManager rememberMeManager()
    {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        cookieRememberMeManager.setCipherKey(Base64.decode("fCq+/xW488hMTCD+cmJ3aQ=="));
        return cookieRememberMeManager;
    }

    /**
     * 同一个用户多设备登录限制
     */
    public KickoutSessionFilter kickoutSessionFilter()
    {
        KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
        kickoutSessionFilter.setCacheManager(getEhCacheManager());
        kickoutSessionFilter.setSessionManager(sessionManager());
        // 同一个用户最大的会话数,默认-1无限制;比如2的意思是同一个用户允许最多同时两个人登录
        kickoutSessionFilter.setMaxSession(maxSession);
        // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序
        kickoutSessionFilter.setKickoutAfter(kickoutAfter);
        // 被踢出后重定向到的地址;
        kickoutSessionFilter.setKickoutUrl("/login?kickout=1");
        return kickoutSessionFilter;
    }

    /**
     * thymeleaf模板引擎和shiro框架的整合
     */
    @Bean
    public ShiroDialect shiroDialect()
    {
        return new ShiroDialect();
    }

    /**
     * 开启Shiro注解通知器
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            @Qualifier("securityManager") SecurityManager securityManager)
    {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

看到上面的Shiro过滤器配置,这个注释了吗?加入

filterChainDefinitionMap.put("/profile/**", "anon");这个句代码,就是在调用页面路径的时候放行权限,不然的话你会404!

  1. 我的项目是springboot项目,里面配置这复杂的权限设置和各个项目分模块的maven管理和子父关系的项目依赖,今个先把视频转码的关键技术和上传文件夹的路径配置的方法贴出来,朋友们可以在自己的项目中,测试是否可用,如果有bug,可以留言贴出你的代码,大家一起谈论和解决!