14 Star 155 Fork 35

吴博 / 文档在线预览

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

file-preview-spring-boot-starter

star fork star fork

一个文档在线预览的中间件
可通过简单的配置即可集成到springboot中
支持word、excel、ppt、pdf、ofd、图片、视频、音频、markdown、代码、网页、epub电子书、Xmind脑图、压缩文件、bpmn(业务流程管理和符号)、cmmn(案例管理模型和符号)、dmn(决策管理和符号)等格式文件的在线预览

代码示例

  1. 使用文档在线预览多平台文件存储实体SQL工具实现的文件预览Demo
  2. 使用文档在线预览多平台文件存储实体SQL工具实现的文件预览VUE Demo

第一步 增加 JitPack 仓库

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

第二步 引入jar

1.1.7版本后groupId更换为com.github.wb04307201
1.2.0版本后升级到jdk17 SpringBoot3+
继续使用jdk 8请查看jdk8分支

<dependency>
    <groupId>com.github.wb04307201</groupId>
    <artifactId>file-preview-spring-boot-starter</artifactId>
    <version>1.2.7</version>
</dependency>

第三步 在启动类上加上@EnableFilePreview注解

@EnableFilePreview
@SpringBootApplication
public class FilePreviewDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(FilePreviewDemoApplication.class, args);
    }

}

第四步 添加相关配置

file:
  preview:
    enableWeb: true  # 默认为true,加载内置页面
    enableRest: true  # 默认为true, 加载内置接口

配置word,excel,ppt文件的预览方式,支持5种模式

jodconverter

安装libroffice并添加配置
详细配置内容请查看jodconverter

file:
  preview:
    # 使用jodconverter模式,可省略该配置
    officeConverter: jod
jodconverter:
  local:
    enabled: true
    # libreOffice根目录
    officeHome: C:\Program Files\LibreOffice
    # 任务执行的超时时间
    taskExecutionTimeout: 86400000
    # 任务队列的超时时间
    taskQueueTimeout: 86400000
    # 端口(线程)
    portNumbers: [ 2001,2002,2003 ]
    # 一个进程的超时时间
    processTimeout: 86400000

spire.office

项目中使用的spire.office为免费版本,转换office文件存在一定限制 如要使用收费版本,请排除免费版本的依赖并添加正式版本
Spire.Office

file:
  preview:
    # 使用spire.office模式
    officeConverter: spire

onlyoffice

使用onlyoffice将不对office文件进行转换
直接预览word,excel,ppt,文本类型的文件
可以通过docker快速安装onlyoffice,命令如下

docker run --name onlyoffice -i -t -d -p 80:80 -e JWT_SECRET=my_jwt_secret onlyoffice/documentserver
#如果需要关闭使用JWT
docker run --name onlyoffice -i -t -d -p 80:80 -e JWT_ENABLED=false onlyoffice/documentserver

可参考官方文档
Installing ONLYOFFICE Docs Community Edition for Docker on a local server
Configuring JWT for ONLYOFFICE Docs

容器启动成功后,打开http://127.0.0.1/可以看到欢迎页面 如果需要使用onlyoffice自带的测试页面,可以找页面中如下部分,并分别在终端中执行1,2的命令,然后点击按钮3
img_3.png
img_7.png
docker版本的onlyoffice安装成功后,在项目中添加配置信息

file:
  preview:
    # 使用onlyoffice模式
    officeConverter: only
  online:
    preview:
      onlyoffice:
        domain: http://ip:port #OnlyOffice服务所在域
        download: http://ip:port/file/preview/download #当前服务的文件下载接口,用于onlyoffice从当前服务下载文件
        callback: http://ip:port/file/preview/onlyoffice/callback #当前服务的回写文件服务,用于onlyoffice回写文件到当前服务
        secret: my_jwt_secret #如果启用JWT,需要在这里配置约定好的secret

LibreOffice Online

使用libreoffice online将不对office文件进行转换
直接预览word,excel,ppt,文本类型的文件
可以通过docker快速安装libreoffice online,命令如下

