From 0f7b043da3ad163feaed0713edd6a7e50756f023 Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Mon, 15 Apr 2024 23:12:02 +0800
Subject: [PATCH 1/8] :hammer: Refactor(custom): refactor private to sharp in
 es6

---
 src/main/apis/app/window/windowManager.ts    | 30 ++++----
 src/main/apis/core/datastore/index.ts        | 38 +++++-----
 src/main/apis/gui/index.ts                   |  2 +-
 src/main/lifeCycle/index.ts                  | 16 ++---
 src/main/manage/datastore/db.ts              | 24 +++----
 src/main/manage/datastore/upDownTaskQueue.ts |  2 +-
 src/main/manage/utils/logger.ts              | 54 +++++++-------
 src/main/manage/utils/threadPool.ts          | 75 --------------------
 src/main/server/index.ts                     | 40 +++++------
 src/main/server/router.ts                    | 22 +++---
 src/main/server/webServer/index.ts           | 22 +++---
 src/main/utils/sshClient.ts                  | 10 +--
 src/renderer/apis/aliyun.ts                  | 16 +----
 src/renderer/apis/github.ts                  |  8 +--
 src/renderer/apis/imgur.ts                   | 39 ++++------
 src/renderer/apis/smms.ts                    |  4 +-
 src/renderer/apis/tcyun.ts                   |  4 +-
 src/renderer/i18n/index.ts                   | 26 +++----
 src/renderer/utils/db.ts                     | 14 ++--
 19 files changed, 173 insertions(+), 273 deletions(-)
 delete mode 100644 src/main/manage/utils/threadPool.ts

