From 7de809b1dec73f602983b663b610a66e843350d7 Mon Sep 17 00:00:00 2001 From: Jeremy Yu Date: Sat, 28 Dec 2024 15:53:53 +0800 Subject: [PATCH] feat: format server import method: from commonJs to EsModule --- service/package-lock.json | 20 +++ service/package.json | 1 + service/src/configs.ts | 14 +- service/src/control/api.ts | 2 +- service/src/control/router.ts | 14 +- service/src/main.ts | 14 +- service/src/service/design.ts | 215 ++++++++++++++------------- service/src/service/files.ts | 85 ++++++----- service/src/service/screenshots.ts | 193 ++++++++++++------------ service/src/service/user.ts | 4 +- service/src/utils/download-single.ts | 5 +- service/src/utils/download.ts | 6 +- service/src/utils/fs.ts | 128 ++++++++-------- service/src/utils/http.ts | 4 +- service/src/utils/node-queue.ts | 4 +- service/src/utils/timeout.ts | 2 +- service/src/utils/tools.ts | 120 +++++++-------- service/src/utils/uuid.ts | 4 +- service/tsconfig.json | 2 +- service/webpack.config.js | 6 + 20 files changed, 440 insertions(+), 403 deletions(-) diff --git a/service/package-lock.json b/service/package-lock.json index 096265c..337c277 100644 --- a/service/package-lock.json +++ b/service/package-lock.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@types/express": "^4.17.21", + "@types/multiparty": "^4.2.1", "@types/node": "^16.18.105", "cross-env": "^7.0.3", "ts-loader": "^6.0.4", @@ -247,6 +248,16 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/multiparty": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@types/multiparty/-/multiparty-4.2.1.tgz", + "integrity": "sha512-Wi6aK3FgvHLvCDxD7ngG4w8MsCK9h64EB53Gvc8t7FVX81tleiz8vFS3ebBohGxqHRzNGHaNwhfdxTGOGAXm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "16.18.105", "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.105.tgz", @@ -4007,6 +4018,15 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "@types/multiparty": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/@types/multiparty/-/multiparty-4.2.1.tgz", + "integrity": "sha512-Wi6aK3FgvHLvCDxD7ngG4w8MsCK9h64EB53Gvc8t7FVX81tleiz8vFS3ebBohGxqHRzNGHaNwhfdxTGOGAXm6A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "16.18.105", "resolved": "https://registry.npmmirror.com/@types/node/-/node-16.18.105.tgz", diff --git a/service/package.json b/service/package.json index 9448d36..1dad8ae 100644 --- a/service/package.json +++ b/service/package.json @@ -27,6 +27,7 @@ }, "devDependencies": { "@types/express": "^4.17.21", + "@types/multiparty": "^4.2.1", "@types/node": "^16.18.105", "cross-env": "^7.0.3", "ts-loader": "^6.0.4", diff --git a/service/src/configs.ts b/service/src/configs.ts index bddcf9d..95150e5 100644 --- a/service/src/configs.ts +++ b/service/src/configs.ts @@ -17,34 +17,34 @@ const serviceComfig = { /** * 端口号 */ -exports.servicePort = serviceComfig.port +export const servicePort = serviceComfig.port /** * 前端绘制页地址 */ -exports.drawLink = isDev ? 'http://127.0.0.1:5173/draw' : serviceComfig.website + '/draw' +export const drawLink = isDev ? 'http://127.0.0.1:5173/draw' : serviceComfig.website + '/draw' /** * 图片缓存目录位置,根据实际情况调整 */ -exports.filePath = isDev ? process.cwd() + `/static/` : serviceComfig.filePath +export const filePath = isDev ? process.cwd() + `/static/` : serviceComfig.filePath /** * 配置服务器端的chrome浏览器位置 */ -exports.executablePath = isDev ? null : '/opt/google/chrome-unstable/chrome' +export const executablePath = isDev ? null : '/opt/google/chrome-unstable/chrome' /** * 截图并发数上限 */ -exports.maxNum = 2 +export const maxNum = 2 /** * 截图队列的阈值,超出时请求将会被熔断 */ -exports.upperLimit = 20 +export const upperLimit = 20 /** * 多久释放浏览器驻留内存,单位:秒(多标签页版生效) */ -exports.releaseTime = 300 +export const releaseTime = 300 diff --git a/service/src/control/api.ts b/service/src/control/api.ts index df51e1a..bf7a724 100644 --- a/service/src/control/api.ts +++ b/service/src/control/api.ts @@ -7,7 +7,7 @@ */ let path = '/api' -module.exports = { +export default { SCREENGHOT: path + '/screenshots', PRINTSCREEN: path + '/printscreen', // 后端示例 diff --git a/service/src/control/router.ts b/service/src/control/router.ts index c6c8a57..53ab640 100644 --- a/service/src/control/router.ts +++ b/service/src/control/router.ts @@ -5,12 +5,12 @@ * @LastEditors: ShawnPhang * @LastEditTime: 2024-08-12 13:40:13 */ -const rExpress = require('express') -const screenshots = require('../service/screenshots.ts') -const fileService = require('../service/files.ts') -const userService = require('../service/user.ts') -const designService = require('../service/design.ts') -const api = require('./api.ts') +import rExpress from 'express' +import screenshots from '../service/screenshots' +import fileService from '../service/files' +import userService from '../service/user' +import designService from '../service/design' +import api from './api' const rRouter = rExpress.Router() rRouter.get(api.SCREENGHOT, screenshots.screenshots) @@ -23,4 +23,4 @@ rRouter.get(api.GET_MATERIAL, designService.getMaterial) rRouter.get(api.GET_PHOTOS, designService.getPhotos) rRouter.post(api.UPDATE_TEMPLATE, designService.saveTemplate) -module.exports = rRouter +export default rRouter diff --git a/service/src/main.ts b/service/src/main.ts index d65a54c..ee68938 100644 --- a/service/src/main.ts +++ b/service/src/main.ts @@ -5,13 +5,13 @@ * @LastEditors: ShawnPhang * @LastEditTime: 2024-11-14 17:36:17 */ -const express = require('express') -const bodyParser = require('body-parser') -const fs = require('fs') -// const path = require('path') -const router = require('./control/router.ts') -const { filePath, servicePort } = require('./configs.ts') -const handleTimeout = require('./utils/timeout.ts') + +import express from 'express' +import bodyParser from 'body-parser' +import fs from 'fs' +import router from './control/router' +import { filePath, servicePort } from './configs' +import handleTimeout from './utils/timeout' const port = process.env.PORT || servicePort const app = express() diff --git a/service/src/service/design.ts b/service/src/service/design.ts index 891d9ea..153a73b 100644 --- a/service/src/service/design.ts +++ b/service/src/service/design.ts @@ -6,111 +6,120 @@ * @LastEditTime: 2024-08-17 11:22:42 */ import { Request, Response } from 'express' -const fs = require('fs') -const path = require('path') -const axios = require('../utils/http.ts') -const multiparty = require('multiparty') -const { filePath } = require('../configs.ts') -const { checkCreateFolder, randomCode, send } = require('../utils/tools.ts') +import fs from 'fs' +import path from 'path' +import axios from '../utils/http' +import multiparty from 'multiparty' +import { filePath } from '../configs' +import { checkCreateFolder, randomCode, send } from '../utils/tools' const FileUrl = 'http://localhost:7001/static/' -module.exports = { - // design/list 获取模板列表(虚拟) - async getTemplates(req: any, res: Response) { - /** - * @api {get} /design/list 获取模板列表(虚拟) - * @apiVersion 1.0.0 - * @apiGroup design - */ - const { cate, type } = req.query - const tempPath = type == 1 ? `../mock/components/list/${cate}.json` : '../mock/templates/list.json' - try { - const list = fs.readFileSync(path.resolve(__dirname, tempPath), 'utf8') - send.success(res, { list: JSON.parse(list) }) - } catch (error) {} - }, - // design/temp 获取模板(虚拟) - async getDetail(req: any, res: Response) { - /** - * @api {get} /design/list 获取模板(虚拟) - * @apiVersion 1.0.0 - * @apiGroup design - */ - const { cate, type, id } = req.query - const dPath = type == 1 ? `../mock/components/detail/${id}.json` : `../mock/templates/${id}.json` - try { - const detail = fs.readFileSync(path.resolve(__dirname, dPath), 'utf8') - send.success(res, JSON.parse(detail)) - } catch (error) {} - }, - // design/material 获取素材(虚拟) - async getMaterial(req: any, res: any) { - /** - * @api {get} /design/material 获取素材(虚拟) - * @apiVersion 1.0.0 - * @apiGroup design - */ - const { cate } = req.query - try { - const detail = fs.readFileSync(path.resolve(__dirname, `../mock/materials/${cate}.json`), 'utf8') - send.success(res, { list: JSON.parse(detail) }) - } catch (error) { - console.log(error) - } - }, - // design/imgs 获取照片素材(虚拟) - async getPhotos(req: any, res: any) { - /** - * @api {get} /design/imgs 获取照片素材(虚拟) - * @apiVersion 1.0.0 - * @apiGroup design - */ - const { cate } = req.query - try { - const detail = fs.readFileSync(path.resolve(__dirname, `../mock/materials/photos/${cate}.json`), 'utf8') - send.success(res, { list: JSON.parse(detail) }) - } catch (error) {} - }, - // design/edit 保存模板(虚拟) - async saveTemplate(req: any, res: any) { - /** - * @api {post} /design/edit 保存模板(虚拟) - * @apiVersion 1.0.0 - * @apiGroup design - */ - let { id, title, data, width, height, type, cate, tag } = req.body - const folder = type == 1 ? 'components/detail' : 'templates' - const listPath = type == 1 ? 'components/list/comp.json' : 'templates/list.json' - try { - const isAdd = !id // 是否新增模板 - id = id || randomCode(8) - const savePath = path.resolve(__dirname, `../mock/${folder}/${id}.json`) - const jsonData = { - id, - data, - title, - width, - height, - } - fs.writeFileSync(savePath, JSON.stringify(jsonData)) - // 生成封面 - const size = width > height ? 640 : 320 - const fetchScreenshotUrl = `http://localhost:7001/api/screenshots?tempid=${id}&tempType=${type}&width=${width}&height=${height}&type=cover&size=${size}&quality=75` - await axios.get(fetchScreenshotUrl, { responseType: 'arraybuffer' }) - // 保存到其他地方可以设置 responseType: 'arraybuffer' 后操作buffer,这里只为了得到封面,发起请求就可以了 - if (isAdd) { - const listVal = fs.readFileSync(path.resolve(__dirname, `../mock/${listPath}`), 'utf8') - const list = JSON.parse(listVal) - const cover = type == 1 ? FileUrl + `/${id}-screenshot.png` : FileUrl + `/${id}-cover.jpg` - list.unshift({ id, cover, title, width, height }) - fs.writeFileSync(path.resolve(__dirname, `../mock/${listPath}`), JSON.stringify(list)) - } - send.success(res, { id }) - } catch (error) { - console.log(error) - } - }, + +// design/list 获取模板列表(虚拟) +export async function getTemplates(req: any, res: Response) { + /** + * @api {get} /design/list 获取模板列表(虚拟) + * @apiVersion 1.0.0 + * @apiGroup design + */ + const { cate, type } = req.query + const tempPath = type == 1 ? `../mock/components/list/${cate}.json` : '../mock/templates/list.json' + try { + const list = fs.readFileSync(path.resolve(__dirname, tempPath), 'utf8') + send.success(res, { list: JSON.parse(list) }) + } catch (error) {} } -export {} +// design/temp 获取模板(虚拟) +export async function getDetail(req: any, res: Response) { + /** + * @api {get} /design/list 获取模板(虚拟) + * @apiVersion 1.0.0 + * @apiGroup design + */ + const { cate, type, id } = req.query + const dPath = type == 1 ? `../mock/components/detail/${id}.json` : `../mock/templates/${id}.json` + try { + const detail = fs.readFileSync(path.resolve(__dirname, dPath), 'utf8') + send.success(res, JSON.parse(detail)) + } catch (error) {} +} + +// design/material 获取素材(虚拟) +export async function getMaterial(req: any, res: any) { + /** + * @api {get} /design/material 获取素材(虚拟) + * @apiVersion 1.0.0 + * @apiGroup design + */ + const { cate } = req.query + try { + const detail = fs.readFileSync(path.resolve(__dirname, `../mock/materials/${cate}.json`), 'utf8') + send.success(res, { list: JSON.parse(detail) }) + } catch (error) { + console.log(error) + } +} + +// design/imgs 获取照片素材(虚拟) +export async function getPhotos(req: any, res: any) { + /** + * @api {get} /design/imgs 获取照片素材(虚拟) + * @apiVersion 1.0.0 + * @apiGroup design + */ + const { cate } = req.query + try { + const detail = fs.readFileSync(path.resolve(__dirname, `../mock/materials/photos/${cate}.json`), 'utf8') + send.success(res, { list: JSON.parse(detail) }) + } catch (error) {} +} + +// design/edit 保存模板(虚拟) +export async function saveTemplate(req: any, res: any) { + /** + * @api {post} /design/edit 保存模板(虚拟) + * @apiVersion 1.0.0 + * @apiGroup design + */ + let { id, title, data, width, height, type, cate, tag } = req.body + const folder = type == 1 ? 'components/detail' : 'templates' + const listPath = type == 1 ? 'components/list/comp.json' : 'templates/list.json' + try { + const isAdd = !id // 是否新增模板 + id = id || randomCode(8) + const savePath = path.resolve(__dirname, `../mock/${folder}/${id}.json`) + const jsonData = { + id, + data, + title, + width, + height, + } + fs.writeFileSync(savePath, JSON.stringify(jsonData)) + // 生成封面 + const size = width > height ? 640 : 320 + const fetchScreenshotUrl = `http://localhost:7001/api/screenshots?tempid=${id}&tempType=${type}&width=${width}&height=${height}&type=cover&size=${size}&quality=75` + await axios.get(fetchScreenshotUrl, { responseType: 'arraybuffer' }) + // 保存到其他地方可以设置 responseType: 'arraybuffer' 后操作buffer,这里只为了得到封面,发起请求就可以了 + if (isAdd) { + const listVal = fs.readFileSync(path.resolve(__dirname, `../mock/${listPath}`), 'utf8') + const list = JSON.parse(listVal) + const cover = type == 1 ? FileUrl + `/${id}-screenshot.png` : FileUrl + `/${id}-cover.jpg` + list.unshift({ id, cover, title, width, height }) + fs.writeFileSync(path.resolve(__dirname, `../mock/${listPath}`), JSON.stringify(list)) + } + send.success(res, { id }) + } catch (error) { + console.log(error) + } +} + +export default { + getTemplates, + getDetail, + getMaterial, + getPhotos, + saveTemplate +} diff --git a/service/src/service/files.ts b/service/src/service/files.ts index 5bbd215..a242e01 100644 --- a/service/src/service/files.ts +++ b/service/src/service/files.ts @@ -12,49 +12,48 @@ const { checkCreateFolder, randomCode, copyFile, send } = require('../utils/tool const FileUrl = 'http://localhost:7001/static/' -module.exports = { - // api/file/upload 上传接口 - async upload(req: Request, res: Response) { - /** - * @api {post} /api/file/upload 上传接口 - * @apiVersion 1.0.0 - * @apiGroup file - * - * @apiParam {File} file 二进制文件 - * @apiParam {String} folder 目标文件夹,空为根目录 - * @apiParam {String} name 文件名,默认随机 - * - * @apiSuccess (__组__) {__类型__} __字段名__ __返回字段说明__ - */ - const form = new multiparty.Form() - form.parse(req, async function (err: any, fields: any, files: any) { - if (err) { - console.error('上传文件出错!') - return - } - if (files) { - const file = files.file ? files.file[0] : {} - const { size, headers, originalFilename } = file - const fileType = headers['content-type'].split('/')[1] - const Suffix = originalFilename.split('.').pop() || fileType || 'png' - const { folder = '', name = `${randomCode(12)}.${Suffix}` } = fields - const folderPath = `${filePath}${folder ? `${folder}/` : ''}` - checkCreateFolder(folderPath) // 检测对应目录是否存在 - const targetPath = `${folderPath}${name}` - copyFile(file.path, targetPath) - .then(() => { - const url = `${FileUrl}${folder ? folder + '/' : ''}${name}` - send.success(res, { - key: `${folder}/${name}`, - url, - }) + +// api/file/upload 上传接口 +export async function upload(req: Request, res: Response) { + /** + * @api {post} /api/file/upload 上传接口 + * @apiVersion 1.0.0 + * @apiGroup file + * + * @apiParam {File} file 二进制文件 + * @apiParam {String} folder 目标文件夹,空为根目录 + * @apiParam {String} name 文件名,默认随机 + * + * @apiSuccess (__组__) {__类型__} __字段名__ __返回字段说明__ + */ + const form = new multiparty.Form() + form.parse(req, async function (err: any, fields: any, files: any) { + if (err) { + console.error('上传文件出错!') + return + } + if (files) { + const file = files.file ? files.file[0] : {} + const { size, headers, originalFilename } = file + const fileType = headers['content-type'].split('/')[1] + const Suffix = originalFilename.split('.').pop() || fileType || 'png' + const { folder = '', name = `${randomCode(12)}.${Suffix}` } = fields + const folderPath = `${filePath}${folder ? `${folder}/` : ''}` + checkCreateFolder(folderPath) // 检测对应目录是否存在 + const targetPath = `${folderPath}${name}` + copyFile(file.path, targetPath) + .then(() => { + const url = `${FileUrl}${folder ? folder + '/' : ''}${name}` + send.success(res, { + key: `${folder}/${name}`, + url, }) - .catch((err: any) => { - console.log('上传异常', err) - }) - } - }) - }, + }) + .catch((err: any) => { + console.log('上传异常', err) + }) + } + }) } -export {} +export default { upload } diff --git a/service/src/service/screenshots.ts b/service/src/service/screenshots.ts index ef81c4d..e790562 100644 --- a/service/src/service/screenshots.ts +++ b/service/src/service/screenshots.ts @@ -5,106 +5,105 @@ * @LastEditors: ShawnPhang * @LastEditTime: 2024-08-17 11:23:58 */ -const { saveScreenshot } = require('../utils/download-single.ts') -const uuid = require('../utils/uuid.ts') -const { filePath, upperLimit, drawLink } = require('../configs.ts') -const { queueRun, queueList } = require('../utils/node-queue.ts') +import { saveScreenshot } from '../utils/download-single' +import uuid from '../utils/uuid' +import { filePath, upperLimit, drawLink } from '../configs' +import { queueRun, queueList } from '../utils/node-queue' // const path = require('path') -const fs = require('fs') -module.exports = { - async screenshots(req: any, res: any) { - /** - * @api {get} api/screenshots 截图 - * @apiVersion 1.0.0 - * @apiGroup screenShot - * - * @apiParam {String|Number} id (可选) 截图id,高优先级 - * @apiParam {String|Number} tempid (可选) 模板id,低优先级,无id时取该值 - * @apiParam {String|Number} tempType (可选) 区分模板和组件类型,临时用 - * @apiParam {String} width (必传)视窗大小 - * @apiParam {String} height (必传)视窗大小 - * @apiParam {String} screenshot_url 可选 - * @apiParam {String} type 可选, file正常截图返回,cover封面生成,默认file - * @apiParam {String} size 可选, 按比例缩小到宽度 - * @apiParam {String} quality 可选, 质量 - * @apiParam {String|Number} index 可选, 下载哪个画板 - */ - let { id, tempid, tempType, width, height, screenshot_url, type = 'file', size, quality, index = 0 } = req.query - id == 'undefined' && (id = null) - const url = (screenshot_url || drawLink) + `${id ? '?id=' : '?tempid='}` - id = id || tempid - const path = filePath + `${id}-screenshot.png` - const thumbPath = type === 'cover' && tempType != 1 ? filePath + `${id}-cover.jpg` : null +/** + * @api {get} api/screenshots 截图 + * @apiVersion 1.0.0 + * @apiGroup screenShot + * + * @apiParam {String|Number} id (可选) 截图id,高优先级 + * @apiParam {String|Number} tempid (可选) 模板id,低优先级,无id时取该值 + * @apiParam {String|Number} tempType (可选) 区分模板和组件类型,临时用 + * @apiParam {String} width (必传)视窗大小 + * @apiParam {String} height (必传)视窗大小 + * @apiParam {String} screenshot_url 可选 + * @apiParam {String} type 可选, file正常截图返回,cover封面生成,默认file + * @apiParam {String} size 可选, 按比例缩小到宽度 + * @apiParam {String} quality 可选, 质量 + * @apiParam {String|Number} index 可选, 下载哪个画板 + */ +export async function screenshots(req: any, res: any) { + let { id, tempid, tempType, width, height, screenshot_url, type = 'file', size, quality, index = 0 } = req.query + id == 'undefined' && (id = null) + const url = (screenshot_url || drawLink) + `${id ? '?id=' : '?tempid='}` + id = id || tempid + const path = filePath + `${id}-screenshot.png` + const thumbPath = type === 'cover' && tempType != 1 ? filePath + `${id}-cover.jpg` : null - if (id && width && height) { - if (queueList.length > upperLimit) { - res.json({ code: 200, msg: '服务器表示顶不住啊,等等再来吧~' }) - return - } - const targetUrl = url + id + `${tempType ? '&tempType=' + tempType : ''}` + `&index=${index}` - queueRun(saveScreenshot, targetUrl, { width, height, path, thumbPath, size, quality }) - .then(() => { - res.setHeader('Content-Type', 'image/jpg') - // const stats = fs.statSync(path) - // res.setHeader('Cache-Control', stats.size) - type === 'file' ? res.sendFile(path) : res.sendFile(thumbPath) - }) - .catch((e: any) => { - res.json({ code: 500, msg: '图片生成错误' }) - }) - } else { - res.json({ code: 500, msg: '缺少参数,请检查' }) + if (id && width && height) { + if (queueList.length > upperLimit) { + res.json({ code: 200, msg: '服务器表示顶不住啊,等等再来吧~' }) + return } - }, - async printscreen(req: any, res: any) { - /** - * @api {get} api/printscreen 全屏网页截图 - * @apiVersion 1.0.0 - * @apiGroup screenShot - * - * @apiParam {String} url (必传) 目标网页link - * @apiParam {String} width (可选) 视窗大小 - * @apiParam {String} height (可选) 视窗大小 - * @apiParam {Boolean} prevent (可选, 默认false) true: 阻止立即截图,使用注入函数接管 - * @apiParam {String} type (可选, 默认file) file: 返回二进制文件,cover: 立即返回地址(path, thumbPath) - * @apiParam {String} size 可选 (只在type=cover生效, eg:300,等比缩放到300像素宽)传该值时会额外产生小图(封面,格式jpeg) - * @apiParam {String} quality 可选 (只在有size生效, eg:75),压缩质量:1-100,质量越小图片占用空间越小 - * @apiParam {Number} wait (可选) 截图前的等待时间,单位 ms - * @apiParam {String} ua (可选) 模拟设备 eg: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' - * @apiParam {String} devices (可选) 套用设备预设,传该值则ua、width、height均会失效。eg: iPhone 6 所有预设:/src/utils/widget/Device.js - * @apiParam {Number} scale (可选) 针对移动端的设备像素比(DPR) 整型范围 1~4,默认1 - */ - let { width = 375, height = 0, url, type = 'file', size, quality, prevent = false, ua, devices, scale, wait } = req.query - const path = filePath + `screenshot_${new Date().getTime()}_${uuid()}.png` - const thumbPath = type === 'cover' ? path.replace('.png', '.jpg') : null - - if (url) { - const sign = `${new Date().getTime()}_${uuid(8)}` - req._queueSign = sign - // console.log(url + id, path, thumbPath); - if (queueList.length > upperLimit) { - res.json({ code: 200, msg: '业务繁忙,等等再来吧~' }) - return - } - queueRun(saveScreenshot, url, { width, height, path, thumbPath, size, quality, prevent, ua, devices, scale, wait }, sign) - .then(() => { - if (!res.headersSent) { - // res.setHeader('Content-Type', 'image/jpg') - // const stats = fs.statSync(path) - // res.setHeader('Cache-Control', stats.size) - res.json({ code: 200, msg: '截图成功', data: { path, thumbPath } }) - } else { - res.json({ code: 200, msg: 'ok' }) - } - }) - .catch((e: any) => { - res.json({ code: 500, msg: '图片生成错误!' }) - }) - } else { - res.json({ code: 500, msg: '缺少参数,请检查' }) - } - }, + const targetUrl = url + id + `${tempType ? '&tempType=' + tempType : ''}` + `&index=${index}` + queueRun(saveScreenshot, targetUrl, { width, height, path, thumbPath, size, quality }) + .then(() => { + res.setHeader('Content-Type', 'image/jpg') + // const stats = fs.statSync(path) + // res.setHeader('Cache-Control', stats.size) + type === 'file' ? res.sendFile(path) : res.sendFile(thumbPath) + }) + .catch((e: any) => { + res.json({ code: 500, msg: '图片生成错误' }) + }) + } else { + res.json({ code: 500, msg: '缺少参数,请检查' }) + } } -export {} +/** + * @api {get} api/printscreen 全屏网页截图 + * @apiVersion 1.0.0 + * @apiGroup screenShot + * + * @apiParam {String} url (必传) 目标网页link + * @apiParam {String} width (可选) 视窗大小 + * @apiParam {String} height (可选) 视窗大小 + * @apiParam {Boolean} prevent (可选, 默认false) true: 阻止立即截图,使用注入函数接管 + * @apiParam {String} type (可选, 默认file) file: 返回二进制文件,cover: 立即返回地址(path, thumbPath) + * @apiParam {String} size 可选 (只在type=cover生效, eg:300,等比缩放到300像素宽)传该值时会额外产生小图(封面,格式jpeg) + * @apiParam {String} quality 可选 (只在有size生效, eg:75),压缩质量:1-100,质量越小图片占用空间越小 + * @apiParam {Number} wait (可选) 截图前的等待时间,单位 ms + * @apiParam {String} ua (可选) 模拟设备 eg: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' + * @apiParam {String} devices (可选) 套用设备预设,传该值则ua、width、height均会失效。eg: iPhone 6 所有预设:/src/utils/widget/Device.js + * @apiParam {Number} scale (可选) 针对移动端的设备像素比(DPR) 整型范围 1~4,默认1 + */ +export async function printscreen(req: any, res: any) { + + let { width = 375, height = 0, url, type = 'file', size, quality, prevent = false, ua, devices, scale, wait } = req.query + const path = filePath + `screenshot_${new Date().getTime()}_${uuid()}.png` + const thumbPath = type === 'cover' ? path.replace('.png', '.jpg') : null + + if (url) { + const sign = `${new Date().getTime()}_${uuid()}` + req._queueSign = sign + // console.log(url + id, path, thumbPath); + if (queueList.length > upperLimit) { + res.json({ code: 200, msg: '业务繁忙,等等再来吧~' }) + return + } + queueRun(saveScreenshot, url, { width, height, path, thumbPath, size, quality, prevent, ua, devices, scale, wait }, sign) + .then(() => { + if (!res.headersSent) { + // res.setHeader('Content-Type', 'image/jpg') + // const stats = fs.statSync(path) + // res.setHeader('Cache-Control', stats.size) + res.json({ code: 200, msg: '截图成功', data: { path, thumbPath } }) + } else { + res.json({ code: 200, msg: 'ok' }) + } + }) + .catch((e: any) => { + res.json({ code: 500, msg: '图片生成错误!' }) + }) + } else { + res.json({ code: 500, msg: '缺少参数,请检查' }) + } +} + +export default { printscreen, screenshots } diff --git a/service/src/service/user.ts b/service/src/service/user.ts index 22bb2a7..47d3a7d 100644 --- a/service/src/service/user.ts +++ b/service/src/service/user.ts @@ -12,7 +12,7 @@ const { checkCreateFolder, filesReader, send } = require('../utils/tools.ts') const FileUrl = 'http://localhost:7001/static/' -module.exports = { +export default { // design/user/image 获取用户上传列表(虚拟) async getUserImages(req: Request, res: Response) { /** @@ -24,5 +24,3 @@ module.exports = { send.success(res, { list }) }, } - -export {} diff --git a/service/src/utils/download-single.ts b/service/src/utils/download-single.ts index 5bb08b9..bf2b470 100644 --- a/service/src/utils/download-single.ts +++ b/service/src/utils/download-single.ts @@ -15,7 +15,7 @@ const forceTimeOut = 60 // 强制超时时间,单位:秒 const maxPXs = 4211840 // 超出此规格会触发限制器降低dpr,节省服务器资源 const maximum = 5000 // 最大宽高限制,超过截断以防止服务崩溃 -const saveScreenshot = async (url: string, { path, width, height, thumbPath, size = 0, quality = 0, prevent, ua, devices, scale, wait }: any) => { +export const saveScreenshot = async (url: string, { path, width, height, thumbPath, size = 0, quality = 0, prevent, ua, devices, scale, wait }: any) => { return new Promise(async (resolve: Function, reject: Function) => { let isPageLoad = false let browser: any = null @@ -154,6 +154,5 @@ const saveScreenshot = async (url: string, { path, width, height, thumbPath, siz }) } -module.exports = { saveScreenshot } +export default { saveScreenshot } -export {} diff --git a/service/src/utils/download.ts b/service/src/utils/download.ts index 8bddac1..caedf0c 100644 --- a/service/src/utils/download.ts +++ b/service/src/utils/download.ts @@ -13,7 +13,7 @@ const forceTimeOut = 60 // 强制超时时间,单位:秒 let browser: typeof puppeteer = null let release: any = null -const saveScreenshot = async (url: string, { path, width, height, thumbPath, size = 0, quality = 0, prevent, ua, devices, scale, wait }: any) => { +export const saveScreenshot = async (url: string, { path, width, height, thumbPath, size = 0, quality = 0, prevent, ua, devices, scale, wait }: any) => { return new Promise(async (resolve: Function) => { // 启动浏览器 if (!browser) { @@ -123,6 +123,4 @@ async function autoScroll(page: any) { }) } -module.exports = { saveScreenshot } - -export {} +export default { saveScreenshot } diff --git a/service/src/utils/fs.ts b/service/src/utils/fs.ts index 0b9f5bd..9bf38b5 100644 --- a/service/src/utils/fs.ts +++ b/service/src/utils/fs.ts @@ -6,73 +6,77 @@ * @LastEditTime: 2024-08-12 06:29:58 */ import fs from 'fs' -const path = require('path') -const imageSize = require('image-size') -const { filePath: StaticPath } = require('../configs.ts') +import path from 'path' +import imageSize from 'image-size' +import { filePath as StaticPath } from '../configs' const FileUrl = 'http://localhost:7001/static/' -module.exports = { - copyFile(sourceFile: string, destinationFile: string): Promise { - return new Promise((resolve, reject) => { - const readStream = fs.createReadStream(sourceFile) - const writeStream = fs.createWriteStream(destinationFile) +export function copyFile(sourceFile: string, destinationFile: string): Promise { + return new Promise((resolve, reject) => { + const readStream = fs.createReadStream(sourceFile) + const writeStream = fs.createWriteStream(destinationFile) - readStream.on('error', (err: any) => { - reject(err) - }) - - writeStream.on('error', (err: any) => { - reject(err) - }) - - writeStream.on('finish', () => { - resolve() - }) - - readStream.pipe(writeStream) + readStream.on('error', (err: any) => { + reject(err) }) - }, - // 读取目录 - filesReader(directoryPath: string) { - return new Promise((resolve) => { - try { - const files = fs.readdirSync(StaticPath + directoryPath) - const filesArray: any = [] - files.forEach((file) => { - const filePath = path.join(directoryPath, file) - // const stats = fs.statSync(filePath); - const { width, height } = imageSize(StaticPath + filePath) - if (file !== '.DS_Store') { - const fileInfo = { - width, - height, - // filename: file, - // link: FileUrl + directoryPath, - url: `${FileUrl + directoryPath}/${file}`, - // filepath: StaticPath + filePath - // size: stats.size, // 文件大小 - // modified: stats.mtime // 最后修改时间 - } - filesArray.push(fileInfo) - } - }) - // JSON.stringify(filesArray, null, 2) - resolve(filesArray) - } catch (err) { - console.error('Error reading directory:', err) - } + + writeStream.on('error', (err: any) => { + reject(err) }) - }, - // 读取文件 - readFile(directoryPath: string) { - return new Promise((resolve) => { - try { - resolve(fs.readFileSync(StaticPath + directoryPath, 'utf8')) - } catch (err) { - console.error('Error reading file:', err) - } + + writeStream.on('finish', () => { + resolve() }) - }, + + readStream.pipe(writeStream) + }) } -export {} +// 读取目录 +export function filesReader(directoryPath: string) { + return new Promise((resolve) => { + try { + const files = fs.readdirSync(StaticPath + directoryPath) + const filesArray: any = [] + files.forEach((file) => { + const filePath = path.join(directoryPath, file) + // const stats = fs.statSync(filePath); + const { width, height } = imageSize(StaticPath + filePath) + if (file !== '.DS_Store') { + const fileInfo = { + width, + height, + // filename: file, + // link: FileUrl + directoryPath, + url: `${FileUrl + directoryPath}/${file}`, + // filepath: StaticPath + filePath + // size: stats.size, // 文件大小 + // modified: stats.mtime // 最后修改时间 + } + filesArray.push(fileInfo) + } + }) + // JSON.stringify(filesArray, null, 2) + resolve(filesArray) + } catch (err) { + console.error('Error reading directory:', err) + } + }) +} + +// 读取文件 +export function readFile(directoryPath: string) { + return new Promise((resolve) => { + try { + resolve(fs.readFileSync(StaticPath + directoryPath, 'utf8')) + } catch (err) { + console.error('Error reading file:', err) + } + }) +} + +export default { + copyFile, + filesReader, + readFile, +} \ No newline at end of file diff --git a/service/src/utils/http.ts b/service/src/utils/http.ts index 42ee406..8cb037a 100644 --- a/service/src/utils/http.ts +++ b/service/src/utils/http.ts @@ -5,7 +5,7 @@ * @LastEditors: ShawnPhang * @LastEditTime: 2024-08-12 13:59:34 */ -const axios = require('axios') +import axios from 'axios' const httpRequest = axios.create({ maxContentLength: Infinity, @@ -16,4 +16,4 @@ httpRequest.interceptors.response.use((config: any) => { return config.data }) -module.exports = httpRequest +export default httpRequest diff --git a/service/src/utils/node-queue.ts b/service/src/utils/node-queue.ts index b630963..17a781d 100644 --- a/service/src/utils/node-queue.ts +++ b/service/src/utils/node-queue.ts @@ -10,7 +10,7 @@ interface Queue { sign?: string | number } -const { maxNum } = require('../configs.ts') +import { maxNum } from '../configs' const queueList: any = [] // 任务队列 let curNum = 0 // 当前执行的任务数 @@ -38,4 +38,4 @@ function run(Fn: Function) { }) } -module.exports = { queueRun, queueList } +export { queueRun, queueList } diff --git a/service/src/utils/timeout.ts b/service/src/utils/timeout.ts index d191979..56c890b 100644 --- a/service/src/utils/timeout.ts +++ b/service/src/utils/timeout.ts @@ -6,7 +6,7 @@ * @LastEditTime: 2023-07-05 20:17:00 */ -module.exports = async (req: any, res: any, next: any) => { +export default async (req: any, res: any, next: any) => { const { queueList } = require('../utils/node-queue.ts') const time = 30000 // 设置所有HTTP请求的服务器响应超时时间 res.setTimeout(time, () => { diff --git a/service/src/utils/tools.ts b/service/src/utils/tools.ts index 6c1f594..5a741fa 100644 --- a/service/src/utils/tools.ts +++ b/service/src/utils/tools.ts @@ -5,71 +5,76 @@ * @LastEditors: ShawnPhang * @LastEditTime: 2024-08-12 13:48:40 */ -const fs = require('fs') -const path = require('path') -const fsFunc = require('./fs.ts') +import fs from 'fs' +import path from 'path' +import { filesReader, copyFile, readFile } from './fs' -module.exports = { - send: { - success: (res: any, result: any, msg: string = 'ok') => { - res.json({ - code: 200, - msg, - result: result || undefined, - }) - }, - error: (res: any, msg: string = 'error') => { - res.json({ - code: 400, - msg, - }) - }, +export const send = { + success: (res: any, result: any, msg: string = 'ok') => { + res.json({ + code: 200, + msg, + result: result || undefined, + }) }, - isNumber: (value: any) => { - return typeof value === 'number' && !isNaN(value) + error: (res: any, msg: string = 'error') => { + res.json({ + code: 400, + msg, + }) }, - buildTree: (data: any[]) => {}, - groupBy: (array: any[], property: any) => {}, - // 检测目录并创建目录(支持深层级) - checkCreateFolder: (folder: string) => { - try { - const pathArr = splitPath(folder) - let _path = '' - for (let i = 0; i < pathArr.length; i++) { - if (pathArr[i]) { - _path += `/${pathArr[i]}` - !fs.existsSync(_path) && fs.mkdirSync(_path) - } +} +export const isNumber = (value: any) => { + return typeof value === 'number' && !isNaN(value) +} + +export const buildTree = (data: any[]) => {} + +export const groupBy = (array: any[], property: any) => {} + +// 检测目录并创建目录(支持深层级) +export const checkCreateFolder = (folder: string) => { + try { + const pathArr = splitPath(folder) + let _path = '' + for (let i = 0; i < pathArr.length; i++) { + if (pathArr[i]) { + _path += `/${pathArr[i]}` + !fs.existsSync(_path) && fs.mkdirSync(_path) } - } catch (e) {} - }, - // 检测文件 - checkCreateFile: (filePath: string) => { - try { - if (!fs.existsSync(filePath)) { - fs.writeFileSync(filePath, '') - } - } catch (e) { + } + } catch (e) {} +} + +// 检测文件 +export const checkCreateFile = (filePath: string) => { + try { + if (!fs.existsSync(filePath)) { fs.writeFileSync(filePath, '') } - }, - // 生成随机码 - randomCode: (length = 5) => { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - let result = '' - for (let i = 0; i < length; i++) { - const randomIndex = Math.floor(Math.random() * chars.length) - result += chars[randomIndex] - } - return result - }, - // 取数组差集 - findDifference: (a: any, b: any) => { - return a.concat(b).filter((v: any) => !a.includes(v) || !b.includes(v)) - }, - ...fsFunc, + } catch (e) { + fs.writeFileSync(filePath, '') + } } +// 生成随机码 +export const randomCode = (length = 5) => { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let result = '' + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * chars.length) + result += chars[randomIndex] + } + return result +} + +// 取数组差集 +export const findDifference = (a: any, b: any) => { + return a.concat(b).filter((v: any) => !a.includes(v) || !b.includes(v)) +} + +export { copyFile, readFile, filesReader } + /** * 将路径切割为数组 * @param dirPath @@ -81,4 +86,3 @@ function splitPath(dirPath: string) { return normalizedPath.split(separator) } -export {} diff --git a/service/src/utils/uuid.ts b/service/src/utils/uuid.ts index ca5edc7..98be44c 100644 --- a/service/src/utils/uuid.ts +++ b/service/src/utils/uuid.ts @@ -7,9 +7,9 @@ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE */ -const nodeCrypto = require('crypto'); +import nodeCrypto from 'crypto'; -module.exports = () => +export default () => // @ts-ignore ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c: number) => (c ^ (nodeCrypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16) diff --git a/service/tsconfig.json b/service/tsconfig.json index 2dfc723..62aa65a 100644 --- a/service/tsconfig.json +++ b/service/tsconfig.json @@ -3,7 +3,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "ESNEXT", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "lib": ["dom", "es2015"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ diff --git a/service/webpack.config.js b/service/webpack.config.js index e9db902..72f1258 100644 --- a/service/webpack.config.js +++ b/service/webpack.config.js @@ -39,6 +39,12 @@ module.exports = { }, ], }, + resolve: { + extensions: ['.ts', '.tsx', '.js', '.json'], // 解析的文件类型 + alias: { + '@': path.resolve(__dirname, 'src') // 配置路径别名,指向 src 目录 + } + }, // plugins: [new BundleAnalyzerPlugin()], plugins: [ new buildPlugin() ] }