-
30 |
- {{.top}}
31 |
-
32 | {{range $key,$value := .treeData }}
33 |
- {{$key}} 34 | {{end}} 35 |
37 |
├── .DS_Store ├── .gitattributes ├── LICENSE ├── README.md ├── cad └── cad-preview-addon-1.0-SNAPSHOT.jar ├── conf └── app.conf ├── controllers ├── BaseController.go ├── PLMFileController.go ├── PreviewController.go └── default.go ├── deploy ├── .DS_Store ├── docker │ ├── Dockerfile.libre │ ├── Dockerfile.plm │ ├── docker-compose.yml │ ├── fonts │ │ └── SIMFANG.TTF │ └── plm-files-preview.tar.gz └── k8s │ ├── file-preview-ns.yaml │ └── file-preview-svc.yaml ├── go.mod ├── main.go ├── main_test.go ├── routers └── router.go ├── tmp ├── bg.jpg ├── convert │ └── .convert ├── decompress │ └── .decompress └── download │ └── .download ├── utils ├── convert.go ├── download.go ├── files.go └── path.go └── views ├── achieve.tpl ├── image.tpl ├── index.tpl ├── preview.tpl └── video.tpl /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.tpl linguist-language=Go 2 | *.yml linguist-language=Dockerfile 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Golang-File-Preview 2 | 3 | 文件预览服务 4 | 5 | 1、对Word\Excel\PPT\PDF 在线预览 6 | 2、实现CAD标准dwg\dxf 文件预览 7 | 3、压缩文件预览:tar.gz\tar.bzip2\tar.xz\zip\rar\tar\brotli\bzip2\flate\gzip\lz4\snappy\xz\zstandard 8 | 4、支持txt,java,php,py,md,js,css等所有纯文本 9 | 5、支持jpg,jpeg,png,gif等图片预览 10 | 6、支持avi,mp4,flv等视频预览 11 | 12 | 13 | 效果图 14 | 15 |  16 | 17 | 构建镜像步骤 18 | 1、首先构建基础环境镜像 19 | `docker build -t plm-files-preview-services -f Dockerfile .` 20 | 21 | 2、然后执行启动服务指令 22 | `docker-compose up -d` 23 | -------------------------------------------------------------------------------- /cad/cad-preview-addon-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/cad/cad-preview-addon-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /conf/app.conf: -------------------------------------------------------------------------------- 1 | appname = plm-files-preview 2 | httpport = 8079 3 | runmode = dev 4 | EnableDocs = true -------------------------------------------------------------------------------- /controllers/BaseController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import "github.com/astaxie/beego" 4 | 5 | type BaseController struct { 6 | beego.Controller 7 | } 8 | 9 | type ReturnMsg struct { 10 | Code int 11 | Msg string 12 | Data interface{} 13 | } 14 | 15 | func (this *BaseController) SuccessJson(data interface{}) { 16 | 17 | res := ReturnMsg{ 18 | 200, "success", data, 19 | } 20 | this.Data["json"] = res 21 | this.ServeJSON() //对json进行序列化输出 22 | this.StopRun() 23 | } 24 | 25 | func (this *BaseController) ErrorJson(code int, msg string, data interface{}) { 26 | 27 | res := ReturnMsg{ 28 | code, msg, data, 29 | } 30 | 31 | this.Data["json"] = res 32 | this.ServeJSON() 33 | this.StopRun() 34 | } 35 | -------------------------------------------------------------------------------- /controllers/PLMFileController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "errors" 5 | "github.com/mileworks/plm-files-preview/utils" 6 | "net/url" 7 | "path" 8 | "strings" 9 | ) 10 | 11 | type PLMFileController struct { 12 | BaseController 13 | } 14 | 15 | type AchieveFiles struct { 16 | Top string `json:"top"` 17 | TreeData map[string]string `json:"treeData"` 18 | } 19 | 20 | func (c *PLMFileController) PLMPreview() { 21 | 22 | // 1、拿当前文件下载地址 23 | downLoadUrl := c.GetString("url") 24 | 25 | _, err := url.ParseRequestURI(downLoadUrl) 26 | if err != nil { 27 | panic(errors.New("请输入正确的预览地址")) 28 | } 29 | 30 | // 2、下载当前的文件,并通过文件后缀进行预览 31 | var local string 32 | fileType, fileSuffix, filenameWithSuffix := utils.FileTypeVerify(downLoadUrl) 33 | local, _ = utils.DownloadFile(downLoadUrl, fileSuffix , filenameWithSuffix) 34 | 35 | if fileType == "achieve" { 36 | utils.UnarchiveFiles(local) 37 | files, _ := utils.GetFilesFromDirectory(local) 38 | 39 | // process preview url 40 | baseUrl := "http://" + c.Ctx.Request.Host + "/api/review?file=" 41 | treeMap := make(map[string]string) 42 | for _, fp := range files { 43 | reviewUrl := baseUrl + fp 44 | treeMap[path.Base(fp)] = reviewUrl 45 | } 46 | 47 | achieveFiles := &AchieveFiles{ 48 | strings.TrimSuffix(path.Base(local), path.Ext(path.Base(local))), 49 | treeMap} 50 | 51 | c.SuccessJson(achieveFiles) 52 | return 53 | } 54 | 55 | var resultPath string 56 | switch { 57 | case fileType == "pdf": 58 | c.Ctx.Output.ContentType("application/x-pdf") 59 | resultPath = local 60 | 61 | break 62 | case fileType == "image": 63 | c.Ctx.Output.ContentType("image/jpeg") 64 | resultPath = local 65 | 66 | break 67 | case fileType == "cad": 68 | c.Ctx.Output.ContentType("application/x-pdf") 69 | resultPath = utils.ConvertFromCADToPDF(local) 70 | 71 | break 72 | case fileType == "office": 73 | c.Ctx.Output.ContentType("application/x-pdf") 74 | resultPath = utils.ConvertToPDF(local) 75 | 76 | break 77 | case fileType == "txt": 78 | c.Ctx.Output.ContentType("text/plain") 79 | resultPath = local 80 | 81 | break 82 | case fileType == "video": 83 | c.Ctx.Output.ContentType("video/mp4") 84 | resultPath = local 85 | 86 | break 87 | default: 88 | panic(errors.New("文件暂时不支持格式")) 89 | } 90 | 91 | data, _ := utils.File2Bytes(resultPath) 92 | c.Ctx.Output.Body(data) 93 | } 94 | -------------------------------------------------------------------------------- /controllers/PreviewController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "errors" 5 | "github.com/astaxie/beego" 6 | "github.com/mileworks/plm-files-preview/utils" 7 | "net/url" 8 | "path" 9 | "strings" 10 | ) 11 | 12 | type PreviewController struct { 13 | beego.Controller 14 | } 15 | 16 | func (c *PreviewController) Preview() { 17 | c.Data["plm"] = "PLM 文件预览服务" 18 | 19 | previewUrl := c.GetString("previewUrl") 20 | 21 | _, err := url.ParseRequestURI(previewUrl) 22 | if err != nil { 23 | panic(errors.New("请输入正确的预览url 地址")) 24 | } 25 | 26 | fileType, fileSuffix, filenameWithSuffix := utils.FileTypeVerify(previewUrl) 27 | host := c.Ctx.Request.Host 28 | 29 | switch { 30 | case fileType == "pdf": 31 | c.Data["url"] = previewUrl 32 | c.TplName = "preview.tpl" 33 | break 34 | 35 | case fileType == "image": 36 | c.Data["url"] = previewUrl 37 | c.TplName = "image.tpl" 38 | break 39 | 40 | case fileType == "cad": 41 | 42 | local, _ := utils.DownloadFile(previewUrl, fileSuffix, filenameWithSuffix) 43 | resultPath := utils.ConvertFromCADToPDF(local) 44 | 45 | c.Data["url"] = "http://" + host + "/api/getfile?file=" + resultPath 46 | c.TplName = "preview.tpl" 47 | 48 | break 49 | case fileType == "office": 50 | 51 | local, _ := utils.DownloadFile(previewUrl, fileSuffix, filenameWithSuffix) 52 | resultPath := utils.ConvertToPDF(local) 53 | 54 | c.Data["url"] = "http://" + host + "/api/getfile?file=" + resultPath 55 | c.TplName = "preview.tpl" 56 | 57 | break 58 | case fileType == "achieve": 59 | 60 | local, _ := utils.DownloadFile(previewUrl, fileSuffix, filenameWithSuffix) 61 | utils.UnarchiveFiles(local) 62 | 63 | files, base := utils.GetFilesFromDirectory(local) 64 | 65 | // process preview url 66 | uri := "http://" + c.Ctx.Request.Host 67 | baseUrl := uri + "/api/preview?previewUrl=" 68 | treeMap := make(map[string]string) 69 | for _, fp := range files { 70 | reviewUrl := uri + "/api/review?file=" + fp 71 | treeMap[path.Base(fp)] = baseUrl + reviewUrl 72 | } 73 | 74 | c.Data["base"] = base 75 | c.Data["top"] = strings.TrimSuffix(path.Base(local), path.Ext(path.Base(local))) 76 | c.Data["treeData"] = treeMap 77 | c.TplName = "achieve.tpl" 78 | 79 | break 80 | case fileType == "txt": 81 | local, _ := utils.DownloadFile(previewUrl, fileSuffix, filenameWithSuffix) 82 | 83 | c.Data["url"] = "http://" + host + "/api/getfile?file=" + local 84 | c.TplName = "preview.tpl" 85 | break 86 | 87 | case fileType == "video": 88 | c.Data["url"] = previewUrl 89 | c.TplName = "video.tpl" 90 | break 91 | 92 | default: 93 | panic(errors.New("文件暂时不支持格式")) 94 | } 95 | 96 | } 97 | 98 | // 2 method below only for test 99 | func (c *PreviewController) GetFile() { 100 | fName := c.GetString("file") 101 | 102 | c.Ctx.Output.ContentType("application/x-pdf") 103 | data, _ := utils.File2Bytes(fName) 104 | c.Ctx.Output.Body(data) 105 | 106 | } 107 | 108 | func (c *PreviewController) AchieveFileForReview() { 109 | fName := c.GetString("file") 110 | 111 | fileType, _, _ := utils.FileTypeVerify(fName) 112 | 113 | switch { 114 | case fileType == "pdf": 115 | c.Ctx.Output.ContentType("application/x-pdf") 116 | break 117 | 118 | case fileType == "image": 119 | c.Ctx.Output.ContentType("image/jpeg") 120 | break 121 | 122 | case fileType == "cad": 123 | c.Ctx.Output.ContentType("application/x-pdf") 124 | break 125 | 126 | case fileType == "office": 127 | c.Ctx.Output.ContentType("application/x-pdf") 128 | break 129 | 130 | case fileType == "txt": 131 | c.Ctx.Output.ContentType("text/plain") 132 | break 133 | 134 | case fileType == "video": 135 | c.Ctx.Output.ContentType("video/mp4") 136 | break 137 | 138 | default: 139 | panic(errors.New("文件暂时不支持格式")) 140 | } 141 | 142 | data, _ := utils.File2Bytes(fName) 143 | c.Ctx.Output.Body(data) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /controllers/default.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | ) 6 | 7 | type MainController struct { 8 | beego.Controller 9 | } 10 | 11 | func (c *MainController) Get() { 12 | c.Data["plm"] = "PLM 文件预览服务" 13 | c.TplName = "index.tpl" 14 | } 15 | -------------------------------------------------------------------------------- /deploy/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/deploy/.DS_Store -------------------------------------------------------------------------------- /deploy/docker/Dockerfile.libre: -------------------------------------------------------------------------------- 1 | FROM sgrio/java:jre_8_ubuntu 2 | MAINTAINER long0419 "borrip0419@gmail.com" 3 | 4 | ENV LANG zh_CN.UTF-8 5 | ENV LC_ALL zh_CN.UTF-8 6 | 7 | WORKDIR /opt/ 8 | 9 | COPY fonts/* /usr/share/fonts/chienes/ 10 | 11 | # libreoffice 12 | RUN apt-get update && apt-get install -y locales &&\ 13 | apt-get install -y locales && apt-get install -y language-pack-zh-hans &&\ 14 | localedef -i zh_CN -c -f UTF-8 -A /usr/share/locale/locale.alias zh_CN.UTF-8 && locale-gen zh_CN.UTF-8 &&\ 15 | apt-get install -y tzdata && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\ 16 | apt-get install -y libxrender1 && apt-get install -y libxt6 && apt-get install -y libxext-dev && apt-get install -y libfreetype6-dev &&\ 17 | apt-get install -y wget && apt-get install -y ttf-mscorefonts-installer && apt-get install -y fontconfig &&\ 18 | cd /tmp &&\ 19 | apt-get update && \ 20 | apt-get -y -q install \ 21 | libreoffice \ 22 | libreoffice-writer \ 23 | ure \ 24 | libreoffice-java-common \ 25 | libreoffice-core \ 26 | libreoffice-common \ 27 | openjdk-8-jre \ 28 | fonts-opensymbol \ 29 | hyphen-fr \ 30 | hyphen-de \ 31 | hyphen-en-us \ 32 | hyphen-it \ 33 | hyphen-ru \ 34 | fonts-dejavu \ 35 | fonts-dejavu-core \ 36 | fonts-dejavu-extra \ 37 | fonts-droid-fallback \ 38 | fonts-dustin \ 39 | fonts-f500 \ 40 | fonts-fanwood \ 41 | fonts-freefont-ttf \ 42 | fonts-liberation \ 43 | fonts-lmodern \ 44 | fonts-lyx \ 45 | fonts-sil-gentium \ 46 | fonts-texgyre \ 47 | fonts-tlwg-purisa && \ 48 | apt-get -y -q remove libreoffice-gnome && \ 49 | apt -y autoremove && \ 50 | rm -rf /var/lib/apt/lists/* && \ 51 | cd /usr/share/fonts/chienes &&\ 52 | mkfontscale &&\ 53 | mkfontdir &&\ 54 | fc-cache -fv 55 | 56 | RUN adduser --home=/opt/libreoffice --disabled-password --gecos "" --shell=/bin/bash libreoffice -------------------------------------------------------------------------------- /deploy/docker/Dockerfile.plm: -------------------------------------------------------------------------------- 1 | FROM registry.cn-hangzhou.aliyuncs.com/plm-services/plm_files_preview_base:latest 2 | MAINTAINER long0419 "borrip0419@gmail.com" 3 | 4 | ENV LANG zh_CN.UTF-8 5 | ENV LC_ALL zh_CN.UTF-8 6 | 7 | WORKDIR /opt/ 8 | 9 | EXPOSE 8079 10 | 11 | ADD plm-files-preview.tar.gz . 12 | 13 | ENTRYPOINT ["nohup","/opt/plm-files-preview","&"] 14 | -------------------------------------------------------------------------------- /deploy/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | 4 | plm-file-preview: 5 | image: registry.cn-hangzhou.aliyuncs.com/plm-services/plm_files_preview:latest 6 | container_name: plm-file-preview-services-0.1 7 | ports: 8 | - 8079:8079 9 | #volumes: 10 | #- "fonts:/usr/share/fonts/chienes" 11 | command: 12 | - /bin/sh 13 | - -c 14 | - | 15 | cd /usr/share/fonts/chienes 16 | mkfontscale 17 | mkfontdir 18 | fc-cache -fv 19 | -------------------------------------------------------------------------------- /deploy/docker/fonts/SIMFANG.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/deploy/docker/fonts/SIMFANG.TTF -------------------------------------------------------------------------------- /deploy/docker/plm-files-preview.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/deploy/docker/plm-files-preview.tar.gz -------------------------------------------------------------------------------- /deploy/k8s/file-preview-ns.yaml: -------------------------------------------------------------------------------- 1 | ################################################################################################## 2 | # namespaces 3 | ################################################################################################## 4 | apiVersion: v1 5 | kind: Namespace 6 | metadata: 7 | name: file-preview 8 | labels: 9 | name: file-preview 10 | --- -------------------------------------------------------------------------------- /deploy/k8s/file-preview-svc.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2020 PLM xulong 2 | # 3 | ################################################################################################## 4 | # FilePreiew service 5 | ################################################################################################## 6 | apiVersion: v1 7 | kind: Service 8 | metadata: 9 | name: plm-file-preview 10 | namespace: file-preview 11 | labels: 12 | app: plm-file-preview 13 | service: plm-file-preview 14 | spec: 15 | ports: 16 | - port: 8079 17 | name: http 18 | selector: 19 | app: plm-file-preview 20 | --- 21 | apiVersion: v1 22 | kind: ServiceAccount 23 | metadata: 24 | name: plm-file-preview 25 | namespace: file-preview 26 | labels: 27 | account: plm-file-preview 28 | --- 29 | apiVersion: apps/v1 30 | kind: Deployment 31 | metadata: 32 | name: plm-file-preview 33 | namespace: file-preview 34 | labels: 35 | app: plm-file-preview 36 | version: v1 37 | spec: 38 | replicas: 3 39 | selector: 40 | matchLabels: 41 | app: plm-file-preview 42 | version: v1 43 | template: 44 | metadata: 45 | labels: 46 | app: plm-file-preview 47 | version: v1 48 | spec: 49 | serviceAccountName: plm-file-preview 50 | containers: 51 | - name: plm-file-preview 52 | image: registry.cn-shenzhen.aliyuncs.com/plm-services/plm-files-preview:0.1 53 | imagePullPolicy: IfNotPresent 54 | ports: 55 | - containerPort: 8079 56 | --- -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mileworks/plm-files-preview 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/astaxie/beego v1.12.1 7 | github.com/mholt/archiver/v3 v3.3.0 8 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect 9 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect 10 | golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect 11 | golang.org/x/text v0.3.3 // indirect 12 | gopkg.in/yaml.v2 v2.3.0 // indirect 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | _ "github.com/mileworks/plm-files-preview/routers" 6 | ) 7 | 8 | // /Users/mac/go/bin/bee pack -be GOOS=linux -be GOARCH=amd64 9 | func main() { 10 | beego.Run() 11 | } 12 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mholt/archiver/v3" 5 | "testing" 6 | ) 7 | 8 | func TestUnzip(t *testing.T) { 9 | archiver.Unarchive("/Users/mac/Downloads/AutoVue配置操作手册.zip", "tmp/decompress") 10 | 11 | } -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/mileworks/plm-files-preview/controllers" 6 | ) 7 | 8 | func init() { 9 | 10 | beego.Router("/", &controllers.MainController{}) 11 | 12 | //beego.Router("/api/preview", &controllers.PreviewController{}, "get:Preview;post:PreviewFile") 13 | beego.Router("/api/preview", &controllers.PreviewController{}, "get:Preview") 14 | beego.Router("/api/getfile", &controllers.PreviewController{}, "get:GetFile") 15 | beego.Router("/api/review", &controllers.PreviewController{}, "get:AchieveFileForReview") 16 | 17 | beego.Router("/api/plmfile", &controllers.PLMFileController{}, "post:PLMPreview") 18 | 19 | //beego.Router("/api/create", &controllers.PreviewController{}, "post:CreatePreview") 20 | //beego.Router("/api/update", &controllers.PreviewController{}, "put:UpdatePreview") 21 | //beego.Router("/api/delete", &controllers.PreviewController{}, "delete:DeletePreview") 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tmp/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/tmp/bg.jpg -------------------------------------------------------------------------------- /tmp/convert/.convert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/tmp/convert/.convert -------------------------------------------------------------------------------- /tmp/decompress/.decompress: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/tmp/decompress/.decompress -------------------------------------------------------------------------------- /tmp/download/.download: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mileworks/Golang-Files-Preview/b61573da4a52f3d675f063b3a7b7f744fe853453/tmp/download/.download -------------------------------------------------------------------------------- /utils/convert.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "path" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | func ConvertFromCADToPDF(filePath string) string { 10 | basePath := "tmp/convert/" + GetFileNameOnly(filePath) + ".dwg.pdf" 11 | if FileExist(basePath) { 12 | return basePath 13 | } else { 14 | commandName := "java" 15 | params := []string{"-jar", "cad/cad-preview-addon-1.0-SNAPSHOT.jar", "-s", filePath, "-t", filePath + ".svg"} 16 | 17 | if _, ok := InteractiveToexec(commandName, params); ok { 18 | resultPath := filePath + ".svg" 19 | if ok, _ := PathExists(resultPath); ok { 20 | return ConvertToPDF(resultPath) 21 | } else { 22 | return "" 23 | } 24 | } else { 25 | return "" 26 | } 27 | } 28 | } 29 | 30 | func ConvertToPDF(filePath string) string { 31 | 32 | basePath := "tmp/convert/" + GetFileNameOnly(filePath) + ".pdf" 33 | if FileExist(basePath) { 34 | return basePath 35 | } else { 36 | commandName := "" 37 | var params []string 38 | if runtime.GOOS == "windows" { 39 | commandName = "cmd" 40 | params = []string{"/c", "soffice", "--headless", "--invisible", "--convert-to", "pdf", "--outdir", "tmp/convert/", filePath} 41 | } else if runtime.GOOS == "linux" { 42 | commandName = "libreoffice" 43 | params = []string{"--invisible", "--headless", "--convert-to", "pdf", "--outdir", "tmp/convert/", filePath} 44 | } else { // https://ask.libreoffice.org/en/question/12084/how-to-convert-documents-to-pdf-on-osx/ 45 | commandName = "/Applications/LibreOffice.app/Contents/MacOS/soffice" 46 | params = []string{"--headless", "--convert-to", "pdf", "--outdir", "tmp/convert/", filePath} 47 | } 48 | 49 | if _, ok := InteractiveToexec(commandName, params); ok { 50 | 51 | paths := strings.Split(path.Base(filePath), ".") 52 | 53 | var tmp = "" 54 | for i, n := 0, len(paths)-1; i < n; i++ { 55 | tmp += "." + paths[i] 56 | } 57 | resultPath := "tmp/convert/" + tmp[1:] + ".pdf" 58 | return resultPath 59 | } else { 60 | return "" 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /utils/download.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "log" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "path" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | func IsFileExist(filename string, filesize int64) bool { 16 | info, err := os.Stat(filename) 17 | if os.IsNotExist(err) { 18 | return false 19 | } 20 | if filesize == info.Size() { 21 | return true 22 | } 23 | os.Remove(filename) 24 | return false 25 | } 26 | 27 | func DownloadFile(source string, fileSuffix string, filenameWithSuffix string) (string, error) { 28 | 29 | basePath := "tmp/download/" + filenameWithSuffix 30 | if FileExist(basePath) { 31 | return basePath, nil 32 | } 33 | 34 | var ( 35 | fsize int64 36 | buf = make([]byte, 32*1024) 37 | written int64 38 | ) 39 | tmpFilePath := "tmp" + ".download" 40 | client := new(http.Client) 41 | resp, err := client.Get(source) 42 | if err != nil { 43 | return "", err 44 | } 45 | fsize, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 32) 46 | if err != nil { 47 | log.Println("Error: <", err, "> when get file remote size") 48 | return "", err 49 | } 50 | if IsFileExist("tmp", fsize) { 51 | return "had", nil 52 | } 53 | file, err := os.Create(tmpFilePath) 54 | if err != nil { 55 | return "", err 56 | } 57 | defer file.Close() 58 | if resp.Body == nil { 59 | return "", errors.New("body is null") 60 | } 61 | defer resp.Body.Close() 62 | for { 63 | nr, er := resp.Body.Read(buf) 64 | if nr > 0 { 65 | nw, ew := file.Write(buf[0:nr]) 66 | if nw > 0 { 67 | written += int64(nw) 68 | } 69 | if ew != nil { 70 | err = ew 71 | break 72 | } 73 | if nr != nw { 74 | err = io.ErrShortWrite 75 | break 76 | } 77 | } 78 | if er != nil { 79 | if er != io.EOF { 80 | err = er 81 | } 82 | break 83 | } 84 | } 85 | 86 | if err == nil { 87 | file.Close() 88 | 89 | filenameWithSuffix := path.Base(source) 90 | fileWSuffix := path.Ext(filenameWithSuffix) 91 | filenameOnly := strings.TrimSuffix(filenameWithSuffix, fileWSuffix) 92 | 93 | decodeurl, _ := url.QueryUnescape(filenameOnly) 94 | 95 | newPath := "tmp/download/" + decodeurl + fileSuffix 96 | 97 | log.Println("=== Download file to " + newPath + " ===") 98 | 99 | os.Rename(tmpFilePath, newPath) 100 | return newPath, nil 101 | } 102 | return "", err 103 | } 104 | -------------------------------------------------------------------------------- /utils/files.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/mholt/archiver/v3" 5 | "log" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | /** 13 | * 预览文件相关处理 14 | */ 15 | 16 | var ( 17 | AllOfficeEtx = []string{".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"} 18 | AllImageEtx = []string{".jpeg", ".jpg", ".png", ".gif", ".bmp", ".heic", ".tiff"} 19 | AllCADEtx = []string{".dwg", ".dxf"} 20 | AllAchieveEtx = []string{".tar.gz", ".tar.bzip2", ".tar.xz", ".zip", ".rar", ".tar", ".7z", "br", ".bz2", ".lz4", ".sz", ".xz", ".zstd"} 21 | AllTxtEtx = []string{".txt", ".java", ".php", ".py", ".md", ".js", ".css", ".xml", ".log"} 22 | AllVideoEtx = []string{".mp4", ".webm", ".ogg"} 23 | ) 24 | 25 | func FileTypeVerify(url string) (string, string, string) { 26 | filenameWithSuffix := path.Base(url) //获取文件名带后缀 27 | filesuffix := path.Ext(url) //文件后缀 28 | if strings.Contains(filesuffix, "?") { //单独测试过 29 | filesuffix = filesuffix[0:strings.Index(filesuffix, "?")] 30 | filenameWithSuffix = filenameWithSuffix[0:strings.Index(filenameWithSuffix, "?")] 31 | } 32 | 33 | if strings.Contains(url, ".pdf") { 34 | return "pdf", ".pdf", filenameWithSuffix 35 | } 36 | 37 | for _, x := range AllOfficeEtx { 38 | if filesuffix == x { 39 | return "office", x, filenameWithSuffix 40 | } 41 | } 42 | 43 | for _, x := range AllImageEtx { 44 | if strings.Contains(url, x) { 45 | return "image", x, filenameWithSuffix 46 | } 47 | } 48 | 49 | for _, x := range AllCADEtx { 50 | if strings.Contains(url, x) { 51 | return "cad", x, filenameWithSuffix 52 | } 53 | } 54 | 55 | for _, x := range AllAchieveEtx { 56 | if strings.Contains(url, x) { 57 | return "achieve", x, filenameWithSuffix 58 | } 59 | } 60 | 61 | for _, x := range AllTxtEtx { 62 | if strings.Contains(url, x) { 63 | return "txt", x, filenameWithSuffix 64 | } 65 | } 66 | 67 | for _, x := range AllVideoEtx { 68 | if strings.Contains(url, x) { 69 | return "video", x, filenameWithSuffix 70 | } 71 | } 72 | 73 | return "", "", filenameWithSuffix 74 | 75 | } 76 | 77 | func File2Bytes(filename string) ([]byte, error) { 78 | 79 | // File 80 | file, err := os.Open(filename) 81 | if err != nil { 82 | return nil, err 83 | } 84 | defer file.Close() 85 | 86 | // FileInfo: 87 | stats, err := file.Stat() 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | // []byte 93 | data := make([]byte, stats.Size()) 94 | count, err := file.Read(data) 95 | if err != nil { 96 | return nil, err 97 | } 98 | log.Printf("read file %s len: %d \n", filename, count) 99 | return data, nil 100 | } 101 | 102 | func UnarchiveFiles(file string) { 103 | log.Println("=== Achieve from " + file + " ===") 104 | decompressName := strings.TrimSuffix(path.Base(file), path.Ext(path.Base(file))) 105 | archiver.Unarchive(file, "tmp/decompress/"+decompressName) 106 | } 107 | 108 | func GetFilesFromDirectory(source string) ([]string, string) { 109 | 110 | decompressName := strings.TrimSuffix(path.Base(source), path.Ext(path.Base(source))) 111 | base := "tmp/decompress/" + decompressName 112 | 113 | files, _ := filepath.Glob(filepath.Join(base, "*")) 114 | for i := range files { 115 | // __MACOSX 目录 过滤掉 116 | if strings.Index(files[i], "__MACOSX") == -1 { 117 | base = files[i] 118 | break 119 | } 120 | } 121 | 122 | files, _ = filepath.Glob(filepath.Join(base, "*.*")) 123 | // Mac 过滤__MACOSX 目录 和.DS_Store 文件 124 | var files_result []string 125 | for i := range files { 126 | if strings.Index(files[i], "__MACOSX") == -1 && strings.Index(files[i], ".DS_Store") == -1 { 127 | // 清空字符串名中空格并重命名 128 | os.Rename(files[i], strings.Replace(files[i], " ", "", -1)) 129 | files[i] = strings.Replace(files[i], " ", "", -1) 130 | 131 | files_result = append(files_result, files[i]) 132 | } 133 | } 134 | 135 | return files_result, base[:len(base)-2] 136 | } 137 | 138 | func FileExist(path string) bool { 139 | _, err := os.Lstat(path) 140 | return !os.IsNotExist(err) 141 | } 142 | 143 | func GetFileNameOnly(url string) string { 144 | 145 | filenameWithSuffix := path.Base(url) //获取文件名带后缀 146 | fileSuffix := path.Ext(filenameWithSuffix) //获取文件后缀 147 | filenameOnly := strings.TrimSuffix(filenameWithSuffix, fileSuffix) //获取文件名 148 | 149 | return filenameOnly 150 | } 151 | -------------------------------------------------------------------------------- /utils/path.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func InteractiveToexec(commandName string, params []string) (string, bool) { 11 | cmd := exec.Command(commandName, params...) 12 | buf, err := cmd.Output() 13 | log.Println(cmd.Args) 14 | w := bytes.NewBuffer(nil) 15 | cmd.Stderr = w 16 | log.Printf("%s\n", w) 17 | if err != nil { 18 | log.Println("Error: <", err, "> when exec command read out buffer") 19 | return "", false 20 | } else { 21 | return string(buf), true 22 | } 23 | } 24 | 25 | func PathExists(path string) (bool, error) { 26 | _, err := os.Stat(path) 27 | if err == nil { 28 | return true, nil 29 | } 30 | if os.IsNotExist(err) { 31 | return false, nil 32 | } 33 | return false, err 34 | } 35 | -------------------------------------------------------------------------------- /views/achieve.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |