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 @@ + +[![docker build](https://github.com/lixiaofei123/pdftoolbox/actions/workflows/docker.yml/badge.svg)](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,如下图所示 + +![pdf在线压缩首页](./images/index.jpg) + +点击【点击此处上传】按钮,选择要转换的文件,即可上传。目前支持三种压缩质量 + - 高质量 (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在线工具箱 + + + + + +
+ +
+
+
+ + +
+
+
+
+ + + + +
+ 点击此处上传 +
+
+
+
+
+ 请选择压缩质量 +
高质量(300 dpi)
+
中等质量(150 dpi)
+
低质量(72dpi)
+
+
+ +
+ + + +
+
+ +
+
+ +
+ +
+ + + + + \ 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() +}