This commit is contained in:
lixiaofei123
2021-09-16 21:55:36 +08:00
commit 12c94d8803
15 changed files with 1210 additions and 0 deletions
+33
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
pdftoolbox*
input/
output/
__debug_bin
+18
View File
@@ -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"]
+34
View File
@@ -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文件
+5
View File
@@ -0,0 +1,5 @@
module github.com/lixiaofei123/pdftoolbox
go 1.17
require github.com/google/uuid v1.3.0
+2
View File
@@ -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=
Executable
BIN
View File
Binary file not shown.
+106
View File
@@ -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())
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+235
View File
@@ -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))
}
Executable
BIN
View File
Binary file not shown.
+83
View File
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>PDF在线工具箱</title>
<link rel="stylesheet" type="text/css" href="/upload.css" />
</head>
<body>
<div id="fullscreen">
<div id="nav">
PDF在线工具箱
</div>
<div id="container">
<div id="noscrollbar">
<div class="checkboxGroup">
<div class="checkbox menu active" value="compress">PDF压缩</div>
<div class="checkbox menu" value="ocr">PDF文本提取</div>
</div>
<div id="upload-area">
<div id="upload-box">
<div class="upload-icon">
<svg class="icon" viewBox="0 0 1024 1024" width="80" height="80" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path fill="#eee"
d="M796.2 466.4c0-2.4 0.4-4.8 0.4-7.2 0-130-103.6-235.2-231.4-235.2-92.2 0-171.4 54.8-208.6 134-16.2-8.2-34.4-13-53.6-13-59 0-108.2 43.8-117.6 101C114.6 470.4 64 538.2 64 618c0 100.4 80.2 182 179 182L448 800l0-160-96.4 0 160.4-167.4 160.4 167.2-96.4 0 0 160 220.6 0c90.4 0 163.4-75 163.4-166.8C960 541.2 886.6 466.6 796.2 466.4z">
</path>
</svg>
<br>
<span class="upload-tips">点击此处上传</span>
</div>
</div>
<div class="allow-upload-tips">
<div id="selectQuality">
<div class="checkboxGroup">
<span>请选择压缩质量</span>
<div class="checkbox quality" value="prepress">高质量(300 dpi)</div>
<div class="checkbox quality active" value="ebook">中等质量(150 dpi)</div>
<div class="checkbox quality" value="screen">低质量(72dpi)</div>
</div>
</div>
<div id="selectLanguage" style="display: none;">
<div class="checkboxGroup">
<span>请选择额外语言</span>
<div class="checkbox language" value="chi_tra+chi_tra_vert">繁体中文</div>
<div class="checkbox language" value="jpn+jpn_vert">日文</div>
<div class="checkbox language" value="kor+kor_vert">韩语</div>
</div>
<div class="tips">已经默认支持简体中文和英文,额外选择语言将会降低转换速度</div>
</div>
</div>
</div>
<div id="upload-files">
</div>
</div>
</div>
<div id="foot">
<div class="foot-part foot-left" id="legalStatement">
</div>
<div class="foot-part foot-center">
<span id="icp" style="display: none;"></span>
Power By <a href="https://www.lixf.cc" target="_blank">李小飞</a>
</div>
<div class="foot-part foot-right">
</div>
</div>
</div>
<script src="/upload.js"></script>
</body>
</html>
+306
View File
@@ -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
}
}
+273
View File
@@ -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 = `<div class="upload-file-thumb">
<svg style="margin-top:10px" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100"><path d="M136.533604 0.00026a49.119975 49.119975 0 0 0-35.839982 15.359992C91.307627 25.600247 85.33463 38.40024 85.33463 51.200234v921.599532a49.119975 49.119975 0 0 0 15.359992 35.839982 50.545974 50.545974 0 0 0 35.839982 15.359992h750.931619a49.119975 49.119975 0 0 0 35.839981-15.359992 50.546974 50.546974 0 0 0 15.359993-35.839982V290.134113L648.533344 0.00026z" fill="#FF5562"></path><path d="M938.666197 290.133113H699.733318a52.492973 52.492973 0 0 1-51.199974-51.199974V0.00026z" fill="#FFBBC0" ></path><path d="M730.266302 865.332821c-53.759973 0-101.599948-92.212953-127.146935-151.999923-42.666978-17.919991-89.599955-34.132983-134.826931-45.226977-40.10498 26.560987-107.518945 65.760967-159.624919 65.760966-32.426984 0-55.466972-16.212992-63.999968-44.372977-6.826997-23.039988-0.853-39.25298 5.972997-47.786976q20.47999-28.159986 84.479957-28.159986c34.132983 0 77.652961 5.972997 126.292936 17.919991a762.015613 762.015613 0 0 0 91.306954-75.092962c-12.799994-59.73297-26.452987-156.159921 8.532995-200.532898 17.066991-21.332989 43.519978-28.159986 75.092962-18.77299 34.986982 10.239995 47.786976 31.572984 51.999974 47.786976 14.506993 58.026971-51.999974 136.532931-97.332951 182.666907 10.239995 40.10698 23.039988 81.919958 39.25298 120.319939C695.33332 716.799896 772.080281 759.466874 780.666277 806.399851c3.412998 16.212992-1.706999 31.572984-14.506993 44.372977-11.092994 9.332995-23.039988 14.506993-35.839982 14.506993z m-79.359959-129.706935C683.333326 801.332853 714.000311 831.999838 730.266302 831.999838c2.559999 0 5.972997-0.853 11.092995-5.119998 5.972997-5.972997 5.972997-10.239995 5.119997-13.652993-3.412998-17.066991-30.666984-45.226977-95.572951-77.652961zM335.173503 647.732931c-41.812979 0-53.759973 10.239995-57.172971 14.506993-0.853 1.706999-4.266998 5.972997-0.852999 17.919991 2.559999 10.239995 9.332995 20.47999 31.572984 20.479989 27.306986 0 66.559966-15.359992 112.639942-42.666978-33.332983-6.826997-62.292968-10.239995-86.186956-10.239995z m168.959914-5.119997q41.577979 11.725994 81.919959 27.306986c-9.332995-24.746987-17.066991-50.346974-23.892988-75.092962-18.77299 16.212992-38.399981 32.426984-58.026971 47.786976z m105.866947-275.67986c-9.332995 0-16.212992 3.412998-22.186989 10.239994-17.919991 22.186989-19.62699 78.50696-5.972997 150.186924 51.999974-55.466972 80.212959-106.666946 73.332963-133.972932-0.853-4.266998-4.266998-16.212992-28.159986-23.039988a46.412976 46.412976 0 0 0-17.012991-3.413998z" fill="#FFFFFF"></path></svg>
</div>
<div class="file-links-box" style="display:none">
<a class="download_button">点击下载</a>
<span class="fileinfo"></span>
</div>
<div class="upload-error-cover" style="display:none">
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="30" height="30"><path fill="red" d="M512 53.248c129.707008 3.412992 237.739008 48.299008 324.096 134.656S967.339008 382.292992 970.752 512c-3.412992 129.707008-48.299008 237.739008-134.656 324.096S641.707008 967.339008 512 970.752c-129.707008-3.412992-237.739008-48.299008-324.096-134.656S56.660992 641.707008 53.248 512c3.412992-129.707008 48.299008-237.739008 134.656-324.096S382.292992 56.660992 512 53.248z m0 403.456L405.504 350.208c-8.192-8.192-17.579008-12.288-28.16-12.288-10.580992 0-19.796992 3.924992-27.648 11.776-7.851008 7.851008-11.776 17.067008-11.776 27.648 0 10.580992 4.096 19.968 12.288 28.16l106.496 106.496-106.496 106.496c-8.192 8.192-12.288 17.579008-12.288 28.16 0 10.580992 3.924992 19.796992 11.776 27.648 7.851008 7.851008 17.067008 11.776 27.648 11.776 10.580992 0 19.968-4.096 28.16-12.288l106.496-106.496 106.496 106.496c10.923008 10.24 23.552 13.483008 37.888 9.728s23.380992-12.8 27.136-27.136c3.755008-14.336 0.512-26.964992-9.728-37.888L567.296 512l106.496-106.496c8.192-8.192 12.288-17.579008 12.288-28.16 0-10.580992-3.924992-19.796992-11.776-27.648-7.851008-7.851008-17.067008-11.776-27.648-11.776-10.580992 0-19.968 4.096-28.16 12.288L512 456.704z" ></path></svg>
<div style="height:8px"></div>
<span class="errorReason"></span>
</div>
<div class="upload-progress">
<div class="progress-bar" style="width: 0%;"></div>
</div>
`;
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} 原大小:<b>${wellSize(
task.size
)}</b> 压缩后大小:<b>${wellSize(task.size2)}</b>`;
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;
}
+111
View File
@@ -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()
}