#安装并启动docker版本lool
docker run --name lool -e "username=admin" -e "password=123456" -e "extra_params=--o:ssl.enable=false --o:storage.filesystem[@allow]=true" -v D:/lool:/srv/data:Z -p 9980:9980 -d libreoffice/online:master

# extra_params=--o:ssl.enable=false 关闭ssl
# --o:storage.filesystem[@allow]=true 允许读取本地文件

容器启动成功后,打开http://127.0.0.1:9980/loleaflet/dist/admin/admin.html可以看到控制台界面 img_18.png docker版本的lool安装成功后,在项目中添加配置信息

file:
  preview:
    # 使用LibreOffice Online模式
    officeConverter: lool
    libreoffice:
      domain: http://ip:port  #libreoffice online服务所在地址
      download: D:\lool #libreoffice online预览文件存储位置,需要和上面docker创建的容器映射到本地的目录一致

Collabora Online

使用Collabora Online将不对office文件进行转换
直接预览word,excel,ppt,文本类型的文件
可以通过docker快速安装Collabora Online,命令如下

docker run -t -d --name code -e "username=admin" -e "password=123456" -e "aliasgroup1=http://10.133.61.38:8090" -e "extra_params=--o:ssl.enable=false" -p 9980:9980 collabora/code

# extra_params=--o:ssl.enable=false 关闭ssl
# aliasgroup1=http://10.133.61.38:8090 配置允许wopi访问地址

容器启动成功后,打开http://127.0.0.1:9980/browser/dist/admin/admin.html可以看到控制台界面 img_29.png docker版本的cool安装成功后,在项目中添加配置信息

file:
  preview:
    # 使用Collabora Online模式
    officeConverter: cool
    collabora:
      domain: http://ip:port  #Collabora Online服务所在地址
      storageDomain: http://ip:port #当前服务的域名,用于Collabora Online从当前服务下载文件

第五步 访问内置界面使用文件上传

上传的文件可通过http://ip:端口/file/preview/list进行查看
注意:如配置了context-path需要在地址中对应添加
img_9.png

其他1:实际使用中,可通过配置和实现文件预览记录接口方法将数据持久化到数据库中

file:
  preview:
    file-preview-record: cn.wubo.file.preview.demo.H2FilePriviewRecordImpl
@Component
public class H2FilePriviewRecordImpl implements IFilePreviewRecord {

    static {
        MutilConnectionPool.init("main", "jdbc:h2:file:./data/demo;AUTO_SERVER=TRUE", "sa", "");
    }

    @Override
    public FilePreviewInfo save(FilePreviewInfo filePreviewInfo) {
        FilePreviewRecord filePreviewRecord = FilePreviewRecord.trans(filePreviewInfo);
        if (!StringUtils.hasLength(filePreviewRecord.getId())) {
            filePreviewRecord.setId(UUID.randomUUID().toString());
            MutilConnectionPool.run("main", conn -> ModelSqlUtils.insertSql(filePreviewRecord).executeUpdate(conn));
        } else MutilConnectionPool.run("main", conn -> ModelSqlUtils.updateSql(filePreviewRecord).executeUpdate(conn));
        return filePreviewRecord.getFilePreviewInfo();
    }

    @Override
    public List<FilePreviewInfo> list(FilePreviewInfo filePreviewInfo) {
        FilePreviewRecord filePreviewRecord = FilePreviewRecord.trans(filePreviewInfo);
        return MutilConnectionPool.run("main", conn -> ModelSqlUtils.selectSql(filePreviewRecord).executeQuery(conn)).stream().map(FilePreviewRecord::getFilePreviewInfo).collect(Collectors.toList());
    }

