361 lines
11 KiB
JavaScript
361 lines
11 KiB
JavaScript
const fs = require('node:fs')
|
||
const path = require('node:path')
|
||
const jsonApi = require('@docmirror/mitmproxy/src/json')
|
||
const lodash = require('lodash')
|
||
const request = require('request')
|
||
const defConfig = require('./config/index.js')
|
||
const mergeApi = require('./merge.js')
|
||
const Shell = require('./shell')
|
||
const log = require('./utils/util.log')
|
||
|
||
let configTarget = lodash.cloneDeep(defConfig)
|
||
|
||
function get () {
|
||
return configTarget
|
||
}
|
||
|
||
const getDefaultConfigBasePath = function () {
|
||
return get().server.setting.userBasePath
|
||
}
|
||
|
||
function _getRemoteSavePath (suffix = '') {
|
||
const dir = getDefaultConfigBasePath()
|
||
if (!fs.existsSync(dir)) {
|
||
fs.mkdirSync(dir)
|
||
}
|
||
return path.join(dir, `/remote_config${suffix}.json5`)
|
||
}
|
||
|
||
function _getConfigPath () {
|
||
const dir = getDefaultConfigBasePath()
|
||
if (!fs.existsSync(dir)) {
|
||
fs.mkdirSync(dir)
|
||
} else {
|
||
// 兼容1.7.3及以下版本的配置文件处理逻辑
|
||
const newFilePath = path.join(dir, '/config.json')
|
||
const oldFilePath = path.join(dir, '/config.json5')
|
||
if (!fs.existsSync(newFilePath) && fs.existsSync(oldFilePath)) {
|
||
return oldFilePath // 如果新文件不存在,且旧文件存在,则返回旧文件路径
|
||
}
|
||
return newFilePath
|
||
}
|
||
return path.join(dir, '/config.json')
|
||
}
|
||
|
||
let timer
|
||
const configApi = {
|
||
async startAutoDownloadRemoteConfig () {
|
||
if (timer != null) {
|
||
clearInterval(timer)
|
||
}
|
||
const download = async () => {
|
||
try {
|
||
await configApi.downloadRemoteConfig()
|
||
configApi.reload()
|
||
} catch (e) {
|
||
log.error('定时下载远程配置并重载配置失败', e)
|
||
}
|
||
}
|
||
await download()
|
||
timer = setInterval(download, 24 * 60 * 60 * 1000) // 1天
|
||
},
|
||
async downloadRemoteConfig () {
|
||
if (get().app.remoteConfig.enabled !== true) {
|
||
// 删除保存的远程配置文件
|
||
configApi.deleteRemoteConfigFile()
|
||
configApi.deleteRemoteConfigFile('_personal')
|
||
return
|
||
}
|
||
|
||
const remoteConfig = get().app.remoteConfig
|
||
await configApi.doDownloadCommonRemoteConfig(remoteConfig.url)
|
||
await configApi.doDownloadCommonRemoteConfig(remoteConfig.personalUrl, '_personal')
|
||
},
|
||
doDownloadCommonRemoteConfig (remoteConfigUrl, suffix = '') {
|
||
if (get().app.remoteConfig.enabled !== true) {
|
||
return
|
||
}
|
||
if (!remoteConfigUrl) {
|
||
// 删除保存的远程配置文件
|
||
configApi.deleteRemoteConfigFile(suffix)
|
||
return
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
log.info('开始下载远程配置:', remoteConfigUrl)
|
||
|
||
const headers = {
|
||
'Cache-Control': 'no-cache', // 禁止使用缓存
|
||
'Pragma': 'no-cache', // 禁止使用缓存
|
||
}
|
||
if (remoteConfigUrl.startsWith('https://raw.githubusercontent.com/')) {
|
||
headers['Server-Name'] = 'baidu.com'
|
||
}
|
||
request(remoteConfigUrl, { headers }, (error, response, body) => {
|
||
if (error) {
|
||
log.error(`下载远程配置失败: ${remoteConfigUrl}, error:`, error, ', response:', response, ', body:', body)
|
||
reject(error)
|
||
return
|
||
}
|
||
if (response && response.statusCode === 200) {
|
||
if (body == null || body.length < 2) {
|
||
log.warn('下载远程配置成功,但内容为空:', remoteConfigUrl)
|
||
resolve()
|
||
return
|
||
} else {
|
||
log.info('下载远程配置成功:', remoteConfigUrl)
|
||
}
|
||
|
||
// 尝试解析远程配置,如果解析失败,则不保存它
|
||
let remoteConfig
|
||
try {
|
||
remoteConfig = jsonApi.parse(body)
|
||
} catch (e) {
|
||
log.error(`远程配置内容格式不正确, url: ${remoteConfigUrl}, body: ${body}`)
|
||
remoteConfig = null
|
||
}
|
||
|
||
if (remoteConfig != null) {
|
||
const remoteSavePath = _getRemoteSavePath(suffix)
|
||
fs.writeFileSync(remoteSavePath, body)
|
||
log.info('保存远程配置文件成功:', remoteSavePath)
|
||
} else {
|
||
log.warn('远程配置对象为空:', remoteConfigUrl)
|
||
}
|
||
|
||
resolve()
|
||
} else {
|
||
log.error(`下载远程配置失败: ${remoteConfigUrl}, response:`, response, ', body:', body)
|
||
|
||
let message
|
||
if (response) {
|
||
message = `下载远程配置失败: ${remoteConfigUrl}, message: ${response.message}, code: ${response.statusCode}`
|
||
} else {
|
||
message = `下载远程配置失败: response: ${response}`
|
||
}
|
||
reject(new Error(message))
|
||
}
|
||
})
|
||
})
|
||
},
|
||
deleteRemoteConfigFile (suffix = '') {
|
||
const remoteSavePath = _getRemoteSavePath(suffix)
|
||
if (fs.existsSync(remoteSavePath)) {
|
||
fs.unlinkSync(remoteSavePath)
|
||
log.info('删除远程配置文件成功:', remoteSavePath)
|
||
}
|
||
},
|
||
readRemoteConfig (suffix = '') {
|
||
return jsonApi.parse(configApi.readRemoteConfigStr(suffix))
|
||
},
|
||
readRemoteConfigStr (suffix = '') {
|
||
if (get().app.remoteConfig.enabled !== true) {
|
||
if (suffix === '_personal') {
|
||
if (!get().app.remoteConfig.personalUrl) {
|
||
return '{}'
|
||
}
|
||
} else if (suffix === '') {
|
||
if (!get().app.remoteConfig.url) {
|
||
return '{}'
|
||
}
|
||
}
|
||
return '{}'
|
||
}
|
||
try {
|
||
const path = _getRemoteSavePath(suffix)
|
||
if (fs.existsSync(path)) {
|
||
const file = fs.readFileSync(path)
|
||
log.info('读取远程配置文件内容成功:', path)
|
||
return file.toString()
|
||
} else {
|
||
log.warn('远程配置文件不存在:', path)
|
||
}
|
||
} catch (e) {
|
||
log.error('读取远程配置文件内容失败:', e)
|
||
}
|
||
|
||
return '{}'
|
||
},
|
||
/**
|
||
* 保存自定义的 config
|
||
* @param newConfig
|
||
*/
|
||
save (newConfig) {
|
||
// 对比默认config的异同
|
||
let defConfig = configApi.getDefault()
|
||
|
||
// 如果开启了远程配置,则读取远程配置,合并到默认配置中
|
||
if (get().app.remoteConfig.enabled === true) {
|
||
if (get().app.remoteConfig.url) {
|
||
defConfig = mergeApi.doMerge(defConfig, configApi.readRemoteConfig())
|
||
}
|
||
if (get().app.remoteConfig.personalUrl) {
|
||
defConfig = mergeApi.doMerge(defConfig, configApi.readRemoteConfig('_personal'))
|
||
}
|
||
}
|
||
|
||
// 计算新配置与默认配置(启用远程配置时,含远程配置)的差异,并保存到 config.json 中
|
||
const diffConfig = mergeApi.doDiff(defConfig, newConfig)
|
||
const configPath = _getConfigPath()
|
||
fs.writeFileSync(configPath, jsonApi.stringify(diffConfig))
|
||
log.info('保存 config.json 自定义配置文件成功:', configPath)
|
||
|
||
// 重载配置
|
||
const allConfig = configApi.set(diffConfig)
|
||
|
||
return {
|
||
diffConfig,
|
||
allConfig,
|
||
}
|
||
},
|
||
doMerge: mergeApi.doMerge,
|
||
doDiff: mergeApi.doDiff,
|
||
/**
|
||
* 读取 config.json 后,合并配置
|
||
* @returns {*}
|
||
*/
|
||
reload () {
|
||
const configPath = _getConfigPath()
|
||
let userConfig
|
||
if (!fs.existsSync(configPath)) {
|
||
userConfig = {}
|
||
log.info('config.json 文件不存在:', configPath)
|
||
} else {
|
||
const file = fs.readFileSync(configPath)
|
||
log.info('读取 config.json 成功:', configPath)
|
||
const fileStr = file.toString()
|
||
userConfig = fileStr && fileStr.length > 2 ? jsonApi.parse(fileStr) : {}
|
||
}
|
||
|
||
const config = configApi.set(userConfig)
|
||
return config || {}
|
||
},
|
||
update (partConfig) {
|
||
const newConfig = lodash.merge(configApi.get(), partConfig)
|
||
configApi.save(newConfig)
|
||
},
|
||
get,
|
||
set (newConfig) {
|
||
if (newConfig == null) {
|
||
log.warn('newConfig 为空,不做任何操作')
|
||
return configTarget
|
||
}
|
||
return configApi.load(newConfig)
|
||
},
|
||
load (newConfig) {
|
||
// 以用户配置作为基准配置,是为了保证用户配置的顺序在前
|
||
const merged = newConfig != null ? lodash.cloneDeep(newConfig) : {}
|
||
|
||
if (get().app.remoteConfig.enabled === true) {
|
||
let personalRemoteConfig = null
|
||
let shareRemoteConfig = null
|
||
|
||
if (get().app.remoteConfig.personalUrl) {
|
||
personalRemoteConfig = configApi.readRemoteConfig('_personal')
|
||
mergeApi.doMerge(merged, personalRemoteConfig) // 先合并一次个人远程配置,使配置顺序在前
|
||
}
|
||
if (get().app.remoteConfig.url) {
|
||
shareRemoteConfig = configApi.readRemoteConfig()
|
||
mergeApi.doMerge(merged, shareRemoteConfig) // 先合并一次共享远程配置,使配置顺序在前
|
||
}
|
||
mergeApi.doMerge(merged, defConfig) // 合并默认配置,顺序排在最后
|
||
if (get().app.remoteConfig.url) {
|
||
mergeApi.doMerge(merged, shareRemoteConfig) // 再合并一次共享远程配置,使配置生效
|
||
}
|
||
if (get().app.remoteConfig.personalUrl) {
|
||
mergeApi.doMerge(merged, personalRemoteConfig) // 再合并一次个人远程配置,使配置生效
|
||
}
|
||
} else {
|
||
mergeApi.doMerge(merged, defConfig) // 合并默认配置
|
||
}
|
||
if (newConfig != null) {
|
||
mergeApi.doMerge(merged, newConfig) // 再合并一次用户配置,使用户配置重新生效
|
||
}
|
||
mergeApi.deleteNullItems(merged) // 删除为null及[delete]的项
|
||
configTarget = merged
|
||
log.info('加载及合并远程配置完成')
|
||
|
||
return configTarget
|
||
},
|
||
getDefault () {
|
||
return lodash.cloneDeep(defConfig)
|
||
},
|
||
addDefault (key, defValue) {
|
||
lodash.set(defConfig, key, defValue)
|
||
},
|
||
// 移除用户配置,用于恢复出厂设置功能
|
||
async removeUserConfig () {
|
||
const configPath = _getConfigPath()
|
||
if (fs.existsSync(configPath)) {
|
||
// 读取 config.json 文件内容
|
||
const fileOriginalStr = fs.readFileSync(configPath).toString()
|
||
|
||
// 判断文件内容是否为空或空配置
|
||
const fileStr = fileOriginalStr.replace(/\s/g, '')
|
||
if (fileStr.length < 5) {
|
||
fs.writeFileSync(configPath, '{}')
|
||
return false // config.json 内容为空,或为空json
|
||
}
|
||
|
||
// 备份用户自定义配置文件
|
||
fs.writeFileSync(`${configPath}.${Date.now()}.bak.json`, fileOriginalStr)
|
||
// 原配置文件内容设为空
|
||
fs.writeFileSync(configPath, '{}')
|
||
|
||
// 重新加载配置
|
||
configApi.load(null)
|
||
|
||
return true // 删除并重新加载配置成功
|
||
} else {
|
||
return false // config.json 文件不存在
|
||
}
|
||
},
|
||
resetDefault (key) {
|
||
if (key) {
|
||
let value = lodash.get(defConfig, key)
|
||
value = lodash.cloneDeep(value)
|
||
lodash.set(configTarget, key, value)
|
||
} else {
|
||
configTarget = lodash.cloneDeep(defConfig)
|
||
}
|
||
return configTarget
|
||
},
|
||
async getVariables (type) {
|
||
const method = type === 'npm' ? Shell.getNpmEnv : Shell.getSystemEnv
|
||
const currentMap = await method()
|
||
const list = []
|
||
const map = configTarget.variables[type]
|
||
for (const key in map) {
|
||
const exists = currentMap[key] != null
|
||
list.push({
|
||
key,
|
||
value: map[key],
|
||
exists,
|
||
})
|
||
}
|
||
return list
|
||
},
|
||
async setVariables (type) {
|
||
const list = await configApi.getVariables(type)
|
||
const noSetList = list.filter((item) => {
|
||
return !item.exists
|
||
})
|
||
if (list.length > 0) {
|
||
const context = {
|
||
root_ca_cert_path: configApi.get().server.setting.rootCaFile.certPath,
|
||
}
|
||
for (const item of noSetList) {
|
||
if (item.value.includes('${')) {
|
||
for (const key in context) {
|
||
item.value = item.value.replcace(new RegExp(`\${${key}}`, 'g'), context[key])
|
||
}
|
||
}
|
||
}
|
||
const method = type === 'npm' ? Shell.setNpmEnv : Shell.setSystemEnv
|
||
return method({ list: noSetList })
|
||
}
|
||
},
|
||
}
|
||
|
||
module.exports = configApi
|