301 lines
8.9 KiB
JavaScript
301 lines
8.9 KiB
JavaScript
const fs = require('fs')
|
||
const Shell = require('./shell')
|
||
const lodash = require('lodash')
|
||
const defConfig = require('./config/index.js')
|
||
const jsonApi = require('@docmirror/mitmproxy/src/json')
|
||
const request = require('request')
|
||
const path = require('path')
|
||
const log = require('./utils/util.log')
|
||
const mergeApi = require('./merge.js')
|
||
|
||
let configTarget = lodash.cloneDeep(defConfig)
|
||
|
||
function get () {
|
||
return configTarget
|
||
}
|
||
|
||
const getDefaultConfigBasePath = function () {
|
||
return get().server.setting.userBasePath
|
||
}
|
||
|
||
function _getRemoteSavePath (prefix = '') {
|
||
const dir = getDefaultConfigBasePath()
|
||
if (!fs.existsSync(dir)) {
|
||
fs.mkdirSync(dir)
|
||
}
|
||
return path.join(dir, prefix + 'remote_config.json5')
|
||
}
|
||
|
||
function _getConfigPath () {
|
||
const dir = getDefaultConfigBasePath()
|
||
if (!fs.existsSync(dir)) {
|
||
fs.mkdirSync(dir)
|
||
}
|
||
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天
|
||
},
|
||
downloadRemoteConfig () {
|
||
if (get().app.remoteConfig.enabled !== true) {
|
||
return
|
||
}
|
||
const remoteConfigUrl = get().app.remoteConfig.url
|
||
// eslint-disable-next-line handle-callback-err
|
||
return new Promise((resolve, reject) => {
|
||
log.info('开始下载远程配置:', remoteConfigUrl)
|
||
request(remoteConfigUrl, (error, response, body) => {
|
||
if (error) {
|
||
log.error('下载远程配置失败, 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()
|
||
fs.writeFileSync(remoteSavePath, body)
|
||
log.info('保存远程配置文件成功:', remoteSavePath)
|
||
} else {
|
||
log.warn('远程配置对象为空:', remoteConfigUrl)
|
||
}
|
||
|
||
resolve()
|
||
} else {
|
||
log.error('下载远程配置失败, response:', response, ', body:', body)
|
||
|
||
let message
|
||
if (response) {
|
||
message = '下载远程配置失败: ' + response.message + ', code: ' + response.statusCode
|
||
} else {
|
||
message = '下载远程配置失败: response: ' + response
|
||
}
|
||
reject(new Error(message))
|
||
}
|
||
})
|
||
})
|
||
},
|
||
readRemoteConfig () {
|
||
if (get().app.remoteConfig.enabled !== true) {
|
||
return {}
|
||
}
|
||
const path = _getRemoteSavePath()
|
||
try {
|
||
if (fs.existsSync(path)) {
|
||
const file = fs.readFileSync(path)
|
||
log.info('读取远程配置文件成功:', path)
|
||
return jsonApi.parse(file.toString())
|
||
} else {
|
||
log.warn('远程配置文件不存在:', path)
|
||
}
|
||
} catch (e) {
|
||
log.error('读取远程配置文件失败:', path, ', error:', e)
|
||
}
|
||
|
||
return {}
|
||
},
|
||
readRemoteConfigStr () {
|
||
if (get().app.remoteConfig.enabled !== true) {
|
||
return '{}'
|
||
}
|
||
try {
|
||
const path = _getRemoteSavePath()
|
||
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) {
|
||
defConfig = mergeApi.doMerge(defConfig, configApi.readRemoteConfig())
|
||
}
|
||
|
||
// 计算新配置与默认配置(启用远程配置时,含远程配置)的差异,并保存到 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) : {}
|
||
|
||
mergeApi.doMerge(merged, defConfig) // 合并默认配置
|
||
mergeApi.doMerge(merged, configApi.readRemoteConfig()) // 合并远程配置
|
||
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 fileStr = fs.readFileSync(configPath).toString().replace(/\s/g, '')
|
||
|
||
// 判断文件内容是否为空或空配置
|
||
if (fileStr === '' || fileStr === '{}') {
|
||
fs.rmSync(configPath)
|
||
return false // config.json 内容为空,或为空json
|
||
}
|
||
|
||
// 备份用户自定义配置文件
|
||
fs.renameSync(configPath, configPath + '.bak' + new Date().getTime() + '.json')
|
||
|
||
// 重新加载配置
|
||
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.indexOf('${') >= 0) {
|
||
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
|