    @Override
    public FilePreviewInfo findById(String s) {
        FilePreviewInfo query = new FilePreviewInfo();
        query.setId(s);
        List<FilePreviewInfo> list = list(query);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public Boolean delete(FilePreviewInfo filePreviewInfo) {
        FilePreviewRecord filePreviewRecord = FilePreviewRecord.trans(filePreviewInfo);
        return MutilConnectionPool.run("main", conn -> ModelSqlUtils.deleteSql(filePreviewRecord).executeUpdate(conn)) > 0;
    }

    @Override
    public void init() {
        if (Boolean.FALSE.equals(MutilConnectionPool.run("main", conn -> new SQL<FilePreviewRecord>() {
        }.isTableExists(conn)))) MutilConnectionPool.run("main", conn -> new SQL<FilePreviewRecord>() {
        }.create().parse().createTable(conn));
    }
}

其他2:实际使用中,可通过配置和实现文件存储记录接口方法将文件持久化到其他平台中

file:
  preview:
    file-storage: cn.wubo.file.preview.demo.MinIOFileStorageImpl
@Component
public class MinIOFileStorageImpl implements IFileStorage {

    @Resource
    FileStorageService fileStorageService;

    @Override
    public FilePreviewInfo save(byte[] bytes, String fileName) {
        FileInfo fileInfo = fileStorageService.save(new MultipartFileStorage(fileName, bytes).setAlias("minio-1").setPath("preview"));
        FilePreviewInfo filePreviewInfo = new FilePreviewInfo();
        filePreviewInfo.setFileName(fileInfo.getOriginalFilename());
        filePreviewInfo.setFilePath(fileInfo.getId());
        return filePreviewInfo;
    }

    @Override
    public Boolean delete(FilePreviewInfo filePreviewInfo) {
        return fileStorageService.delete(filePreviewInfo.getFilePath());
    }

    @Override
    public byte[] getBytes(FilePreviewInfo filePreviewInfo) {
        MultipartFileStorage file = fileStorageService.download(filePreviewInfo.getFilePath());
        return file.getBytes();
    }