diff --git a/src/main/apis/app/window/windowManager.ts b/src/main/apis/app/window/windowManager.ts
index 195ba2ce..2d23b6c6 100644
--- a/src/main/apis/app/window/windowManager.ts
+++ b/src/main/apis/app/window/windowManager.ts
@@ -10,22 +10,22 @@ import { BrowserWindow } from 'electron'
 import { IWindowList } from '#/types/enum'
 
 class WindowManager implements IWindowManager {
-  private windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
-  private windowIdMap: Map<number, IWindowList | string> = new Map()
+  #windowMap: Map<IWindowList | string, BrowserWindow> = new Map()
+  #windowIdMap: Map<number, IWindowList | string> = new Map()
   create (name: IWindowList) {
     const windowConfig: IWindowListItem = windowList.get(name)!
     if (windowConfig.isValid) {
       if (!windowConfig.multiple) {
-        if (this.has(name)) return this.windowMap.get(name)!
+        if (this.has(name)) return this.#windowMap.get(name)!
       }
       const window = new BrowserWindow(windowConfig.options())
       const id = window.id
       if (windowConfig.multiple) {
-        this.windowMap.set(`${name}_${window.id}`, window)
-        this.windowIdMap.set(window.id, `${name}_${window.id}`)
+        this.#windowMap.set(`${name}_${window.id}`, window)
+        this.#windowIdMap.set(window.id, `${name}_${window.id}`)
       } else {
-        this.windowMap.set(name, window)
-        this.windowIdMap.set(window.id, name)
+        this.#windowMap.set(name, window)
+        this.#windowIdMap.set(window.id, name)
       }
       windowConfig.callback(window, this)
       window.on('close', () => {
@@ -39,7 +39,7 @@ class WindowManager implements IWindowManager {
 
   get (name: IWindowList) {
     if (this.has(name)) {
-      return this.windowMap.get(name)!
+      return this.#windowMap.get(name)!
     } else {
       const window = this.create(name)
       return window
@@ -47,24 +47,24 @@ class WindowManager implements IWindowManager {
   }
 
   has (name: IWindowList) {
-    return this.windowMap.has(name)
+    return this.#windowMap.has(name)
   }
 
   deleteById = (id: number) => {
-    const name = this.windowIdMap.get(id)
+    const name = this.#windowIdMap.get(id)
     if (name) {
-      this.windowMap.delete(name)
-      this.windowIdMap.delete(id)
+      this.#windowMap.delete(name)
+      this.#windowIdMap.delete(id)
     }
   }
 
   getAvailableWindow (isSkipMiniWindow = false) {
-    const miniWindow = this.windowMap.get(IWindowList.MINI_WINDOW)
+    const miniWindow = this.#windowMap.get(IWindowList.MINI_WINDOW)
     if (miniWindow && miniWindow.isVisible() && !isSkipMiniWindow) {
       return miniWindow
     } else {
-      const settingWindow = this.windowMap.get(IWindowList.SETTING_WINDOW)
-      const trayWindow = this.windowMap.get(IWindowList.TRAY_WINDOW)
+      const settingWindow = this.#windowMap.get(IWindowList.SETTING_WINDOW)
+      const trayWindow = this.#windowMap.get(IWindowList.TRAY_WINDOW)
       return settingWindow || trayWindow || this.create(IWindowList.SETTING_WINDOW)!
     }
   }
diff --git a/src/main/apis/core/datastore/index.ts b/src/main/apis/core/datastore/index.ts
index 868d3e84..95bf4eb7 100644
--- a/src/main/apis/core/datastore/index.ts
+++ b/src/main/apis/core/datastore/index.ts
@@ -24,12 +24,12 @@ const CONFIG_PATH: string = dbPathChecker()
 export const DB_PATH: string = getGalleryDBPath().dbPath
 
 class ConfigStore {
-  private db: JSONStore
+  #db: JSONStore
   constructor () {
-    this.db = new JSONStore(CONFIG_PATH)
+    this.#db = new JSONStore(CONFIG_PATH)
 
-    if (!this.db.has('picBed')) {
-      this.db.set('picBed', {
+    if (!this.#db.has('picBed')) {
+      this.#db.set('picBed', {
         current: 'smms', // deprecated
         uploader: 'smms',
         smms: {
@@ -38,8 +38,8 @@ class ConfigStore {
       })
     }
 
-    if (!this.db.has(configPaths.settings.shortKey._path)) {
-      this.db.set(configPaths.settings.shortKey['picgo:upload'], {
+    if (!this.#db.has(configPaths.settings.shortKey._path)) {
+      this.#db.set(configPaths.settings.shortKey['picgo:upload'], {
         enable: true,
         key: 'CommandOrControl+Shift+P',
         name: 'upload',
@@ -50,31 +50,31 @@ class ConfigStore {
   }
 
   flush () {
-    this.db = new JSONStore(CONFIG_PATH)
+    this.#db = new JSONStore(CONFIG_PATH)
   }
 
   read () {
-    this.db.read()
-    return this.db
+    this.#db.read()
+    return this.#db
   }
 
   get (key = ''): any {
     if (key === '') {
-      return this.db.read()
+      return this.#db.read()
     }
-    return this.db.get(key)
+    return this.#db.get(key)
   }
 
   set (key: string, value: any): void {
-    return this.db.set(key, value)
+    return this.#db.set(key, value)
   }
 
   has (key: string) {
-    return this.db.has(key)
+    return this.#db.has(key)
   }
 
   unset (key: string, value: any): boolean {
-    return this.db.unset(key, value)
+    return this.#db.unset(key, value)
   }
 
   getConfigPath () {
@@ -88,16 +88,16 @@ export default db
 
 // v2.3.0 add gallery db
 class GalleryDB {
-  private static instance: DBStore
+  static #instance: DBStore
   private constructor () {
     console.log('init gallery db')
   }
 
-  public static getInstance (): DBStore {
-    if (!GalleryDB.instance) {
-      GalleryDB.instance = new DBStore(DB_PATH, 'gallery')
+  static getInstance (): DBStore {
+    if (!GalleryDB.#instance) {
+      GalleryDB.#instance = new DBStore(DB_PATH, 'gallery')
     }
-    return GalleryDB.instance
+    return GalleryDB.#instance
   }
 }
 
diff --git a/src/main/apis/gui/index.ts b/src/main/apis/gui/index.ts
index 1a1eff4e..72e4ed27 100644
--- a/src/main/apis/gui/index.ts
+++ b/src/main/apis/gui/index.ts
@@ -37,7 +37,7 @@ class GuiApi implements IGuiApi {
     console.log('init guiapi')
   }
 
-  public static getInstance (): GuiApi {
+  static getInstance (): GuiApi {
     if (!GuiApi.instance) {
       GuiApi.instance = new GuiApi()
     }
diff --git a/src/main/lifeCycle/index.ts b/src/main/lifeCycle/index.ts
index 92a55ad7..f5789f69 100644
--- a/src/main/lifeCycle/index.ts
+++ b/src/main/lifeCycle/index.ts
@@ -135,7 +135,7 @@ autoUpdater.on('error', (err) => {
 })
 
 class LifeCycle {
-  private async beforeReady () {
+  async #beforeReady () {
     protocol.registerSchemesAsPrivileged([{ scheme: 'picgo', privileges: { secure: true, standard: true } }])
     // fix the $PATH in macOS & linux
     fixPath()
@@ -148,7 +148,7 @@ class LifeCycle {
     busEventList.listen()
   }
 
-  private onReady () {
+  #onReady () {
     const readyFunction = async () => {
       createProtocol('picgo')
       windowManager.create(IWindowList.TRAY_WINDOW)
@@ -228,7 +228,7 @@ class LifeCycle {
     app.whenReady().then(readyFunction)
   }
 
-  private onRunning () {
+  #onRunning () {
     app.on('second-instance', (event, commandLine, workingDirectory) => {
       logger.info('detect second instance')
       const result = handleStartUpFiles(commandLine, workingDirectory)
@@ -263,7 +263,7 @@ class LifeCycle {
     }
   }
 
-  private onQuit () {
+  #onQuit () {
     app.on('window-all-closed', () => {
       if (process.platform !== 'darwin') {
         app.quit()
@@ -298,10 +298,10 @@ class LifeCycle {
     if (!gotTheLock) {
       app.quit()
     } else {
-      await this.beforeReady()
-      this.onReady()
-      this.onRunning()
-      this.onQuit()
+      await this.#beforeReady()
+      this.#onReady()
+      this.#onRunning()
+      this.#onQuit()
     }
   }
 }
diff --git a/src/main/manage/datastore/db.ts b/src/main/manage/datastore/db.ts
index a48b6146..5bd25197 100644
--- a/src/main/manage/datastore/db.ts
+++ b/src/main/manage/datastore/db.ts
@@ -4,22 +4,22 @@ import { IJSON } from '@picgo/store/dist/types'
 import { ManageApiType, ManageConfigType } from '~/universal/types/manage'
 
 class ManageDB {
-  private readonly ctx: ManageApiType
-  private readonly db: JSONStore
+  readonly #ctx: ManageApiType
+  readonly #db: JSONStore
   constructor (ctx: ManageApiType) {
-    this.ctx = ctx
-    this.db = new JSONStore(this.ctx.configPath)
+    this.#ctx = ctx
+    this.#db = new JSONStore(this.#ctx.configPath)
     let initParams: IStringKeyMap = {
       picBed: {},
       settings: {},
       currentPicBed: 'placeholder'
     }
     for (let key in initParams) {
-      if (!this.db.has(key)) {
+      if (!this.#db.has(key)) {
         try {
-          this.db.set(key, initParams[key])
+          this.#db.set(key, initParams[key])
         } catch (e: any) {
-          this.ctx.logger.error(e)
+          this.#ctx.logger.error(e)
           throw e
         }
       }
@@ -27,27 +27,27 @@ class ManageDB {
   }
 
   read (flush?: boolean): IJSON {
-    return this.db.read(flush)
+    return this.#db.read(flush)
   }
 
   get (key: string = ''): any {
     this.read(true)
-    return this.db.get(key)
+    return this.#db.get(key)
   }
 
   set (key: string, value: any): void {
     this.read(true)
-    return this.db.set(key, value)
+    return this.#db.set(key, value)
   }
 
   has (key: string): boolean {
     this.read(true)
-    return this.db.has(key)
+    return this.#db.has(key)
   }
 
   unset (key: string, value: any): boolean {
     this.read(true)
-    return this.db.unset(key, value)
+    return this.#db.unset(key, value)
   }
 
   saveConfig (config: Partial<ManageConfigType>): void {
diff --git a/src/main/manage/datastore/upDownTaskQueue.ts b/src/main/manage/datastore/upDownTaskQueue.ts
index 019d824e..fe2b8af1 100644
--- a/src/main/manage/datastore/upDownTaskQueue.ts
+++ b/src/main/manage/datastore/upDownTaskQueue.ts
@@ -69,7 +69,7 @@ class UpDownTaskQueue {
     this.restore()
   }
 
-  public static getInstance () {
+  static getInstance () {
     if (!UpDownTaskQueue.instance) {
       UpDownTaskQueue.instance = new UpDownTaskQueue()
     }
diff --git a/src/main/manage/utils/logger.ts b/src/main/manage/utils/logger.ts
index d05453ee..abab5d63 100644
--- a/src/main/manage/utils/logger.ts
+++ b/src/main/manage/utils/logger.ts
@@ -10,49 +10,49 @@ import { enforceNumber, isDev } from '#/utils/common'
 import { configPaths } from '~/universal/utils/configPaths'
 
 export class ManageLogger implements ILogger {
-  private readonly level = {
+  readonly #level = {
     [ILogType.success]: 'green',
     [ILogType.info]: 'blue',
     [ILogType.warn]: 'yellow',
     [ILogType.error]: 'red'
   }
 
-  private readonly ctx: ManageApiType
-  private logLevel!: string
-  private logPath!: string
+  readonly #ctx: ManageApiType
+  #logLevel!: string
+  #logPath!: string
 
   constructor (ctx: ManageApiType) {
-    this.ctx = ctx
+    this.#ctx = ctx
   }
 
-  private handleLog (type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
-    const logHeader = chalk[this.level[type] as ILogColor](
+  #handleLog (type: ILogType, ...msg: ILogArgvTypeWithError[]): void {
+    const logHeader = chalk[this.#level[type] as ILogColor](
       `[PicList ${type.toUpperCase()}]`
     )
     console.log(logHeader, ...msg)
-    this.logLevel = this.ctx.getConfig(configPaths.settings.logLevel)
-    this.logPath =
-      this.ctx.getConfig<Undefinable<string>>(configPaths.settings.logPath) ||
-      path.join(this.ctx.baseDir, './manage.log')
+    this.#logLevel = this.#ctx.getConfig(configPaths.settings.logLevel)
+    this.#logPath =
+      this.#ctx.getConfig<Undefinable<string>>(configPaths.settings.logPath) ||
+      path.join(this.#ctx.baseDir, './manage.log')
     setTimeout(() => {
       try {
-        const result = this.checkLogFileIsLarge(this.logPath)
+        const result = this.#checkLogFileIsLarge(this.#logPath)
         if (result.isLarge) {
           const warningMsg = `Log file is too large (> ${
             result.logFileSizeLimit! / 1024 / 1024 || '10'
           } MB), recreate log file`
           console.log(chalk.yellow('[PicList WARN]:'), warningMsg)
-          this.recreateLogFile(this.logPath)
+          this.#recreateLogFile(this.#logPath)
           msg.unshift(warningMsg)
         }
-        this.handleWriteLog(this.logPath, type, ...msg)
+        this.#handleWriteLog(this.#logPath, type, ...msg)
       } catch (e) {
         console.error('[PicList Error] on checking log file size', e)
       }
     }, 0)
   }
 
-  private checkLogFileIsLarge (logPath: string): {
+  #checkLogFileIsLarge (logPath: string): {
     isLarge: boolean
     logFileSize?: number
     logFileSizeLimit?: number
@@ -61,7 +61,7 @@ export class ManageLogger implements ILogger {
       const logFileSize = fs.statSync(logPath).size
       const logFileSizeLimit =
         enforceNumber(
-          this.ctx.getConfig<Undefinable<number>>(
+          this.#ctx.getConfig<Undefinable<number>>(
             configPaths.settings.logFileSizeLimit
           ) || 10
         ) *
@@ -79,23 +79,23 @@ export class ManageLogger implements ILogger {
     }
   }
 
-  private recreateLogFile (logPath: string): void {
+  #recreateLogFile (logPath: string): void {
     if (fs.existsSync(logPath)) {
       fs.unlinkSync(logPath)
       fs.createFileSync(logPath)
     }
   }
 
-  private handleWriteLog (
+  #handleWriteLog (
     logPath: string,
     type: string,
     ...msg: ILogArgvTypeWithError[]
   ): void {
     try {
-      if (this.checkLogLevel(type, this.logLevel)) {
+      if (this.#checkLogLevel(type, this.#logLevel)) {
         let log = `${dayjs().format('YYYY-MM-DD HH:mm:ss')} [PicList ${type.toUpperCase()}] `
         msg.forEach((item: ILogArgvTypeWithError) => {
-          log += this.formatLogItem(item, type)
+          log += this.#formatLogItem(item, type)
         })
         log += '\n'
         fs.appendFileSync(logPath, log)
@@ -105,7 +105,7 @@ export class ManageLogger implements ILogger {
     }
   }
 
-  private formatLogItem (item: ILogArgvTypeWithError, type: string): string {
+  #formatLogItem (item: ILogArgvTypeWithError, type: string): string {
     let result = ''
     if (item instanceof Error && type === 'error') {
       result += `\n------Error Stack Begin------\n${util.format(item?.stack)}\n-------Error Stack End------- `
@@ -121,7 +121,7 @@ export class ManageLogger implements ILogger {
     return result
   }
 
-  private checkLogLevel (
+  #checkLogLevel (
     type: string,
     level: undefined | string | string[]
   ): boolean {
@@ -135,24 +135,24 @@ export class ManageLogger implements ILogger {
   }
 
   success (...msq: ILogArgvType[]): void {
-    return this.handleLog(ILogType.success, ...msq)
+    return this.#handleLog(ILogType.success, ...msq)
   }
 
   info (...msq: ILogArgvType[]): void {
-    return this.handleLog(ILogType.info, ...msq)
+    return this.#handleLog(ILogType.info, ...msq)
   }
 
   error (...msq: ILogArgvTypeWithError[]): void {
-    return this.handleLog(ILogType.error, ...msq)
+    return this.#handleLog(ILogType.error, ...msq)
   }
 
   warn (...msq: ILogArgvType[]): void {
-    return this.handleLog(ILogType.warn, ...msq)
+    return this.#handleLog(ILogType.warn, ...msq)
   }
 
   debug (...msq: ILogArgvType[]): void {
     if (isDev) {
-      this.handleLog(ILogType.info, ...msq)
+      this.#handleLog(ILogType.info, ...msq)
     }
   }
 }
diff --git a/src/main/manage/utils/threadPool.ts b/src/main/manage/utils/threadPool.ts
deleted file mode 100644
index 70bef7e9..00000000
--- a/src/main/manage/utils/threadPool.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-import { Worker, WorkerOptions } from 'worker_threads'
-
-interface Task {
-  data: any
-  workerOptions: WorkerOptions | undefined
-  resolve: (result: any) => void
-  reject: (error: any) => void
-}
-
-class ThreadPool {
-  private size: number
-  private workerPath: string
-  private availablePool: Worker[]
-  private taskQueue: Task[]
-  private busyPool: Worker[]
-  private callBackList: any[]
-
-  constructor (size: number, workerPath: string) {
-    this.size = size
-    this.workerPath = workerPath
-    this.availablePool = []
-    this.busyPool = []
-    for (let i = 0; i < this.size; i++) {
-      this.availablePool.push(new Worker(this.workerPath))
-    }
-    this.taskQueue = []
-    this.callBackList = []
-    this.init()
-  }
-
-  private init () {
-    for (const worker of this.availablePool) {
-      worker.on('message', (result) => {
-        const { data } = result
-        this.callBackList.shift()(data)
-        this.busyPool = this.busyPool.filter((w) => w.threadId !== worker.threadId)
-        this.availablePool.push(worker)
-        this.processQueue()
-      })
-    }
-  }
-
-  private processQueue () {
-    if (this.taskQueue.length === 0) return
-    if (this.availablePool.length === 0) return
-    const task = this.taskQueue.shift()
-    const worker = this.availablePool.pop()
-    if (worker && task) {
-      this.callBackList.push(task.resolve)
-      this.busyPool.push(worker)
-      worker.postMessage(task.data)
-    }
-  }
-
-  public async addTask (data: any, workerOptions?: WorkerOptions): Promise<any> {
-    return new Promise((resolve, reject) => {
-      this.taskQueue.push({ data, workerOptions, resolve, reject })
-      this.processQueue()
-    })
-  }
-
-  public async destroy (): Promise<void> {
-    const terminatePromises = this.availablePool.map((worker) => new Promise((resolve) => {
-      worker.terminate()
-      worker.on('exit', () => {
-        resolve(true)
-      })
-    }))
-    await Promise.all(terminatePromises)
-    this.availablePool = []
-    this.taskQueue = []
-  }
-}
-
-export default ThreadPool
diff --git a/src/main/server/index.ts b/src/main/server/index.ts
index 655b1294..5f565cc2 100644
--- a/src/main/server/index.ts
+++ b/src/main/server/index.ts
@@ -41,17 +41,17 @@ const uploadMulter = multer({
 })
 
 class Server {
-  private httpServer: http.Server
-  private config: IServerConfig
+  #httpServer: http.Server
+  #config: IServerConfig
 
   constructor () {
-    this.config = this.getConfigWithDefaults()
-    this.httpServer = http.createServer(this.handleRequest)
+    this.#config = this.getConfigWithDefaults()
+    this.#httpServer = http.createServer(this.#handleRequest)
   }
 
   getConfigWithDefaults () {
     let config = picgo.getConfig<IServerConfig>(configPaths.settings.server)
-    if (!this.isValidConfig(config)) {
+    if (!this.#isValidConfig(config)) {
       config = { port: DEFAULT_PORT, host: DEFAULT_HOST, enable: true }
       picgo.saveConfig({ [configPaths.settings.server]: config })
     }
@@ -59,20 +59,20 @@ class Server {
     return config
   }
 
-  private isValidConfig (config: IObj | undefined) {
+  #isValidConfig (config: IObj | undefined) {
     return config && config.port && config.host && (config.enable !== undefined)
   }
 
-  private handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
+  #handleRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
     switch (request.method) {
       case 'OPTIONS':
         handleResponse({ response })
         break
       case 'POST':
-        this.handlePostRequest(request, response)
+        this.#handlePostRequest(request, response)
         break
       case 'GET':
-        this.handleGetRequest(request, response)
+        this.#handleGetRequest(request, response)
         break
       default:
         logger.warn(`[PicList Server] don't support [${request.method}] method`)
@@ -81,7 +81,7 @@ class Server {
     }
   }
 
-  private handlePostRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
+  #handlePostRequest = (request: http.IncomingMessage, response: http.ServerResponse) => {
     const [url, query] = (request.url || '').split('?')
     if (!routers.getHandler(url, 'POST')) {
       logger.warn(`[PicList Server] don't support [${url}] endpoint`)
@@ -160,7 +160,7 @@ class Server {
     }
   }
 
-  private handleGetRequest = (_request: http.IncomingMessage, response: http.ServerResponse) => {
+  #handleGetRequest = (_request: http.IncomingMessage, response: http.ServerResponse) => {
     const [url, query] = (_request.url || '').split('?')
     if (!routers.getHandler(url, 'GET')) {
       logger.info(`[PicList Server] don't support [${url}] endpoint`)
@@ -178,23 +178,23 @@ class Server {
   }
 
   // port as string is a bug
-  private listen = (port: number | string) => {
-    logger.info(`[PicList Server] is listening at ${port} of ${this.config.host}`)
+  #listen = (port: number | string) => {
+    logger.info(`[PicList Server] is listening at ${port} of ${this.#config.host}`)
     if (typeof port === 'string') {
       port = parseInt(port, 10)
     }
-    this.httpServer.listen(port, this.config.host).on('error', async (err: ErrnoException) => {
+    this.#httpServer.listen(port, this.#config.host).on('error', async (err: ErrnoException) => {
       if (err.code === 'EADDRINUSE') {
         try {
           // make sure the system has a PicGo Server instance
-          await axios.post(ensureHTTPLink(`${this.config.host}:${port}/heartbeat`))
+          await axios.post(ensureHTTPLink(`${this.#config.host}:${port}/heartbeat`))
           logger.info(`[PicList Server] server is already running at ${port}`)
           this.shutdown(true)
         } catch (e) {
           logger.warn(`[PicList Server] ${port} is busy, trying with port ${(port as number) + 1}`)
           // fix a bug: not write an increase number to config file
           // to solve the auto number problem
-          this.listen((port as number) + 1)
+          this.#listen((port as number) + 1)
         }
       } else {
         logger.error('[PicList Server]', err)
@@ -203,13 +203,13 @@ class Server {
   }
 
   startup () {
-    if (this.config.enable) {
-      this.listen(this.config.port)
+    if (this.#config.enable) {
+      this.#listen(this.#config.port)
     }
   }
 
   shutdown (hasStarted?: boolean) {
-    this.httpServer.close()
+    this.#httpServer.close()
     if (!hasStarted) {
       logger.info('[PicList Server] shutdown')
     }
@@ -217,7 +217,7 @@ class Server {
 
   restart () {
     this.shutdown()
-    this.config = this.getConfigWithDefaults()
+    this.#config = this.getConfigWithDefaults()
     this.startup()
   }
 }
diff --git a/src/main/server/router.ts b/src/main/server/router.ts
index c1283869..2211be02 100644
--- a/src/main/server/router.ts
+++ b/src/main/server/router.ts
@@ -1,31 +1,31 @@
 type HttpMethod = 'GET' | 'POST'
 
 class Router {
-  private router = new Map<string, Map<HttpMethod, {handler: routeHandler, urlparams?: URLSearchParams}>>()
+  #router = new Map<string, Map<HttpMethod, {handler: routeHandler, urlparams?: URLSearchParams}>>()
 
-  private addRoute (method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
-    if (!this.router.has(url)) {
-      this.router.set(url, new Map())
+  #addRoute (method: HttpMethod, url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
+    if (!this.#router.has(url)) {
+      this.#router.set(url, new Map())
     }
-    this.router.get(url)!.set(method, { handler: callback, urlparams })
+    this.#router.get(url)!.set(method, { handler: callback, urlparams })
   }
 
   get (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
-    this.addRoute('GET', url, callback, urlparams)
+    this.#addRoute('GET', url, callback, urlparams)
   }
 
   post (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
-    this.addRoute('POST', url, callback, urlparams)
+    this.#addRoute('POST', url, callback, urlparams)
   }
 
   any (url: string, callback: routeHandler, urlparams?: URLSearchParams): void {
-    this.addRoute('GET', url, callback, urlparams)
-    this.addRoute('POST', url, callback, urlparams)
+    this.#addRoute('GET', url, callback, urlparams)
+    this.#addRoute('POST', url, callback, urlparams)
   }
 
   getHandler (url: string, method: HttpMethod) {
-    if (this.router.has(url)) {
-      const methods = this.router.get(url)!
+    if (this.#router.has(url)) {
+      const methods = this.#router.get(url)!
       if (methods.has(method)) {
         return methods.get(method)
       }
diff --git a/src/main/server/webServer/index.ts b/src/main/server/webServer/index.ts
index 22ab2bc4..7c5e27c7 100644
--- a/src/main/server/webServer/index.ts
+++ b/src/main/server/webServer/index.ts
@@ -40,8 +40,8 @@ function serveFile (res:http.ServerResponse, filePath: fs.PathLike) {
 }
 
 class WebServer {
-  private server!: http.Server
-  private config!: IStringKeyMap
+  #server!: http.Server
+  #config!: IStringKeyMap
 
   constructor () {
     this.loadConfig()
@@ -49,7 +49,7 @@ class WebServer {
   }
 
   loadConfig (): void {
-    this.config = {
+    this.#config = {
       enableWebServer: picgo.getConfig<boolean>(configPaths.settings.enableWebServer) || false,
       webServerHost: picgo.getConfig<string>(configPaths.settings.webServerHost) || '0.0.0.0',
       webServerPort: picgo.getConfig<number>(configPaths.settings.webServerPort) || 37777,
@@ -58,9 +58,9 @@ class WebServer {
   }
 
   initServer (): void {
-    this.server = http.createServer((req, res) => {
+    this.#server = http.createServer((req, res) => {
       const requestPath = req.url?.split('?')[0]
-      const filePath = path.join(this.config.webServerPath, decodeURIComponent(requestPath || ''))
+      const filePath = path.join(this.#config.webServerPath, decodeURIComponent(requestPath || ''))
 
       try {
         const stats = fs.statSync(filePath)
@@ -77,12 +77,12 @@ class WebServer {
   }
 
   start () {
-    if (this.config.enableWebServer) {
-      this.server
+    if (this.#config.enableWebServer) {
+      this.#server
         .listen(
-          this.config.webServerPort === 36699 ? 37777 : this.config.webServerPort,
-          this.config.webServerHost, () => {
-            logger.info(`Web server is running at http://${this.config.webServerHost}:${this.config.webServerPort}, root path is ${this.config.webServerPath}`)
+          this.#config.webServerPort === 36699 ? 37777 : this.#config.webServerPort,
+          this.#config.webServerHost, () => {
+            logger.info(`Web server is running at http://${this.#config.webServerHost}:${this.#config.webServerPort}, root path is ${this.#config.webServerPath}`)
           })
         .on('error', (err) => {
           logger.error(err)
@@ -93,7 +93,7 @@ class WebServer {
   }
 
   stop () {
-    this.server.close(() => {
+    this.#server.close(() => {
       logger.info('Web server is stopped')
     })
   }
diff --git a/src/main/utils/sshClient.ts b/src/main/utils/sshClient.ts
index b783a20a..13363bd5 100644
--- a/src/main/utils/sshClient.ts
+++ b/src/main/utils/sshClient.ts
@@ -11,11 +11,11 @@ class SSHClient {
   private static _client: NodeSSH
   private _isConnected = false
 
-  public static get instance (): SSHClient {
+  static get instance (): SSHClient {
     return this._instance || (this._instance = new this())
   }
 
-  public static get client (): NodeSSH {
+  static get client (): NodeSSH {
     return this._client || (this._client = new NodeSSH())
   }
 
@@ -23,7 +23,7 @@ class SSHClient {
     return path.replace(/\\/g, '/')
   }
 
-  public async connect (config: ISftpPlistConfig): Promise<boolean> {
+  async connect (config: ISftpPlistConfig): Promise<boolean> {
     const { username, password, privateKey, passphrase } = config
     const loginInfo: Config = privateKey
       ? { username, privateKeyPath: privateKey, passphrase: passphrase || undefined }
@@ -41,7 +41,7 @@ class SSHClient {
     }
   }
 
-  public async deleteFileSFTP (config: ISftpPlistConfig, remote: string): Promise<boolean> {
+  async deleteFileSFTP (config: ISftpPlistConfig, remote: string): Promise<boolean> {
     try {
       const client = new Client()
       const { username, password, privateKey, passphrase } = config
@@ -162,7 +162,7 @@ class SSHClient {
     return SSHClient.client.isConnected()
   }
 
-  public close (): void {
+  close (): void {
     SSHClient.client.dispose()
     this._isConnected = false
   }
diff --git a/src/renderer/apis/aliyun.ts b/src/renderer/apis/aliyun.ts
index 2d255ad0..e5138504 100644
--- a/src/renderer/apis/aliyun.ts
+++ b/src/renderer/apis/aliyun.ts
@@ -6,17 +6,7 @@ interface IConfigMap {
 }
 
 export default class AliyunApi {
-  private static createClient (config: IConfigMap['config']): OSS {
-    const { accessKeyId, accessKeySecret, bucket, area } = config
-    return new OSS({
-      accessKeyId,
-      accessKeySecret,
-      bucket,
-      region: area
-    })
-  }
-
-  private static getKey (fileName: string, path?: string): string {
+  static #getKey (fileName: string, path?: string): string {
     return path && path !== '/'
       ? `${path.replace(/^\/+|\/+$/, '')}/${fileName}`
       : fileName
@@ -25,8 +15,8 @@ export default class AliyunApi {
   static async delete (configMap: IConfigMap): Promise<boolean> {
     const { fileName, config } = configMap
     try {
-      const client = AliyunApi.createClient(config)
-      const key = AliyunApi.getKey(fileName, config.path)
+      const client = new OSS({ ...config, region: config.area })
+      const key = AliyunApi.#getKey(fileName, config.path)
       const result = await client.delete(key)
       return result.res.status === 204
     } catch (error) {
diff --git a/src/renderer/apis/github.ts b/src/renderer/apis/github.ts
index f0073e7e..f5e61653 100644
--- a/src/renderer/apis/github.ts
+++ b/src/renderer/apis/github.ts
@@ -7,13 +7,13 @@ interface IConfigMap {
 }
 
 export default class GithubApi {
-  private static createOctokit (token: string) {
+  static #createOctokit (token: string) {
     return new Octokit({
       auth: token
     })
   }
 
-  private static createKey (path: string | undefined, fileName: string): string {
+  static #createKey (path: string | undefined, fileName: string): string {
     const formatedFileName = fileName.replace(/%2F/g, '/')
     return path && path !== '/'
       ? `${path.replace(/^\/+|\/+$/, '')}/${formatedFileName}`
@@ -23,8 +23,8 @@ export default class GithubApi {
   static async delete (configMap: IConfigMap): Promise<boolean> {
     const { fileName, hash, config: { repo, token, branch, path } } = configMap
     const [owner, repoName] = repo.split('/')
-    const octokit = GithubApi.createOctokit(token)
-    const key = GithubApi.createKey(path, fileName)
+    const octokit = GithubApi.#createOctokit(token)
+    const key = GithubApi.#createKey(path, fileName)
     try {
       const { status } = await octokit.rest.repos.deleteFile({
         owner,
diff --git a/src/renderer/apis/imgur.ts b/src/renderer/apis/imgur.ts
index dff5b3a9..11e7e411 100644
--- a/src/renderer/apis/imgur.ts
+++ b/src/renderer/apis/imgur.ts
@@ -5,28 +5,8 @@ interface IConfigMap {
   hash?: string
 }
 
-interface IConfig {
-  headers: {
-    Authorization: string
-  }
-  timeout: number
-}
-
 export default class ImgurApi {
-  static baseUrl = 'https://api.imgur.com/3'
-  private static async makeRequest (
-    method: 'delete',
-    url: string,
-    config: IConfig
-  ): Promise<boolean> {
-    try {
-      const response: AxiosResponse = await axios[method](url, config)
-      return response.status === 200
-    } catch (error) {
-      console.error(error)
-      return false
-    }
-  }
+  static #baseUrl = 'https://api.imgur.com/3'
 
   static async delete (configMap: IConfigMap): Promise<boolean> {
     const {
@@ -37,17 +17,22 @@ export default class ImgurApi {
 
     if (username && accessToken) {
       Authorization = `Bearer ${accessToken}`
-      apiUrl = `${ImgurApi.baseUrl}/account/${username}/image/${hash}`
+      apiUrl = `${ImgurApi.#baseUrl}/account/${username}/image/${hash}`
     } else if (clientId) {
       Authorization = `Client-ID ${clientId}`
-      apiUrl = `${ImgurApi.baseUrl}/image/${hash}`
+      apiUrl = `${ImgurApi.#baseUrl}/image/${hash}`
     } else {
       return false
     }
-    const requestConfig: IConfig = {
-      headers: { Authorization },
-      timeout: 30000
+    try {
+      const response: AxiosResponse = await axios.delete(apiUrl, {
+        headers: { Authorization },
+        timeout: 30000
+      })
+      return response.status === 200
+    } catch (error) {
+      console.error(error)
+      return false
     }
-    return ImgurApi.makeRequest('delete', apiUrl, requestConfig)
   }
 }
diff --git a/src/renderer/apis/smms.ts b/src/renderer/apis/smms.ts
index 11763431..ae2c3647 100644
--- a/src/renderer/apis/smms.ts
+++ b/src/renderer/apis/smms.ts
@@ -6,7 +6,7 @@ interface IConfigMap {
 }
 
 export default class SmmsApi {
-  private static readonly baseUrl = 'https://smms.app/api/v2'
+  static readonly #baseUrl = 'https://smms.app/api/v2'
 
   static async delete (configMap: IConfigMap): Promise<boolean> {
     const { hash, config } = configMap
@@ -19,7 +19,7 @@ export default class SmmsApi {
 
     try {
       const response: AxiosResponse = await axios.get(
-        `${SmmsApi.baseUrl}/delete/${hash}`, {
+        `${SmmsApi.#baseUrl}/delete/${hash}`, {
           headers: {
             Authorization: token
           },
diff --git a/src/renderer/apis/tcyun.ts b/src/renderer/apis/tcyun.ts
index 19ce67cc..e4ff9d26 100644
--- a/src/renderer/apis/tcyun.ts
+++ b/src/renderer/apis/tcyun.ts
@@ -6,7 +6,7 @@ interface IConfigMap {
 }
 
 export default class TcyunApi {
-  private static createCOS (SecretId: string, SecretKey: string): COS {
+  static #createCOS (SecretId: string, SecretKey: string): COS {
     return new COS({
       SecretId,
       SecretKey
@@ -16,7 +16,7 @@ export default class TcyunApi {
   static async delete (configMap: IConfigMap): Promise<boolean> {
     const { fileName, config: { secretId, secretKey, bucket, area, path } } = configMap
     try {
-      const cos = TcyunApi.createCOS(secretId, secretKey)
+      const cos = TcyunApi.#createCOS(secretId, secretKey)
       let key
       if (path === '/' || !path) {
         key = `/${fileName}`
diff --git a/src/renderer/i18n/index.ts b/src/renderer/i18n/index.ts
index d3c5a37f..08bf77ab 100644
--- a/src/renderer/i18n/index.ts
+++ b/src/renderer/i18n/index.ts
@@ -5,45 +5,45 @@ import bus from '@/utils/bus'
 import { builtinI18nList } from '#/i18n'
 
 export class I18nManager {
-  private i18n: I18n | null = null
-  private i18nFileList: II18nItem[] = builtinI18nList
+  #i18n: I18n | null = null
+  #i18nFileList: II18nItem[] = builtinI18nList
 
-  private getLanguageList () {
+  #getLanguageList () {
     ipcRenderer.send(GET_LANGUAGE_LIST)
     ipcRenderer.once(GET_LANGUAGE_LIST, (event, list: II18nItem[]) => {
-      this.i18nFileList = list
+      this.#i18nFileList = list
     })
   }
 
-  private getCurrentLanguage () {
+  #getCurrentLanguage () {
     ipcRenderer.send(GET_CURRENT_LANGUAGE)
     ipcRenderer.once(GET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
-      this.setLocales(lang, locales)
+      this.#setLocales(lang, locales)
       bus.emit(FORCE_UPDATE)
     })
   }
 
-  private setLocales (lang: string, locales: ILocales) {
+  #setLocales (lang: string, locales: ILocales) {
     const objectAdapter = new ObjectAdapter({
       [lang]: locales
     })
-    this.i18n = new I18n({
+    this.#i18n = new I18n({
       adapter: objectAdapter,
       defaultLanguage: lang
     })
   }
 
   constructor () {
-    this.getCurrentLanguage()
-    this.getLanguageList()
+    this.#getCurrentLanguage()
+    this.#getLanguageList()
     ipcRenderer.on(SET_CURRENT_LANGUAGE, (event, lang: string, locales: ILocales) => {
-      this.setLocales(lang, locales)
+      this.#setLocales(lang, locales)
       bus.emit(FORCE_UPDATE)
     })
   }
 
   T (key: ILocalesKey, args: IStringKeyMap = {}): string {
-    return this.i18n?.translate(key, args) || key
+    return this.#i18n?.translate(key, args) || key
   }
 
   setCurrentLanguage (lang: string) {
@@ -51,7 +51,7 @@ export class I18nManager {
   }
 
   get languageList () {
-    return this.i18nFileList
+    return this.#i18nFileList
   }
 }
 
diff --git a/src/renderer/utils/db.ts b/src/renderer/utils/db.ts
index 5b3ad143..fd7181ea 100644
--- a/src/renderer/utils/db.ts
+++ b/src/renderer/utils/db.ts
@@ -25,36 +25,36 @@ import { getRawData } from './common'
 
 export class GalleryDB implements IGalleryDB {
   async get<T> (filter?: IFilter): Promise<IGetResult<T>> {
-    const res = await this.msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
+    const res = await this.#msgHandler<IGetResult<T>>(PICGO_GET_DB, filter)
     return res
   }
 
   async insert<T> (value: T): Promise<IResult<T>> {
-    const res = await this.msgHandler<IResult<T>>(PICGO_INSERT_DB, value)
+    const res = await this.#msgHandler<IResult<T>>(PICGO_INSERT_DB, value)
     return res
   }
 
   async insertMany<T> (value: T[]): Promise<IResult<T>[]> {
-    const res = await this.msgHandler<IResult<T>[]>(PICGO_INSERT_MANY_DB, value)
+    const res = await this.#msgHandler<IResult<T>[]>(PICGO_INSERT_MANY_DB, value)
     return res
   }
 
   async updateById (id: string, value: IObject): Promise<boolean> {
-    const res = await this.msgHandler<boolean>(PICGO_UPDATE_BY_ID_DB, id, value)
+    const res = await this.#msgHandler<boolean>(PICGO_UPDATE_BY_ID_DB, id, value)
     return res
   }
 
   async getById<T> (id: string): Promise<IResult<T> | undefined> {
-    const res = await this.msgHandler<IResult<T> | undefined>(PICGO_GET_BY_ID_DB, id)
+    const res = await this.#msgHandler<IResult<T> | undefined>(PICGO_GET_BY_ID_DB, id)
     return res
   }
 
   async removeById (id: string): Promise<void> {
-    const res = await this.msgHandler<void>(PICGO_REMOVE_BY_ID_DB, id)
+    const res = await this.#msgHandler<void>(PICGO_REMOVE_BY_ID_DB, id)
     return res
   }
 
-  private msgHandler<T> (method: string, ...args: any[]): Promise<T> {
+  #msgHandler<T> (method: string, ...args: any[]): Promise<T> {
     return new Promise((resolve) => {
       const callbackId = uuid()
       const callback = (event: IpcRendererEvent, data: T, returnCallbackId: string) => {

From 2fab2a97652bf0f9939d591484cdb05ef755dcc2 Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Thu, 18 Apr 2024 17:31:37 +0800
Subject: [PATCH 2/8] :hammer: Refactor(custom): refactor encrypt class

---
 src/main/utils/aesHelper.ts | 25 ++++++++++---------------
 1 file changed, 10 insertions(+), 15 deletions(-)

diff --git a/src/main/utils/aesHelper.ts b/src/main/utils/aesHelper.ts
index 6a1c4cdf..a974f80d 100644
--- a/src/main/utils/aesHelper.ts
+++ b/src/main/utils/aesHelper.ts
@@ -3,18 +3,15 @@ import picgo from '@core/picgo'
 import { DEFAULT_AES_PASSWORD } from '~/universal/utils/static'
 import { configPaths } from '~/universal/utils/configPaths'
 
-function getDerivedKey (): Buffer {
-  const userPassword = picgo.getConfig<string>(configPaths.settings.aesPassword) || DEFAULT_AES_PASSWORD
-  const fixedSalt = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
-  const fixedIterations = 100000
-  const keyLength = 32
-  return crypto.pbkdf2Sync(userPassword, fixedSalt, fixedIterations, keyLength, 'sha512')
-}
-
 export class AESHelper {
   key: Buffer
+
   constructor () {
-    this.key = getDerivedKey()
+    const userPassword = picgo.getConfig<string>(configPaths.settings.aesPassword) || DEFAULT_AES_PASSWORD
+    const fixedSalt = Buffer.from('a8b3c4d2e4f5098712345678feedc0de', 'hex')
+    const fixedIterations = 100000
+    const keyLength = 32
+    this.key = crypto.pbkdf2Sync(userPassword, fixedSalt, fixedIterations, keyLength, 'sha512')
   }
 
   encrypt (plainText: string) {
@@ -22,17 +19,15 @@ export class AESHelper {
     const cipher = crypto.createCipheriv('aes-256-cbc', this.key, iv)
     let encrypted = cipher.update(plainText, 'utf8', 'hex')
     encrypted += cipher.final('hex')
-    const encryptedData = `${iv.toString('hex')}:${encrypted}`
-    return encryptedData
+    return `${iv.toString('hex')}:${encrypted}`
   }
 
   decrypt (encryptedData: string) {
-    const parts = encryptedData.split(':')
-    if (parts.length !== 2) {
+    const [ivHex, encryptedText] = encryptedData.split(':')
+    if (!ivHex || !encryptedText) {
       return '{}'
     }
-    const iv = Buffer.from(parts[0], 'hex')
-    const encryptedText = parts[1]
+    const iv = Buffer.from(ivHex, 'hex')
     const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, iv)
     let decrypted = decipher.update(encryptedText, 'hex', 'utf8')
     decrypted += decipher.final('utf8')

From 2087d9a3550937e6e40ade050db962bebffd0278 Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Fri, 19 Apr 2024 21:51:23 +0800
Subject: [PATCH 3/8] :sparkles: Feature(custom): add feedback entry

---
 public/i18n/en.yml              | 1 +
 public/i18n/zh-CN.yml           | 1 +
 public/i18n/zh-TW.yml           | 1 +
 src/main/events/remotes/menu.ts | 9 ++++++++-
 src/universal/types/i18n.d.ts   | 1 +
 5 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/public/i18n/en.yml b/public/i18n/en.yml
index 543dccb7..cc21af5b 100644
--- a/public/i18n/en.yml
+++ b/public/i18n/en.yml
@@ -22,6 +22,7 @@ CONFIG_THING: Config ${c}
 FIND_NEW_VERSION: Find New Version
 NO_MORE_NOTICE: No More Notice
 SHOW_DEVTOOLS: Show Devtools
+FEEDBACK: Feedback
 CURRENT_PICBED: Current Picbed
 START_WATCH_CLIPBOARD: Start Watch Clipboard
 STOP_WATCH_CLIPBOARD: Stop Watch Clipboard
diff --git a/public/i18n/zh-CN.yml b/public/i18n/zh-CN.yml
index 38a3cde2..740eb8f7 100644
--- a/public/i18n/zh-CN.yml
+++ b/public/i18n/zh-CN.yml
@@ -22,6 +22,7 @@ CONFIG_THING: 配置${c}
 FIND_NEW_VERSION: 发现新版本
 NO_MORE_NOTICE: 以后不再提醒
 SHOW_DEVTOOLS: 打开开发者工具
+FEEDBACK: 反馈问题
 CURRENT_PICBED: 当前图床
 START_WATCH_CLIPBOARD: 开始监听剪贴板
 STOP_WATCH_CLIPBOARD: 停止监听剪贴板
diff --git a/public/i18n/zh-TW.yml b/public/i18n/zh-TW.yml
index 48d5e0db..02b0d376 100644
--- a/public/i18n/zh-TW.yml
+++ b/public/i18n/zh-TW.yml
@@ -22,6 +22,7 @@ CONFIG_THING: 設定${c}
 FIND_NEW_VERSION: 發現新版本
 NO_MORE_NOTICE: 以後不再提醒
 SHOW_DEVTOOLS: 開啟開發者工具
+FEEDBACK: 問題反饋
 CURRENT_PICBED: 當前圖床
 START_WATCH_CLIPBOARD: 開始監聽剪貼簿
 STOP_WATCH_CLIPBOARD: 停止監聽剪貼簿
diff --git a/src/main/events/remotes/menu.ts b/src/main/events/remotes/menu.ts
index 05cf1c01..3ad18f2c 100644
--- a/src/main/events/remotes/menu.ts
+++ b/src/main/events/remotes/menu.ts
@@ -2,7 +2,7 @@
 import pkg from 'root/package.json'
 
 // Electron modules
-import { Menu, BrowserWindow, app, dialog } from 'electron'
+import { Menu, BrowserWindow, app, dialog, shell } from 'electron'
 
 // Custom utilities and modules
 import windowManager from 'apis/app/window/windowManager'
@@ -140,6 +140,13 @@ const buildMainPageMenu = (win: BrowserWindow) => {
       click () {
         win?.webContents?.openDevTools({ mode: 'detach' })
       }
+    },
+    {
+      label: T('FEEDBACK'),
+      click () {
+        const url = 'https://github.com/Kuingsmile/PicList/issues'
+        shell.openExternal(url)
+      }
     }
   ]
   // @ts-ignore
diff --git a/src/universal/types/i18n.d.ts b/src/universal/types/i18n.d.ts
index 08de9b1e..baa9b2a4 100644
--- a/src/universal/types/i18n.d.ts
+++ b/src/universal/types/i18n.d.ts
@@ -23,6 +23,7 @@ interface ILocales {
   FIND_NEW_VERSION: string
   NO_MORE_NOTICE: string
   SHOW_DEVTOOLS: string
+  FEEDBACK: string
   CURRENT_PICBED: string
   START_WATCH_CLIPBOARD: string
   STOP_WATCH_CLIPBOARD: string

From 939c907d232a6454c0624ea26d1bcbd43cc39ee4 Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Sat, 20 Apr 2024 11:41:56 +0800
Subject: [PATCH 4/8] :sparkles: Feature(custom): auto refresh after change
 custom url

ISSUES CLOSED: #191
---
 src/renderer/manage/pages/bucketPage.vue    | 37 +++++++++------------
 src/renderer/manage/pages/manageSetting.vue |  2 +-
 2 files changed, 17 insertions(+), 22 deletions(-)

diff --git a/src/renderer/manage/pages/bucketPage.vue b/src/renderer/manage/pages/bucketPage.vue
index 8e78fdef..ab59ff6b 100644
--- a/src/renderer/manage/pages/bucketPage.vue
+++ b/src/renderer/manage/pages/bucketPage.vue
@@ -21,7 +21,7 @@
           style="width: 200px;"
           :persistent="false"
           teleported
-          @change="handleChangeCustomUrl"
+          @change="handleChangeCustomUrlInput"
         >
           <el-option
             v-for="item in customDomainList"
@@ -35,7 +35,7 @@
           v-model="currentCustomDomain"
           :placeholder="$T('MANAGE_BUCKET_PAGE_CUSTOM_URL_INPUT_PLACEHOLDER')"
           style="width: 200px;"
-          @blur="handleChangeCustomUrl"
+          @blur="handleChangeCustomUrlInput"
         />
         <el-link
           v-else
@@ -2063,23 +2063,14 @@ async function handleClickFile (item: any) {
   }
 }
 
+async function handleChangeCustomUrlInput () {
+  await handleChangeCustomUrl()
+  await forceRefreshFileList()
+}
 // 自定义域名相关
 
 async function handleChangeCustomUrl () {
-  if (currentPicBedName.value === 'github') {
-    isShowLoadingPage.value = true
-    if (isLoadingData.value) {
-      ElNotification({
-        title: $T('MANAGE_BUCKET_CHANGE_CUSTOM_URL_TITLE'),
-        message: $T('MANAGE_BUCKET_CHANGE_CUSTOM_URL_MSG'),
-        type: 'error',
-        duration: 2000
-      })
-    }
-    isShowLoadingPage.value = true
-    await resetParam(true)
-    isShowLoadingPage.value = false
-  } else if (['aliyun', 'tcyun', 'qiniu', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value)) {
+  if (['aliyun', 'tcyun', 'qiniu', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value)) {
     const currentConfigs = await getConfig<any>('picBed')
     const currentConfig = currentConfigs[configMap.alias]
     const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
@@ -2174,7 +2165,7 @@ async function initCustomDomainList () {
         currentCustomDomain.value = `https://${configMap.bucketName}.s3.amazonaws.com`
       }
     }
-    handleChangeCustomUrl()
+    await handleChangeCustomUrl()
   } else if (currentPicBedName.value === 'webdavplist') {
     const currentConfigs = await getConfig<any>('picBed')
     const currentConfig = currentConfigs[configMap.alias]
@@ -2188,17 +2179,20 @@ async function initCustomDomainList () {
       }
       currentCustomDomain.value = endpoint
     }
-    handleChangeCustomUrl()
+    await handleChangeCustomUrl()
   } else if (currentPicBedName.value === 'local' || currentPicBedName.value === 'sftp') {
     const currentConfigs = await getConfig<any>('picBed')
     const currentConfig = currentConfigs[configMap.alias]
     const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
     if (currentTransformedConfig[configMap.bucketName] && currentTransformedConfig[configMap.bucketName]?.customUrl) {
       currentCustomDomain.value = currentTransformedConfig[configMap.bucketName].customUrl ?? ''
+      if (manageStore.config.settings.isForceCustomUrlHttps && currentCustomDomain.value.startsWith('http://')) {
+        currentCustomDomain.value = currentCustomDomain.value.replace('http://', 'https://')
+      }
     } else {
       currentCustomDomain.value = ''
     }
-    handleChangeCustomUrl()
+    await handleChangeCustomUrl()
   }
 }
 
@@ -2232,6 +2226,7 @@ async function resetParam (force: boolean = false) {
   fileSortSizeReverse.value = false
   fileSortTimeReverse.value = false
   if (!isAutoRefresh.value && !force && !paging.value) {
+    console.log('use cache')
     const cachedData = await searchExistFileList()
     if (cachedData.length > 0) {
       currentPageFilesInfo.push(...cachedData[0].value.fullList)
@@ -2283,7 +2278,7 @@ watch(route, async (newRoute) => {
     const parsedConfigMap = JSON.parse(queryConfigMap)
     Object.assign(configMap, parsedConfigMap)
     await initCustomDomainList()
-    await resetParam(false)
+    await resetParam(true)
     isShowLoadingPage.value = false
   }
 })
@@ -3683,7 +3678,7 @@ onBeforeMount(async () => {
   await manageStore.refreshConfig()
   isShowLoadingPage.value = true
   await initCustomDomainList()
-  await resetParam(false)
+  await resetParam(true)
   isShowLoadingPage.value = false
   document.addEventListener('keydown', handleDetectShiftKey)
   document.addEventListener('keyup', handleDetectShiftKey)
diff --git a/src/renderer/manage/pages/manageSetting.vue b/src/renderer/manage/pages/manageSetting.vue
index 261ea880..30bca98e 100644
--- a/src/renderer/manage/pages/manageSetting.vue
+++ b/src/renderer/manage/pages/manageSetting.vue
@@ -618,7 +618,7 @@ async function initData () {
   form.isShowThumbnail = config.settings.isShowThumbnail ?? false
   form.isShowList = config.settings.isShowList ?? false
   form.isIgnoreCase = config.settings.isIgnoreCase ?? false
-  form.isForceCustomUrlHttps = config.settings.isForceCustomUrlHttps ?? true
+  form.isForceCustomUrlHttps = config.settings.isForceCustomUrlHttps ?? false
   form.isEncodeUrl = config.settings.isEncodeUrl ?? false
   form.isUploadKeepDirStructure = config.settings.isUploadKeepDirStructure ?? true
   form.isDownloadFileKeepDirStructure = config.settings.isDownloadKeepDirStructure ?? false

From 36816814011ab4b9d7788891790d0b4c2a75637b Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Mon, 22 Apr 2024 16:27:31 +0800
Subject: [PATCH 5/8] :bug: Fix(custom): fix  aws s3 urlprefix bug

---
 package.json                             |  2 +-
 src/main/utils/deleteFunc.ts             | 23 +++++++++++++++++------
 src/renderer/manage/pages/bucketPage.vue |  5 ++---
 yarn.lock                                |  8 ++++----
 4 files changed, 24 insertions(+), 14 deletions(-)

diff --git a/package.json b/package.json
index 60d71b28..e69d3d96 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
     "multer": "^1.4.5-lts.1",
     "node-ssh-no-cpu-features": "^1.0.1",
     "nodejs-file-downloader": "^4.12.1",
-    "piclist": "^1.8.5",
+    "piclist": "^1.8.6",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.0",
     "proxy-agent": "^5.0.0",
diff --git a/src/main/utils/deleteFunc.ts b/src/main/utils/deleteFunc.ts
index c808ab90..648a2050 100644
--- a/src/main/utils/deleteFunc.ts
+++ b/src/main/utils/deleteFunc.ts
@@ -134,12 +134,23 @@ export async function removeFileFromS3InMain (configMap: IStringKeyMap, dogeMode
         sessionToken: configMap.config.sessionToken
       }
     }
-    const client = new S3Client(s3Options)
-    const command = new DeleteObjectCommand({
-      Bucket: bucketName,
-      Key: fileKey
-    })
-    const result = await client.send(command)
+    let result: any
+    try {
+      const client = new S3Client(s3Options)
+      const command = new DeleteObjectCommand({
+        Bucket: bucketName,
+        Key: fileKey
+      })
+      result = await client.send(command)
+    } catch (err: any) {
+      s3Options.region = 'us-east-1'
+      const client = new S3Client(s3Options)
+      const command = new DeleteObjectCommand({
+        Bucket: bucketName,
+        Key: fileKey
+      })
+      result = await client.send(command)
+    }
     return result.$metadata.httpStatusCode === 204
   } catch (err: any) {
     console.log(err)
diff --git a/src/renderer/manage/pages/bucketPage.vue b/src/renderer/manage/pages/bucketPage.vue
index ab59ff6b..a08fbf99 100644
--- a/src/renderer/manage/pages/bucketPage.vue
+++ b/src/renderer/manage/pages/bucketPage.vue
@@ -2832,9 +2832,8 @@ async function getBucketFileListBackStage () {
   isLoadingData.value = true
   const fileTransferStore = useFileTransferStore()
   fileTransferStore.resetFileTransferList()
-  if (currentPicBedName.value === 'webdavplist' ||
-    currentPicBedName.value === 'local' ||
-    currentPicBedName.value === 'sftp') {
+  const picBedNamesArr = ['webdavplist', 'local', 'sftp']
+  if (picBedNamesArr.includes(currentPicBedName.value)) {
     param.baseDir = configMap.baseDir
     param.webPath = configMap.webPath
   }
diff --git a/yarn.lock b/yarn.lock
index 92dec1bd..356aa487 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12397,10 +12397,10 @@ performance-now@^2.1.0:
   resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
 
-piclist@^1.8.5:
-  version "1.8.5"
-  resolved "https://registry.yarnpkg.com/piclist/-/piclist-1.8.5.tgz#982f481420c83cb42d6c1ff07bd2ad467ed71539"
-  integrity sha512-RrSWViLbgTb3VeZ5Poajo6oyRbpFovF3g48/JiyMG6XChdt7BZ1dD/URmPzfrQNZM+bwunSwJr1yjhK87cSvwA==
+piclist@^1.8.6:
+  version "1.8.6"
+  resolved "https://registry.yarnpkg.com/piclist/-/piclist-1.8.6.tgz#858706b5628953926ce0777d4a287482c2ebee10"
+  integrity sha512-ja/bUCifnoI/J2zTI79Kkuao4rdm64ZL6kqUFcZ2aSHQULLsFMkgDPiPPvVN5wF8Sv7mX9z4fVmj6UVM7r5uQQ==
   dependencies:
     "@aws-sdk/client-s3" "3.421.0"
     "@aws-sdk/lib-storage" "3.421.0"

From 25648eac4f4783db130d96f01aa0d1f777e92182 Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Tue, 23 Apr 2024 10:22:36 +0800
Subject: [PATCH 6/8] :sparkles: Feature(custom): change timestamp to
 milliseconds

ISSUES CLOSED: #194
---
 package.json | 2 +-
 yarn.lock    | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index e69d3d96..587dd2c9 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
     "multer": "^1.4.5-lts.1",
     "node-ssh-no-cpu-features": "^1.0.1",
     "nodejs-file-downloader": "^4.12.1",
-    "piclist": "^1.8.6",
+    "piclist": "^1.8.7",
     "pinia": "^2.1.7",
     "pinia-plugin-persistedstate": "^3.2.0",
     "proxy-agent": "^5.0.0",
diff --git a/yarn.lock b/yarn.lock
index 356aa487..b897d756 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12397,10 +12397,10 @@ performance-now@^2.1.0:
   resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
 
-piclist@^1.8.6:
-  version "1.8.6"
-  resolved "https://registry.yarnpkg.com/piclist/-/piclist-1.8.6.tgz#858706b5628953926ce0777d4a287482c2ebee10"
-  integrity sha512-ja/bUCifnoI/J2zTI79Kkuao4rdm64ZL6kqUFcZ2aSHQULLsFMkgDPiPPvVN5wF8Sv7mX9z4fVmj6UVM7r5uQQ==
+piclist@^1.8.7:
+  version "1.8.7"
+  resolved "https://registry.yarnpkg.com/piclist/-/piclist-1.8.7.tgz#aa40af42762b857ac0c45a097421278a301320cf"
+  integrity sha512-asSc588Fh1aMpIq/guqqHGhZb0wsLn+wZllKbtznDasbh4zNZvQECNDxRGVtmvsSYJlR+V+yyA2Z85AW/aQqyA==
   dependencies:
     "@aws-sdk/client-s3" "3.421.0"
     "@aws-sdk/lib-storage" "3.421.0"

From bcb47608d7a5b71f1cdbb5981af651180f7aeea7 Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Sun, 28 Apr 2024 21:57:52 +0800
Subject: [PATCH 7/8] :pencil: Docs(custom): prepare for 2.8.4

---
 currentVersion.md    | 18 ++++++++++++------
 currentVersion_en.md | 17 +++++++++++------
 2 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/currentVersion.md b/currentVersion.md
index 20f12774..4274de9a 100644
--- a/currentVersion.md
+++ b/currentVersion.md
@@ -1,9 +1,15 @@
-✨ Features
 
-- 现在不再对gif图片进行格式转换
+### ✨ Features
 
-🐛 Bug Fixes
+- 管理功能
+  - 现在修改自定义域名后会自动强制刷新当前页面
+  - 现在第一次进入管理页面时默认获取云端最新文件列表
+- 现在内置s3图床默认允许自签证书
+- 现在高级重命名中的时间戳精确到毫秒
 
-- 修复了重新保存配置后图片水印功能失效的问题
-- 修复了同时打开主界面和mini窗口时,重命名窗口定位错误的问题
-- 修复了特殊情况下软件界面异常退出并导致后台CPU占用过高的问题
+### 🐛 Bug Fixes
+
+- 管理功能
+  - 修复了强制https对本地图床没有生效的问题
+- 修复了不填写区域时,minio无法正常删除图片的问题
+- 修复了内置s3图床,配合minio使用时会额外添加桶名的问题
diff --git a/currentVersion_en.md b/currentVersion_en.md
index 735ae34c..4f297a3e 100644
--- a/currentVersion_en.md
+++ b/currentVersion_en.md
@@ -1,9 +1,14 @@
-✨ Features
+### ✨ Features
 
-- Now no longer convert gif images
+- Manage
+  - Now, after modifying the custom domain name, the current page will be automatically forced to refresh
+  - Now, the cloud-side latest file list is obtained by default when entering the management page for the first time
+- Now the built-in s3 image bed defaults to allowing self-signed certificates
+- Now the timestamp in advanced renaming is accurate to milliseconds
 
-🐛 Bug Fixes
+### 🐛 Bug Fixes
 
-- Fix the problem that the image watermark function is invalid after re-saving the configuration
-- Fix the problem that the rename window is positioned incorrectly when the main interface and mini window are opened at the same time
-- Fix the problem that the software interface exits abnormally under special circumstances and causes high CPU usage in the background
+- Manage
+  - Fixed the problem that forcing https does not take effect on the local image bed
+- Fixed the problem that Minio cannot delete images normally when the region is not filled in
+- Fixed the problem that the built-in s3 image bed will add an additional bucket name when used with Minio
\ No newline at end of file

From ec92be8cfbe093ef389623b90be4256dc92b3e3c Mon Sep 17 00:00:00 2001
From: Kuingsmile <ma_shiqing@163.com>
Date: Sun, 28 Apr 2024 21:58:17 +0800
Subject: [PATCH 8/8] :tada: Release: v2.8.4

---
 CHANGELOG.md | 21 +++++++++++++++++++++
 package.json |  2 +-
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb685df2..0bc45d86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,24 @@
+## :tada: 2.8.4 (2024-04-28)
+
+
+### :sparkles: Features
+
+* **custom:** add feedback entry ([2087d9a](https://github.com/Kuingsmile/piclist/commit/2087d9a))
+* **custom:** auto refresh after change custom url ([939c907](https://github.com/Kuingsmile/piclist/commit/939c907)), closes [#191](https://github.com/Kuingsmile/piclist/issues/191)
+* **custom:** change timestamp to milliseconds ([25648ea](https://github.com/Kuingsmile/piclist/commit/25648ea)), closes [#194](https://github.com/Kuingsmile/piclist/issues/194)
+
+
+### :bug: Bug Fixes
+
+* **custom:** fix  aws s3 urlprefix bug ([3681681](https://github.com/Kuingsmile/piclist/commit/3681681))
+
+
+### :pencil: Documentation
+
+* **custom:** prepare for 2.8.4 ([bcb4760](https://github.com/Kuingsmile/piclist/commit/bcb4760))
+
+
+
 ## :tada: 2.8.3 (2024-04-11)
 
 
diff --git a/package.json b/package.json
index 587dd2c9..a87ad66e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "piclist",
-  "version": "2.8.3",
+  "version": "2.8.4",
   "author": {
     "name": "Kuingsmile",
     "email": "pkukuing@gmail.com"