commit 12c94d88030cada9c549f2e48ad24f267facc8b7
Author: lixiaofei123 <326256365@qq.com>
Date: Thu Sep 16 21:55:36 2021 +0800
init
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..d9b094b
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,33 @@
+name: ci
+
+on:
+ push:
+ branches:
+ - 'master'
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v2
+ -
+ name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+ -
+ name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+ -
+ name: Login to DockerHub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ -
+ name: Build and push
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ push: true
+ tags: mrlee326/pdftoolbox:latest
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2b5f922
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+pdftoolbox*
+input/
+output/
+__debug_bin
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..b4382cf
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,18 @@
+FROM golang AS build
+WORKDIR /build
+COPY . .
+ENV GOPROXY https://goproxy.io,direct
+ENV CGO_ENABLED=0
+RUN go build -o pdftoolbox
+
+FROM ubuntu
+RUN apt-get -y update && rm -rf /var/lib/apt/lists/*
+RUN cp gs /usr/bin/gs
+RUN mkdir -p /opt/pdftoolbox
+RUN mkdir -p /opt/pdftoolbox/input
+RUN mkdir -p /opt/pdftoolbox/output
+COPY --from=build /build/pdftoolbox /opt/pdftoolbox/pdftoolbox
+COPY static /opt/pdftoolbox/static
+EXPOSE 8082
+WORKDIR /opt/pdftoolbox/
+ENTRYPOINT ["./pdftoolbox"]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..239902d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+
+[](https://github.com/lixiaofei123/pdftoolbox/actions/workflows/docker.yml)
+
+## 一个简单的在线压缩pdf网站
+
+使用 [GhostScript](https://www.ghostscript.com) 进行压缩
+
+## 使用方法
+
+### 使用Docker部署
+
+建议使用Docker一键部署
+
+```
+docker run -d --name pdftoolbox --restart=always -p 8082:8082 -v /data/pdftoolbox/input:/opt/pdftoolbox/input -v /data/pdftoolbox/output:/opt/pdftoolbox/output mrlee326/pdftoolbox
+```
+
+启动成功后,在浏览器中访问 http://ip:8082,如下图所示
+
+
+
+点击【点击此处上传】按钮,选择要转换的文件,即可上传。目前支持三种压缩质量
+ - 高质量 (300dpi)
+ - 中质量 (150dpi)
+ - 低质量 (72dpi)
+
+### 在Linux上部署
+
+请参考Dockerfile文件
+
+
+
+
+
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..228e4d7
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/lixiaofei123/pdftoolbox
+
+go 1.17
+
+require github.com/google/uuid v1.3.0
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..3dfe1c9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,2 @@
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
diff --git a/gs b/gs
new file mode 100755
index 0000000..3230000
Binary files /dev/null and b/gs differ
diff --git a/gsbin.go b/gsbin.go
new file mode 100644
index 0000000..2b3fe7a
--- /dev/null
+++ b/gsbin.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+ "fmt"
+ "os/exec"
+ "strconv"
+ "strings"
+)
+
+type HandleStatus int
+type CompressSetting string
+
+const (
+ Ready HandleStatus = iota
+ Start
+ Compressing
+ Success
+ Error
+)
+
+const (
+ Prepress CompressSetting = "prepress"
+ Ebook CompressSetting = "ebook"
+ Screen CompressSetting = "screen"
+)
+
+const (
+ English string = "eng"
+ SimplifiedChinese string = "chi_sim+chi_sim_vert"
+ TraditionalChinese string = "chi_tra+chi_tra_vert"
+ Japanse string = "jpn+jpn_vert"
+ korea string = "kor+kor_vert"
+)
+
+var DefaultOCRLanguages []string = []string{English, SimplifiedChinese}
+
+type CommandWriter struct {
+ totalPage int
+ handlePage int
+ progress HandleProgress
+ state HandleStatus
+}
+
+func NewCommandWriter(progress HandleProgress) *CommandWriter {
+ return &CommandWriter{
+ totalPage: 0,
+ handlePage: 0,
+ progress: progress,
+ }
+}
+
+func (w *CommandWriter) Write(p []byte) (n int, err error) {
+
+ output := string(p)
+ lines := strings.Split(output, "\n")
+ for _, line := range lines {
+ fmt.Println(line)
+ if strings.HasPrefix(line, "Processing pages") {
+ w.totalPage, _ = strconv.Atoi(line[strings.Index(line, "through ")+8 : len(line)-1])
+ w.progress(float32(1)/float32(w.totalPage), Start, "")
+ } else if strings.HasPrefix(line, "Page ") {
+ w.handlePage, _ = strconv.Atoi(line[5:])
+ w.progress(float32(w.handlePage)/float32(w.totalPage), Compressing, "")
+ } else if strings.Contains(line, "error") && w.state != Error {
+ reason := line[strings.Index(line, "error")+6:]
+ w.progress(100, Error, reason)
+ w.state = Error
+ }
+ }
+
+ return len(p), nil
+}
+
+type HandleProgress func(progress float32, status HandleStatus, reason string)
+
+func CompressPdf(inputFile string, outputFile string, setting CompressSetting, progress HandleProgress) {
+ cmd := exec.Command("/usr/bin/gs", "-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4", fmt.Sprintf("-dPDFSETTINGS=/%s", string(setting)),
+ "-dNOPAUSE", "-dBATCH", fmt.Sprintf("-sOutputFile=%s", outputFile), inputFile)
+
+ commandWriter := NewCommandWriter(progress)
+ cmd.Stdout = commandWriter
+ cmd.Stderr = commandWriter
+
+ err := cmd.Run()
+ if err == nil {
+ progress(1, Success, "")
+ }
+}
+
+func OCRPdf(inputFile string, outputFile string, languages []string, progress HandleProgress) {
+
+ cmd := exec.Command("/usr/bin/gs", "-sDEVICE=ocr", fmt.Sprintf(`-sOCRLanguage="%s"`, strings.Join(append(DefaultOCRLanguages, languages...), "+")), "-o", outputFile, "-r600", "-dDownScaleFactor=3", inputFile)
+
+ commandWriter := NewCommandWriter(progress)
+ cmd.Stdout = commandWriter
+ cmd.Stderr = commandWriter
+
+ err := cmd.Run()
+ if err == nil {
+ progress(1, Success, "")
+ } else {
+ if commandWriter.state != Error {
+ progress(0, Error, err.Error())
+ }
+ }
+}
diff --git a/images/index.jpg b/images/index.jpg
new file mode 100644
index 0000000..36786af
Binary files /dev/null and b/images/index.jpg differ
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..9bba660
--- /dev/null
+++ b/main.go
@@ -0,0 +1,235 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "path"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+var taskService *TaskService
+
+func init() {
+ taskService = NewTaskService()
+}
+
+func HandleError(err error, rw http.ResponseWriter) {
+ rw.WriteHeader(503)
+ rw.Write([]byte(err.Error()))
+}
+
+func CompressFile(rw http.ResponseWriter, r *http.Request) {
+
+ r.ParseForm()
+
+ file, handler, err := r.FormFile("file")
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+ defer file.Close()
+
+ setting := r.FormValue("setting")
+ if setting == "" {
+ setting = "prepress"
+ }
+
+ fileBytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+ uuid := uuid.NewString()
+
+ inputFile := fmt.Sprintf("input/%s_%s", uuid[:6], handler.Filename)
+ err = ioutil.WriteFile(inputFile, fileBytes, 0666)
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+
+ outFile := fmt.Sprintf("output/%s_%s_%s.txt", setting, uuid[:6], handler.Filename)
+
+ go func(uuid string, dataLen int) {
+
+ defer func() {
+ if err := recover(); err != nil {
+ log.Println(err)
+ }
+ }()
+
+ CompressPdf(inputFile, outFile, CompressSetting(setting), func() HandleProgress {
+
+ task := &Task{
+ ID: uuid,
+ TaskType: Compress,
+ Name: path.Base(inputFile),
+ OutName: path.Base(outFile),
+ CreateTime: time.Now().Unix(),
+ Status: Ready,
+ Progress: 0,
+ Size: int64(dataLen),
+ }
+
+ taskId := taskService.AddTask(task)
+
+ return func(progress float32, status HandleStatus, reason string) {
+ if status == Start {
+ taskService.UpdateTask(taskId, Start, progress, "")
+ } else if status == Compressing {
+ taskService.UpdateTask(taskId, Compressing, progress, "")
+ } else if status == Success {
+ fi, _ := os.Stat(outFile)
+ taskService.UpdateTaskFileSize(taskId, fi.Size())
+ } else if status == Error {
+ taskService.UpdateTask(taskId, Error, progress, reason)
+ }
+ }
+ }())
+
+ }(uuid, len(fileBytes))
+
+ rw.WriteHeader(200)
+ rw.Write([]byte(fmt.Sprintf(`{"taskID" : "%s"}`, uuid)))
+
+}
+
+func OCRFile(rw http.ResponseWriter, r *http.Request) {
+
+ r.ParseForm()
+
+ file, handler, err := r.FormFile("file")
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+ defer file.Close()
+
+ languages := []string{}
+ languagesStr := r.FormValue("languages")
+ if languagesStr != "" {
+ languages = strings.Split(languagesStr, ",")
+ }
+
+ fileBytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+ uuid := uuid.NewString()
+
+ inputFile := fmt.Sprintf("input/%s_%s", uuid[:6], handler.Filename)
+ err = ioutil.WriteFile(inputFile, fileBytes, 0666)
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+
+ outFile := fmt.Sprintf("output/%s_%s.txt", uuid[:6], handler.Filename)
+
+ go func(uuid string, dataLen int) {
+
+ defer func() {
+ if err := recover(); err != nil {
+ log.Println(err)
+ }
+ }()
+
+ OCRPdf(inputFile, outFile, languages, func() HandleProgress {
+
+ task := &Task{
+ ID: uuid,
+ TaskType: OCR,
+ Name: path.Base(inputFile),
+ OutName: path.Base(outFile),
+ CreateTime: time.Now().Unix(),
+ Status: Ready,
+ Progress: 0,
+ Size: int64(dataLen),
+ }
+
+ taskId := taskService.AddTask(task)
+
+ return func(progress float32, status HandleStatus, reason string) {
+ if status == Start {
+ taskService.UpdateTask(taskId, Start, progress, "")
+ } else if status == Compressing {
+ taskService.UpdateTask(taskId, Compressing, progress, "")
+ } else if status == Success {
+ fi, _ := os.Stat(outFile)
+ taskService.UpdateTaskFileSize(taskId, fi.Size())
+ } else if status == Error {
+ taskService.UpdateTask(taskId, Error, progress, reason)
+ }
+ }
+ }())
+
+ }(uuid, len(fileBytes))
+
+ rw.WriteHeader(200)
+ rw.Write([]byte(fmt.Sprintf(`{"taskID" : "%s"}`, uuid)))
+
+}
+
+func GetTask(rw http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ taskId := r.FormValue("taskId")
+ task, err := taskService.GetTask(taskId)
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+
+ data, _ := json.Marshal(task)
+ rw.Write(data)
+}
+
+func Download(rw http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ taskId := r.FormValue("taskId")
+ task, err := taskService.GetTask(taskId)
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+
+ data, err := ioutil.ReadFile(fmt.Sprintf("output/%s", task.OutName))
+ if err != nil {
+ HandleError(err, rw)
+ return
+ }
+
+ rw.Header().Add("Content-Length", strconv.Itoa(len(data)))
+ rw.Header().Add("Content-Type", "application/octet-stream")
+ rw.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, task.OutName))
+ rw.Write(data)
+}
+
+func Tasks(rw http.ResponseWriter, r *http.Request) {
+ tasks := taskService.GetTaskList()
+ data, _ := json.Marshal(tasks)
+ rw.Write(data)
+}
+
+func main() {
+
+ http.HandleFunc("/api/compress", CompressFile)
+ http.HandleFunc("/api/ocr", OCRFile)
+ http.HandleFunc("/api/task", GetTask)
+ http.HandleFunc("/api/tasks", Tasks)
+ http.HandleFunc("/download", Download)
+ fs := http.FileServer(http.Dir("./static"))
+ http.Handle("/", fs)
+
+ log.Println("服务已经启动......")
+ log.Fatal(http.ListenAndServe(":8082", nil))
+
+}
diff --git a/pdfcompress b/pdfcompress
new file mode 100755
index 0000000..22791c0
Binary files /dev/null and b/pdfcompress differ
diff --git a/static/index.html b/static/index.html
new file mode 100644
index 0000000..8dbc66f
--- /dev/null
+++ b/static/index.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+ PDF在线工具箱
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/upload.css b/static/upload.css
new file mode 100644
index 0000000..d9c1af3
--- /dev/null
+++ b/static/upload.css
@@ -0,0 +1,306 @@
+#fullscreen {
+ position: fixed;
+ top: 0px;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ background: #E4E4E4;
+ background-size: cover;
+ background-position: center;
+}
+
+body {
+ padding: 0px;
+ margin: 0px;
+}
+
+#nav {
+ width: calc(100% - 40px);
+ padding: 15px 20px;
+ background: white;
+ text-align: center;
+}
+
+img {
+ padding: 0px;
+ margin: 0px;
+}
+
+#userinfo {
+ float: right;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.buttons {
+ display: inline-block;
+ margin-right: 20px;
+}
+
+.buttons a {
+ color: #666666;
+ text-decoration: none;
+ font-size: 14px;
+ margin-right: 10px;
+}
+
+.buttons a:hover {
+ font-size: 15px;
+}
+
+#avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 20px;
+ overflow: hidden;
+ cursor: pointer;
+ display: inline-block;
+}
+
+#container {
+ width: calc(100% - 20px);
+ max-width: 1320px;
+ height: calc(100% - 100px);
+ margin-left: auto;
+ margin-right: auto;
+ overflow: hidden;
+}
+
+#noscrollbar {
+ padding-top: 20px;
+ width: calc(100% + 17px);
+ height: calc(100% - 120px);
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+
+#upload-area {
+ background: white;
+ padding: 40px 40px 20px 40px;
+ border-radius: 5px;
+ color: #666666;
+}
+
+#upload-box {
+ width: 100%;
+ height: 240px;
+ border: 1px solid rgb(212, 212, 212);
+ border-radius: 5px;
+ cursor: pointer;
+}
+
+#upload-box:hover {
+ border: 1px solid #409EFF;
+}
+
+.upload-icon {
+ text-align: center;
+ padding-top: 60px;
+}
+
+.upload-tips {
+ margin-top: 10px;
+ font-size: 14px;
+}
+
+.allow-upload-tips {
+ margin-top: 10px;
+ font-size: 14px;
+}
+
+#upload-files {
+ margin-top: 20px;
+ display: flex;
+ flex-wrap: wrap
+}
+
+.upload-file-card {
+ width: calc(50% - 12px);
+ height: 123px;
+ margin: 6px;
+ flex-shrink: 1;
+ background: white;
+ position: relative;
+ cursor: pointer;
+ float: left;
+ display: flex;
+}
+
+.upload-file-thumb {
+ width: 160px;
+ flex-shrink: 0;
+ text-align: center;
+}
+
+.file-links-box {
+ flex: 1;
+ padding: 10px;
+}
+
+.download_button {
+ display: block;
+ width: 100px;
+ padding: 5px 10px;
+ background: #069af1;
+ color: white;
+ margin-left: auto;
+ margin-right: auto;
+ text-align: center;
+ margin-top: 10px;
+ font-size: 14px;
+ text-decoration: none;
+}
+
+.download_button:hover{
+ background: #0588d4;
+}
+
+.fileinfo{
+ display: block;
+ margin-top: 10px;
+ font-size: 12px;
+ text-align: center;
+ color: grey;
+}
+
+.link {
+ width: calc(100% - 24px);
+ margin-bottom: 6px;
+ padding: 6px 10px;
+ font-size: 14px;
+ cursor: pointer;
+ border: 1px solid grey;
+ background: #eee;
+}
+
+.copytip {
+ display: inline-block;
+ position: absolute;
+ left: 4px;
+ top: 5px;
+ z-index: 1;
+ padding: 2px 8px;
+ background: #61cc00bd;
+ color: white;
+ border-radius: 2px;
+ font-size: 12px;
+}
+
+.upload-progress {
+ width: 100%;
+ position: absolute;
+ bottom: 0px;
+}
+
+.upload-error-cover {
+ position: absolute;
+ top: 0px;
+ bottom: 5px;
+ left: 0px;
+ right: 0px;
+ z-index: 1;
+ background: rgba(0, 0, 0, 0.5);
+ padding: 15px 5px;
+ color: white;
+ text-align: center;
+ font-size: 12px;
+}
+
+.progress-bar {
+ height: 3px;
+ background-color: #409EFF;
+}
+
+#foot {
+ position: fixed;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
+ padding: 10px 20px;
+ background-color: white;
+ display: flex;
+ justify-content: space-around;
+ color: #666666;
+ font-size: 14px;
+}
+
+#foot a {
+ color: #666666;
+ font-size: 14px;
+}
+
+.foot-part {
+ flex: 1;
+}
+
+.foot-left {
+ text-align: left;
+}
+
+.foot-center {
+ text-align: center;
+}
+
+.foot-right {
+ text-align: right;
+}
+
+.checkboxGroup {
+ display: flex;
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+
+.checkboxGroup .checkbox {
+ padding: 5px 20px;
+ cursor: pointer;
+ border: 1px solid rgb(235, 235, 235);
+ margin-left: 15px;
+}
+
+.checkboxGroup .checkbox:hover {
+ background: #409EFF;
+ color: white;
+}
+
+.menu{
+ padding: 10px 20px;
+ background: white;
+}
+
+
+.active {
+ background: #409EFF;
+ color: white;
+}
+
+
+@media screen and (max-width: 767px) {
+ .foot-left {
+ display: none;
+ }
+
+ .foot-right {
+ display: none;
+ }
+
+ .upload-file-card {
+ width: 100%;
+ }
+
+ #noscrollbar {
+ padding-top: 20px;
+ width: 100%;
+ height: calc(100% - 60px);
+ }
+
+ #upload-area {
+ padding: 20px;
+ }
+
+ #noscrollbar::-webkit-scrollbar {
+ width: 0 !important
+ }
+
+}
\ No newline at end of file
diff --git a/static/upload.js b/static/upload.js
new file mode 100644
index 0000000..388dd08
--- /dev/null
+++ b/static/upload.js
@@ -0,0 +1,273 @@
+let uploadArea = document.getElementById("upload-box");
+let uploadFileCards = document.getElementById("upload-files");
+let selectQuality = document.getElementById("selectQuality");
+let selectLanguage = document.getElementById("selectLanguage");
+
+let uploadMaxFileSize = 1024 * 1024 * 1024;
+let setting = "ebook";
+let action = "compress"
+let languages = ""
+
+window.onload = function () {
+ addUploadEventListener();
+ addSettingBtnListener();
+};
+
+function addSettingBtnListener() {
+ let btns = document.getElementsByClassName("quality");
+ for (let btn of btns) {
+ btn.addEventListener("click", function () {
+ for (let btn0 of document.getElementsByClassName("quality")) {
+ btn0.className = "checkbox quality";
+ }
+ btn.className = "checkbox quality active";
+ setting = btn.getAttribute("value");
+ });
+ }
+
+ btns = document.getElementsByClassName("menu");
+ for (let btn of btns) {
+ btn.addEventListener("click", function () {
+ for (let btn0 of document.getElementsByClassName("menu")) {
+ btn0.className = "checkbox menu";
+ }
+ btn.className = "checkbox menu active";
+ action = btn.getAttribute("value");
+ if (action === "compress") {
+ selectQuality.style.display = "block";
+ selectLanguage.style.display = "none";
+ } else if (action === "ocr") {
+ selectQuality.style.display = "none";
+ selectLanguage.style.display = "block";
+ }
+ });
+ }
+
+ btns = document.getElementsByClassName("language");
+
+ for (let btn of btns) {
+ btn.addEventListener("click",function(){
+
+ if(btn.className.indexOf("active") === -1){
+ btn.className = "checkbox language active"
+ }else{
+ btn.className = "checkbox language"
+ }
+ languages = ""
+ for(let btn0 of document.getElementsByClassName("language")){
+ if(btn0.className.indexOf("active") !== -1){
+ languages = languages + "," + btn0.getAttribute("value")
+ }
+ }
+ languages = languages.substr(1)
+ })
+
+ }
+}
+
+function addUploadEventListener() {
+ uploadArea.addEventListener("click", () => {
+ let fileInput = document.createElement("input");
+ fileInput.setAttribute("type", "file");
+ fileInput.setAttribute("style", "visibility:hidden");
+ fileInput.setAttribute("multiple", "multiple");
+ fileInput.setAttribute("accept", "application/pdf");
+ fileInput.addEventListener("change", function () {
+ for (let i = 0; i < this.files.length && i < 10; i++) {
+ uploadFile(this.files[i]);
+ }
+ });
+ fileInput.click();
+ });
+}
+
+function wellSize(num) {
+ if (num <= 1024) {
+ return num + "byte";
+ }
+ if (num <= 1024 * 1024) {
+ return (num / 1024).toFixed(2) + "kb";
+ }
+ if (num <= 1024 * 1024 * 1024) {
+ return (num / 1024 / 1024).toFixed(2) + "mb";
+ }
+ if (num <= 1024 * 1024 * 1024 * 1024) {
+ return (num / 1024 / 1024 / 1024).toFixed(2) + "gb";
+ }
+
+ return (num / 1024 / 1024 / 1024 / 1024).toFixed(2) + "tb";
+}
+
+function addUploadCard(callback) {
+ callback = callback || function () {};
+ let addCard = () => {
+ let cardHtml = `
+
+
+
+ `;
+ let card = document.createElement("div");
+ card.className = "upload-file-card";
+ card.innerHTML = cardHtml;
+ uploadFileCards.prepend(card);
+ let progressBar = card.getElementsByClassName("progress-bar")[0];
+ let errReason = card.getElementsByClassName("errorReason")[0];
+ let uploadErrorCover = card.getElementsByClassName("upload-error-cover")[0];
+ let downloadButton = card.getElementsByClassName("download_button")[0];
+ let fileinfo = card.getElementsByClassName("fileinfo")[0];
+ let fileLinksBox = card.getElementsByClassName("file-links-box")[0];
+ let setErrorInfo = function (errorText) {
+ progressBar.style.background = "#F56C6C";
+ errReason.innerText = errorText;
+ uploadErrorCover.style.display = "block";
+ };
+ callback(
+ (percent) => {
+ progressBar.style.width = percent + "%";
+ },
+ (taskID) => {
+ let timer = setInterval(() => {
+ getTaskStatus(
+ taskID,
+ function (task) {
+ if (task.status === 3 || task.status === 4) {
+ clearInterval(timer);
+ }
+ if (task.status < 3) {
+ // 下载进度
+ progressBar.style.background = "#ffc940";
+ progressBar.style.width = task.progress * 100 + "%";
+ }
+
+ if (task.status === 3) {
+ progressBar.style.background = "#ffc940";
+ progressBar.style.width = task.progress * 100 + "%";
+ downloadButton.setAttribute(
+ "href",
+ `/download?taskId=${taskID}`
+ );
+
+ if(task.taskType === "compress"){
+ fileinfo.innerHTML = `${task.name} 原大小:${wellSize(
+ task.size
+ )} 压缩后大小:${wellSize(task.size2)}`;
+ fileLinksBox.style.display = "block";
+ }else if(task.taskType === "ocr"){
+ fileinfo.innerHTML = `${task.name} 转换完毕`;
+ fileLinksBox.style.display = "block";
+ }
+
+ }
+
+ if (task.status === 4) {
+ progressBar.style.background = "red";
+ progressBar.style.width = task.progress * 100 + "%";
+ setErrorInfo(`转换失败,原因是:${task.reason}`);
+ }
+ },
+ function () {
+ clearInterval(timer);
+ setErrorInfo("转换失败");
+ }
+ );
+ }, 500);
+ },
+ (reason) => {
+ setErrorInfo(reason);
+ }
+ );
+ };
+ addCard();
+}
+
+function getTaskStatus(taskID, callback, errCalback) {
+ callback = callback || function () {};
+ errCalback = errCalback || function () {};
+ let request = new XMLHttpRequest();
+ request.open("GET", `/api/task?taskId=${taskID}`);
+ request.addEventListener("load", (e) => {
+ let resp = JSON.parse(request.response);
+ callback(resp);
+ });
+
+ request.addEventListener("error", (e) => {
+ errCalback("获取状态出错");
+ });
+
+ request.send();
+}
+
+function uploadFile(file) {
+ addUploadCard((setProgress, uploadSuccess, uploadError) => {
+ // 检查是否允许上传
+ let size = file.size;
+ if (size > uploadMaxFileSize) {
+ uploadError("超过了上传文件最大限制");
+ return;
+ }
+ ajaxUploadFile(file, setProgress, uploadSuccess, uploadError);
+ });
+}
+
+function ajaxUploadFile(file, setProgress, uploadSuccess, uploadError) {
+ let formData = new FormData();
+ formData.append("file", file);
+ formData.append("setting", setting);
+ formData.append("languages", languages);
+
+ let request = new XMLHttpRequest();
+ request.open("POST", `/api/${action}`);
+ request.upload.addEventListener("progress", (e) => {
+ let percent_complete = (e.loaded / e.total) * 100;
+ setProgress(percent_complete);
+ });
+
+ request.addEventListener("load", (e) => {
+ let resp = JSON.parse(request.response);
+ if (request.status === 200) {
+ uploadSuccess(resp.taskID);
+ }
+ });
+
+ request.addEventListener("error", (e) => {
+ uploadError("上传出错");
+ });
+
+ request.send(formData);
+}
+
+function isMobile() {
+ var userAgentInfo = navigator.userAgent;
+ var mobileAgents = [
+ "Android",
+ "iPhone",
+ "SymbianOS",
+ "Windows Phone",
+ "iPad",
+ "iPod",
+ ];
+ var mobile_flag = false;
+ for (var v = 0; v < mobileAgents.length; v++) {
+ if (userAgentInfo.indexOf(mobileAgents[v]) > 0) {
+ mobile_flag = true;
+ break;
+ }
+ }
+ var screen_width = window.screen.width;
+ var screen_height = window.screen.height;
+ if (screen_width < 500 && screen_height < 800) {
+ mobile_flag = true;
+ }
+ return mobile_flag;
+}
diff --git a/task.go b/task.go
new file mode 100644
index 0000000..46d9409
--- /dev/null
+++ b/task.go
@@ -0,0 +1,111 @@
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+type TaskType string
+
+const (
+ Compress TaskType = "compress"
+ OCR TaskType = "ocr"
+)
+
+type Task struct {
+ ID string `json:"id"`
+ TaskType TaskType `json:"taskType"`
+ CreateTime int64 `json:"createTime"`
+ Name string `json:"name"`
+ OutName string `json:"outName"`
+ Status HandleStatus `json:"status"`
+ Progress float32 `json:"progress"`
+ Reason string `json:"reason"`
+ Size int64 `json:"size"`
+ Size2 int64 `json:"size2"`
+}
+
+type TaskService struct {
+ taskList *sync.Map
+ lock *sync.RWMutex
+}
+
+func NewTaskService() *TaskService {
+ taskList := sync.Map{}
+ lock := sync.RWMutex{}
+ return &TaskService{
+ taskList: &taskList,
+ lock: &lock,
+ }
+}
+
+func (t *TaskService) AddTask(task *Task) string {
+
+ t.lock.Lock()
+ t.taskList.Store(task.ID, task)
+ t.lock.Unlock()
+ return task.ID
+}
+
+func (t *TaskService) GetTask(id string) (*Task, error) {
+ t.lock.RLock()
+ task, ok := t.taskList.Load(id)
+ t.lock.RUnlock()
+
+ if ok {
+ return task.(*Task), nil
+ }
+ return nil, fmt.Errorf("unexist task [%s]", id)
+}
+
+func (t *TaskService) GetTaskList() []*Task {
+ tasks := []*Task{}
+ t.lock.RLock()
+ t.taskList.Range(func(key, value interface{}) bool {
+ tasks = append(tasks, value.(*Task))
+ return true
+ })
+ t.lock.RUnlock()
+
+ return tasks
+}
+
+func (t *TaskService) UpdateTask(id string, status HandleStatus, progress float32, reason string) error {
+ t.lock.RLock()
+ taskInter, ok := t.taskList.Load(id)
+ t.lock.RUnlock()
+
+ if ok {
+ t.lock.Lock()
+ task := taskInter.(*Task)
+ task.Status = status
+ task.Progress = progress
+ task.Reason = reason
+ t.taskList.Store(id, task)
+ t.lock.Unlock()
+ }
+ return fmt.Errorf("unexist task [%s]", id)
+}
+
+func (t *TaskService) UpdateTaskFileSize(id string, size2 int64) error {
+ t.lock.RLock()
+ taskInter, ok := t.taskList.Load(id)
+ t.lock.RUnlock()
+
+ if ok {
+ t.lock.Lock()
+ task := taskInter.(*Task)
+ task.Status = Success
+ task.Progress = 1
+ task.Size2 = size2
+ t.taskList.Store(id, task)
+ t.lock.Unlock()
+ }
+ return fmt.Errorf("unexist task [%s]", id)
+}
+
+func (t *TaskService) DelTask(id string) {
+ t.lock.Lock()
+ t.taskList.Delete(id)
+ t.lock.Unlock()
+}