    @Override
    public void init() {

    }
}

注意: 文件存储这部分使用了file-storage-spring-boot-starter

其他3:通过内置Rest接口实现自定义页面

<!DOCTYPE html>
<html lang="en">
<head>
    <title>预览文件记录</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" href="/layui/2.9.6/css/layui.css"/>
    <script type="text/javascript" src="/layui/2.9.6/layui.js"></script>
    <style>
        body {
            padding: 10px 20px 10px 20px;
        }
    </style>
</head>
<body>
<form class="layui-form layui-row layui-col-space16">
    <div class="layui-col-md4">
        <div class="layui-form-item">
            <label class="layui-form-label">文件名</label>
            <div class="layui-input-block">
                <input type="text" name="fileName" placeholder="请输入" class="layui-input" lay-affix="clear">
            </div>
        </div>
    </div>
    <div class="layui-col-md4">
        <div class="layui-form-item">
            <label class="layui-form-label">原始文件名</label>
            <div class="layui-input-block">
                <input type="text" name="originalFilename" placeholder="请输入" class="layui-input" lay-affix="clear">
            </div>
        </div>
    </div>
    <div class="layui-col-md4">
        <div class="layui-form-item">
            <label class="layui-form-label">文件位置</label>
            <div class="layui-input-block">
                <input type="text" name="filePath" placeholder="请输入" class="layui-input" lay-affix="clear">
            </div>
        </div>
    </div>
    <div class="layui-btn-container layui-col-xs12">
        <button class="layui-btn" lay-submit lay-filter="table-search">查询</button>
        <button type="reset" class="layui-btn layui-btn-primary">重置</button>
    </div>
</form>
<!-- 拖拽上传 -->
<div class="layui-upload-drag" style="display: block;" id="ID-upload-demo-drag">
    <i class="layui-icon layui-icon-upload"></i>
    <div>点击上传,或将文件拖拽到此处</div>
    <div class="layui-hide" id="ID-upload-demo-preview">
        <hr>
        <img src="" alt="上传成功后渲染" style="max-width: 100%">
    </div>
</div>
<!-- 原始容器 -->
<table class="layui-hide" id="table"></table>
<!-- 操作列 -->
<script type="text/html" id="table-templet-operator">
    <div class="layui-clear-space">
        <a class="layui-btn layui-btn-xs" lay-event="delete">删除</a>
        <a class="layui-btn layui-btn-xs" lay-event="preview">预览</a>
        <a class="layui-btn layui-btn-xs" lay-event="download">下载</a>
    </div>
</script>
<script>
    layui.use(['table', 'form', 'util'], function () {
        let table = layui.table, form = layui.form, layer = layui.layer, $ = layui.$, laydate = layui.laydate,
            upload = layui.upload;

        // 搜索提交
        form.on('submit(table-search)', function (data) {
            let field = data.field; // 获得表单字段
            // 执行搜索重载
            table.reloadData('table', {
                where: field // 搜索的字段
            });
            return false; // 阻止默认 form 跳转
        });

        // 渲染
        upload.render({
            elem: '#ID-upload-demo-drag', // 绑定多个元素
            url: '/file/preview/upload', // 此处配置你自己的上传接口即可
            accept: 'file', // 普通文件
            done: function (res) {
                if (res.code === 200)
                    table.reloadData('table', {});
                layer.msg(res.message);
            }
        });

        table.render({
            elem: '#table',
            cols: [[ //标题栏
                {type: 'numbers', fixed: 'left'},
                {field: 'id', title: 'ID', width: 150, fixed: 'left', hide: true},
                {field: 'fileName', title: '文件名', width: 300},
                {field: 'originalFilename', title: '原始文件名', width: 300},
                {field: 'filePath', title: '文件位置', width: 300},
                {field: 'operator', title: '操作', width: 200, fixed: 'right', templet: '#table-templet-operator'},
            ]],
            url: '/file/preview/list',
            method: 'post',
            contentType: 'application/json',
            parseData: function (res) { // res 即为原始返回的数据
                return {
                    "code": res.code === 200 ? 0 : res.code, // 解析接口状态
                    "msg": res.message, // 解析提示文本
                    "count": res.data.length, // 解析数据长度
                    "data": res.data // 解析数据列表
                };
            },
        });

        // 操作列事件
        table.on('tool(table)', function (obj) {
            let data = obj.data; // 获得当前行数据
            switch (obj.event) {
                case 'delete':
                    deleteRow(data.id)
                    break;
                case 'preview':
                    previewRow(data.id)
                    break;
                case 'download':
                    downloadRow(data.id)
                    break;
            }
        })

        function deleteRow(id) {
            layer.confirm('确定要删除么?', {icon: 3}, function (index, layero, that) {
                fetch("/file/preview/delete?id=" + id)
                    .then(response => response.json())
                    .then(res => {
                        if (res.code === 200)
                            table.reloadData('table', {});
                        layer.close(index);
                        layer.msg(res.message);
                    })
                    .catch(err => {
                        layer.msg(err)
                        layer.close(index);
                    })
            }, function (index, layero, that) {
            });
        }

        function previewRow(id) {
            window.open("/file/preview?id=" + id);
        }

        function downloadRow(id) {
            window.open("/file/preview/download?id=" + id);
        }
    })
</script>
</body>
</html>

其他4:通过注入FileStorageService实现自定义Rest接口和自定义页面

@Controller
public class Demo2Controller {

    @Autowired
    FilePreviewService filePreviewService;

    /**
     * 处理demo2请求
     *
     * @param model           模型对象
     * @param filePreviewInfo 文件预览信息
     * @return 返回demo2页面
     */
    @PostMapping(value = "/demo2")
    public String demo2(Model model, FilePreviewInfo filePreviewInfo) {
        // 获取文件存储记录列表
        model.addAttribute("list", filePreviewService.list(filePreviewInfo));
        // 设置查询参数
        model.addAttribute("query", filePreviewInfo);
        return "demo2";
    }

