mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-26 10:40:26 +08:00
✨ Feature(custom): add upload max concurrency and upload interval setting for api
ISSUES CLOSED: #487
This commit is contained in:
@@ -22,6 +22,41 @@ const LOG_PATH = appLogPath()
|
|||||||
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
const errorMessage = `upload error. see ${LOG_PATH} for more detail.`
|
||||||
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
|
const deleteErrorMessage = `delete error. see ${LOG_PATH} for more detail.`
|
||||||
|
|
||||||
|
// Upload rate-limiting state
|
||||||
|
let runningUploads = 0
|
||||||
|
const uploadWaitQueue: (() => void)[] = []
|
||||||
|
let lastUploadFinishTime = 0
|
||||||
|
|
||||||
|
async function withUploadRateLimit<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
|
const maxConcurrency: number = allConfig.settings?.serverMaxConcurrency || 0
|
||||||
|
const uploadInterval: number = allConfig.settings?.serverUploadInterval || 0
|
||||||
|
if (maxConcurrency > 0) {
|
||||||
|
if (runningUploads >= maxConcurrency) {
|
||||||
|
await new Promise<void>(resolve => uploadWaitQueue.push(resolve))
|
||||||
|
}
|
||||||
|
runningUploads++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadInterval > 0) {
|
||||||
|
const wait = uploadInterval - (Date.now() - lastUploadFinishTime)
|
||||||
|
if (wait > 0) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, wait))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fn()
|
||||||
|
} finally {
|
||||||
|
if (maxConcurrency > 0) {
|
||||||
|
runningUploads--
|
||||||
|
const next = uploadWaitQueue.shift()
|
||||||
|
if (next) next()
|
||||||
|
}
|
||||||
|
lastUploadFinishTime = Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function responseForGet({ response }: { response: http.ServerResponse }) {
|
async function responseForGet({ response }: { response: http.ServerResponse }) {
|
||||||
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
||||||
const htmlContent = marked(markdownContent)
|
const htmlContent = marked(markdownContent)
|
||||||
@@ -44,6 +79,7 @@ router.post(
|
|||||||
urlparams?: URLSearchParams
|
urlparams?: URLSearchParams
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
await withUploadRateLimit(async () => {
|
||||||
const allConfig = picgo.getConfig<any>() || {}
|
const allConfig = picgo.getConfig<any>() || {}
|
||||||
const picbed = urlparams?.get('picbed')
|
const picbed = urlparams?.get('picbed')
|
||||||
const passedKey = urlparams?.get('key')
|
const passedKey = urlparams?.get('key')
|
||||||
@@ -162,6 +198,7 @@ router.post(
|
|||||||
if (needRestore) {
|
if (needRestore) {
|
||||||
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
changeCurrentUploader(currentPicBedType, currentPicBedConfig, currentPicBedConfigId)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
handleResponse({
|
handleResponse({
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export interface IConfigStruct {
|
|||||||
deleteCloudFile: boolean
|
deleteCloudFile: boolean
|
||||||
server: IServerConfig
|
server: IServerConfig
|
||||||
serverKey: string
|
serverKey: string
|
||||||
|
serverMaxConcurrency: number
|
||||||
|
serverUploadInterval: number
|
||||||
pasteStyle: string
|
pasteStyle: string
|
||||||
aesPassword: string
|
aesPassword: string
|
||||||
rename: boolean
|
rename: boolean
|
||||||
@@ -146,6 +148,8 @@ export const configPaths = {
|
|||||||
deleteCloudFile: 'settings.deleteCloudFile',
|
deleteCloudFile: 'settings.deleteCloudFile',
|
||||||
server: 'settings.server',
|
server: 'settings.server',
|
||||||
serverKey: 'settings.serverKey',
|
serverKey: 'settings.serverKey',
|
||||||
|
serverMaxConcurrency: 'settings.serverMaxConcurrency',
|
||||||
|
serverUploadInterval: 'settings.serverUploadInterval',
|
||||||
pasteStyle: 'settings.pasteStyle',
|
pasteStyle: 'settings.pasteStyle',
|
||||||
aesPassword: 'settings.aesPassword',
|
aesPassword: 'settings.aesPassword',
|
||||||
rename: 'settings.rename',
|
rename: 'settings.rename',
|
||||||
|
|||||||
@@ -854,9 +854,13 @@
|
|||||||
"serverHost": "Listen Address",
|
"serverHost": "Listen Address",
|
||||||
"serverKey": "Authentication Key",
|
"serverKey": "Authentication Key",
|
||||||
"serverKeyPlaceholder": "Please enter the authentication key to prevent API abuse",
|
"serverKeyPlaceholder": "Please enter the authentication key to prevent API abuse",
|
||||||
|
"serverMaxConcurrency": "Max Upload Concurrency",
|
||||||
|
"serverMaxConcurrencyPlaceholder": "Max simultaneous uploads via API (0 = unlimited)",
|
||||||
"serverPort": "Listen Port",
|
"serverPort": "Listen Port",
|
||||||
"serverSettings": "Server Settings",
|
"serverSettings": "Server Settings",
|
||||||
"serverSettingsNotice": "If you don't know the purpose of the Upload API Service, please read the documentation or do not modify the configuration.",
|
"serverSettingsNotice": "If you don't know the purpose of the Upload API Service, please read the documentation or do not modify the configuration.",
|
||||||
|
"serverUploadInterval": "Upload Interval (ms)",
|
||||||
|
"serverUploadIntervalPlaceholder": "Delay in ms between uploads via API (0 = no delay)",
|
||||||
"setLog": "Set Log",
|
"setLog": "Set Log",
|
||||||
"setLogDesc": "Configure log level and file size",
|
"setLogDesc": "Configure log level and file size",
|
||||||
"setProxyAndMirror": "Set Proxy and Mirror Address",
|
"setProxyAndMirror": "Set Proxy and Mirror Address",
|
||||||
|
|||||||
@@ -854,9 +854,13 @@
|
|||||||
"serverHost": "监听地址",
|
"serverHost": "监听地址",
|
||||||
"serverKey": "鉴权密钥",
|
"serverKey": "鉴权密钥",
|
||||||
"serverKeyPlaceholder": "请输入鉴权密钥,用于防止接口滥用",
|
"serverKeyPlaceholder": "请输入鉴权密钥,用于防止接口滥用",
|
||||||
|
"serverMaxConcurrency": "最大上传并发数",
|
||||||
|
"serverMaxConcurrencyPlaceholder": "通过API同时上传的最大数量(0表示不限制)",
|
||||||
"serverPort": "监听端口",
|
"serverPort": "监听端口",
|
||||||
"serverSettings": "服务器设置",
|
"serverSettings": "服务器设置",
|
||||||
"serverSettingsNotice": "如果你不知道上传API服务的作用,请阅读文档,或者不用修改配置。",
|
"serverSettingsNotice": "如果你不知道上传API服务的作用,请阅读文档,或者不用修改配置。",
|
||||||
|
"serverUploadInterval": "上传间隔(毫秒)",
|
||||||
|
"serverUploadIntervalPlaceholder": "通过API上传时每次上传之间的延迟(0表示无延迟)",
|
||||||
"setLog": "设置日志",
|
"setLog": "设置日志",
|
||||||
"setLogDesc": "配置日志等级和文件大小",
|
"setLogDesc": "配置日志等级和文件大小",
|
||||||
"setProxyAndMirror": "设置代理和镜像地址",
|
"setProxyAndMirror": "设置代理和镜像地址",
|
||||||
|
|||||||
@@ -854,9 +854,13 @@
|
|||||||
"serverHost": "監聽地址",
|
"serverHost": "監聽地址",
|
||||||
"serverKey": "鑑權密鑰",
|
"serverKey": "鑑權密鑰",
|
||||||
"serverKeyPlaceholder": "請輸入鑑權密鑰,用於防止接口濫用",
|
"serverKeyPlaceholder": "請輸入鑑權密鑰,用於防止接口濫用",
|
||||||
|
"serverMaxConcurrency": "最大上傳並發數",
|
||||||
|
"serverMaxConcurrencyPlaceholder": "通過API同時上傳的最大數量(0表示不限制)",
|
||||||
"serverPort": "監聽端口",
|
"serverPort": "監聽端口",
|
||||||
"serverSettings": "伺服器設置",
|
"serverSettings": "伺服器設置",
|
||||||
"serverSettingsNotice": "如果你不知道上傳API服務的作用,請閱讀文檔,或者不用修改配置。",
|
"serverSettingsNotice": "如果你不知道上傳API服務的作用,請閱讀文檔,或者不用修改配置。",
|
||||||
|
"serverUploadInterval": "上傳間隔(毫秒)",
|
||||||
|
"serverUploadIntervalPlaceholder": "通過API上傳時每次上傳之間的延遲(0表示無延遲)",
|
||||||
"setLog": "設置日誌",
|
"setLog": "設置日誌",
|
||||||
"setLogDesc": "配置日誌等級和文件大小",
|
"setLogDesc": "配置日誌等級和文件大小",
|
||||||
"setProxyAndMirror": "設置代理和鏡像地址",
|
"setProxyAndMirror": "設置代理和鏡像地址",
|
||||||
|
|||||||
@@ -1044,6 +1044,26 @@
|
|||||||
:placeholder="t('pages.settings.advanced.serverKeyPlaceholder')"
|
:placeholder="t('pages.settings.advanced.serverKeyPlaceholder')"
|
||||||
/>
|
/>
|
||||||
</SettingCard>
|
</SettingCard>
|
||||||
|
<SettingCard>
|
||||||
|
<CustomInput
|
||||||
|
v-model="formOfSetting.serverMaxConcurrency"
|
||||||
|
type="number"
|
||||||
|
:min="0"
|
||||||
|
:step="1"
|
||||||
|
:title="t('pages.settings.advanced.serverMaxConcurrency')"
|
||||||
|
:placeholder="t('pages.settings.advanced.serverMaxConcurrencyPlaceholder')"
|
||||||
|
/>
|
||||||
|
</SettingCard>
|
||||||
|
<SettingCard>
|
||||||
|
<CustomInput
|
||||||
|
v-model="formOfSetting.serverUploadInterval"
|
||||||
|
type="number"
|
||||||
|
:min="0"
|
||||||
|
:step="100"
|
||||||
|
:title="t('pages.settings.advanced.serverUploadInterval')"
|
||||||
|
:placeholder="t('pages.settings.advanced.serverUploadIntervalPlaceholder')"
|
||||||
|
/>
|
||||||
|
</SettingCard>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -1441,6 +1461,8 @@ const formOfSetting = ref<ISettingForm>({
|
|||||||
sinkToken: '',
|
sinkToken: '',
|
||||||
deleteLocalFile: false,
|
deleteLocalFile: false,
|
||||||
serverKey: '',
|
serverKey: '',
|
||||||
|
serverMaxConcurrency: 0,
|
||||||
|
serverUploadInterval: 0,
|
||||||
aesPassword: 'PicList-aesPassword',
|
aesPassword: 'PicList-aesPassword',
|
||||||
enableWebServer: false,
|
enableWebServer: false,
|
||||||
webServerHost: '0.0.0.0',
|
webServerHost: '0.0.0.0',
|
||||||
@@ -1552,6 +1574,8 @@ const autoWatchKeys = [
|
|||||||
'webServerPort',
|
'webServerPort',
|
||||||
'webServerPath',
|
'webServerPath',
|
||||||
'serverKey',
|
'serverKey',
|
||||||
|
'serverMaxConcurrency',
|
||||||
|
'serverUploadInterval',
|
||||||
'uploadNotification',
|
'uploadNotification',
|
||||||
'uploadResultNotification',
|
'uploadResultNotification',
|
||||||
'autoCloseMainWindow',
|
'autoCloseMainWindow',
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export interface IConfigStruct {
|
|||||||
deleteCloudFile: boolean
|
deleteCloudFile: boolean
|
||||||
server: IServerConfig
|
server: IServerConfig
|
||||||
serverKey: string
|
serverKey: string
|
||||||
|
serverMaxConcurrency: number
|
||||||
|
serverUploadInterval: number
|
||||||
pasteStyle: string
|
pasteStyle: string
|
||||||
aesPassword: string
|
aesPassword: string
|
||||||
rename: boolean
|
rename: boolean
|
||||||
@@ -151,6 +153,8 @@ export const configPaths = {
|
|||||||
deleteCloudFile: 'settings.deleteCloudFile',
|
deleteCloudFile: 'settings.deleteCloudFile',
|
||||||
server: 'settings.server',
|
server: 'settings.server',
|
||||||
serverKey: 'settings.serverKey',
|
serverKey: 'settings.serverKey',
|
||||||
|
serverMaxConcurrency: 'settings.serverMaxConcurrency',
|
||||||
|
serverUploadInterval: 'settings.serverUploadInterval',
|
||||||
pasteStyle: 'settings.pasteStyle',
|
pasteStyle: 'settings.pasteStyle',
|
||||||
aesPassword: 'settings.aesPassword',
|
aesPassword: 'settings.aesPassword',
|
||||||
rename: 'settings.rename',
|
rename: 'settings.rename',
|
||||||
|
|||||||
2
src/universal/types/view.d.ts
vendored
2
src/universal/types/view.d.ts
vendored
@@ -30,6 +30,8 @@ interface ISettingForm {
|
|||||||
sinkToken: string
|
sinkToken: string
|
||||||
deleteLocalFile: boolean
|
deleteLocalFile: boolean
|
||||||
serverKey: string
|
serverKey: string
|
||||||
|
serverMaxConcurrency: number
|
||||||
|
serverUploadInterval: number
|
||||||
aesPassword: string
|
aesPassword: string
|
||||||
enableWebServer: boolean
|
enableWebServer: boolean
|
||||||
webServerHost: string
|
webServerHost: string
|
||||||
|
|||||||
Reference in New Issue
Block a user