    /**
     * 上传文件
     *
     * @param file  上传的文件
     * @param model 模型对象
     * @return 返回上传结果页面
     */
    @PostMapping(value = "/upload")
    public String upload(MultipartFile file, Model model) {
        try {
            filePreviewService.covert(file.getInputStream(), file.getOriginalFilename());
            FilePreviewInfo filePreviewInfo = new FilePreviewInfo();
            model.addAttribute("list", filePreviewService.list(filePreviewInfo));
            model.addAttribute("query", filePreviewInfo);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return "demo2";
    }

    /**
     * 获取文件列表
     *
     * @param model 模型对象
     * @return 返回页面名称
     */
    @GetMapping(value = "/demo2")
    public String demo2(Model model) {
        FilePreviewInfo filePreviewInfo = new FilePreviewInfo();
        List<FilePreviewInfo> list = filePreviewService.list(filePreviewInfo);
        model.addAttribute("list", list);
        model.addAttribute("query", filePreviewInfo);
        return "demo2";
    }

    /**
     * 删除文件
     *
     * @param req   请求对象
     * @param model 模型对象
     * @return 返回页面名称
     */
    @GetMapping(value = "/delete")
    public String delete(HttpServletRequest req, Model model) {
        String id = req.getParameter("id");
        filePreviewService.delete(id);
        FilePreviewInfo filePreviewInfo = new FilePreviewInfo();
        List<FilePreviewInfo> list = filePreviewService.list(filePreviewInfo);
        model.addAttribute("list", list);
        model.addAttribute("query", filePreviewInfo);
        return "demo2";
    }

    /**
     * 下载文件
     *
     * @param req  请求对象
     * @param resp 响应对象
     */
    @GetMapping(value = "/download")
    public void download(HttpServletRequest req, HttpServletResponse resp) {
        String id = req.getParameter("id");
        FilePreviewInfo filePreviewInfo = filePreviewService.findById(id);
        byte[] bytes = filePreviewService.getBytes(filePreviewInfo);
        resp.reset();
        try {
            resp.setContentType(FileUtils.getMimeType(filePreviewInfo.getFileName()));
            try (OutputStream os = resp.getOutputStream()) {
                resp.addHeader("Content-Disposition", "attachment;filename=" + new String(Objects.requireNonNull(filePreviewInfo.getOriginalFilename()).getBytes(), StandardCharsets.ISO_8859_1));
                IoUtils.writeToStream(bytes, os);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
    <link rel="stylesheet" type="text/css" href="/bootstrap/5.3.2/css/bootstrap.min.css"/>
    <script type="text/javascript" src="/bootstrap/5.3.2/js/bootstrap.bundle.min.js"></script>
    <style>
        .table tbody tr td {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
    </style>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col">
            <form method="post" enctype="multipart/form-data" action="/upload">
                <div class="mb-3">
                    <label for="fileInput" class="form-label">文件上传</label>
                    <input type="file" class="form-control" id="fileInput" aria-describedby="fileHelp" name="file">
                    <div id="fileHelp" class="form-text">支持word,excel,ppt,pdf,图片,视频,音频,markdown等格式文件的在线预览</div>
                </div>
                <button type="submit" class="btn btn-primary">提交</button>
            </form>
        </div>
    </div>
    <form class="row g-3 mb-3 mt-3" method="POST" action="/demo2">
        <div class="col-6">
            <label class="form-check-label" for="fileName">文件名</label>
            <input type="text" class="form-control" id="fileName" name="fileName" aria-describedby="文件名"
                   value="${(query.fileName)!''}">
        </div>
        <div class="col-6">
            <label class="form-check-label" for="originalFilename">原始文件名</label>
            <input type="text" class="form-control" id="originalFilename" name="originalFilename" aria-describedby="原始文件名"
                   value="${(query.originalFilename)!''}">
        </div>
        <div class="col-6">
            <label class="form-check-label" for="filePath">文件位置</label>
            <input type="text" class="form-control" id="filePath" name="filePath" aria-describedby="文件位置"
                   value="${(query.filePath)!''}">
        </div>
        <div class="col-12">
            <button type="submit" class="btn btn-primary">查询</button>
        </div>
    </form>
    <div class="row">
        <div class="col-12" style="overflow-x: auto">
            <table class="table table-striped table-border">
                <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">文件名</th>
                    <th scope="col">原始文件名</th>
                    <th scope="col">文件位置</th>
                    <th scope="col">删除</th>
                    <th scope="col">预览</th>
                    <th scope="col">下载</th>
                </tr>
                </thead>
                <tbody>
                <#if list?? && (list?size > 0)>
                    <#list list as row>
                        <tr>
                            <th scope="row">${row_index + 1}</th>
                            <td>${row.originalFilename!'-'}</td>
                            <td>${row.fileName!'-'}</td>
                            <td>${row.filePath!'-'}</td>
                            <td><a href="/delete?id=${row.id}" class="link-primary">@删除</a></td>
                            <td><a href="/file/preview?id=${row.id}" rel="noopener" target="_blank" class="link-primary">@预览</a></td>
                            <td><a href="/download?id=${row.id}" class="link-primary">@下载</a></td>
                        </tr>
                    </#list>
                </#if>
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

其他5:预览文件使用的开源项目

文件类型 预览组件 预览示例
word/excel/ppt jodconverter
word/excel/ppt Spire.Office
word/excel/ppt onlyoffice
word/excel/ppt libreoffice online
word/excel/ppt Collabora Online
pdf PDF.js
audio音频 audio.js
video视频 videojs
markdonw vditor
代码 CodeMirror
epub电子书 epub.js
xmid脑图 xmind-embed-viewer
网页 直接渲染
压缩文件 Apache Commons Compress
bpmn bpmn.io
cmmn bpmn.io
dmn bpmn.io
ofd ofd.js

其他6:自定义预览界面渲染

比如在实际使用minio作为对象存储,并想直接使用minio的url播放上传的视频
可通过继承IRenderPage并实现support和render方法的方式自定义页面渲染的方式

@Service
public class MinIORenderPage implements IRenderPage {
    @Override
    public Boolean support(FilePreviewService filePreviewService, FilePreviewInfo filePreviewInfo) {
        if ("life goes on.mp4".equals(filePreviewInfo.getOriginalFilename())) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    @Override
    public ServerResponse render(FilePreviewService filePreviewService, FilePreviewInfo filePreviewInfo) {
        try {
            return ServerResponse.permanentRedirect(new URI("http://127.0.0.1:9000/testfilestorage/temp/preview/50236952-e8c5-4e67-afe9-ff39e3eac8ca.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MJC7FWTK56VPHS6SUZQL%2F20231106%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231106T004949Z&X-Amz-Expires=604800&X-Amz-Security-Token=eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJNSkM3RldUSzU2VlBIUzZTVVpRTCIsImV4cCI6MTY5OTI3NDk3NiwicGFyZW50IjoiUk9PVFVTRVIifQ.DHAPkWUuPpy7-EVcQOh9VN6FOIbtsZiIX5THR3n7ds72zRpn9EY23BdCqf1wBYwjOel9a8IHF3qi-6z0PAAC0g&X-Amz-SignedHeaders=host&versionId=null&X-Amz-Signature=acf50009a980667d5084236bcb4993a42a57b2419b0ec1955f5eef8f2e7f982c")).build();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }
}
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2022 wubo Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

一个文档在线预览的中间件,可通过简单的配置即可集成到springboot中,支持word、excel、ppt、pdf、图片、视频、音频、markdown、代码、网页、epub电子书、Xmind脑图、压缩文件、bpmn(业务流程管理和符号)、cmmn(案例管理模型和符号)、dmn(决策管理和符号)等格式文件的在线预览 展开 收起
Apache-2.0
取消

发行版 (32)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/wb04307201/file-preview-spring-boot-starter.git
git@gitee.com:wb04307201/file-preview-spring-boot-starter.git
wb04307201
file-preview-spring-boot-starter
文档在线预览
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891