feat: 新增知资札记工具及相关功能
1. 新增知资札记完整站点:包含首页、详情页、管理后台 2. 添加站点图标、鉴权配置与后端API鉴权逻辑 3. 在市场页面新增知资札记入口跳转 4. 新增经典乡村音乐播放列表文件 5. 临时添加养生谚语批量导入脚本
This commit is contained in:
@@ -613,6 +613,170 @@ app.use('/api/fitness_diary', (req, res, next) => {
|
||||
|
||||
ensureFitnessDiaryLoaded()
|
||||
|
||||
// ── 知资札记 鉴权 ──
|
||||
const ZHIZI_NOTES_AUTH_COOKIE = 'zhizi_notes_gate'
|
||||
const readZhiziNotesAuthConfig = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'public', 'tools', 'zhizi_notes', 'auth_config.json')
|
||||
if (fs.existsSync(p)) {
|
||||
const cfg = JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
if (cfg && typeof cfg === 'object') return cfg
|
||||
}
|
||||
} catch {}
|
||||
return {}
|
||||
}
|
||||
const readZhiziNotesJwk = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'config', 'zhizi_notes.jwk.json')
|
||||
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
} catch {}
|
||||
return { kty: 'RSA', n: '', e: '' }
|
||||
}
|
||||
const getZhiziNotesCookieName = () => {
|
||||
try {
|
||||
const cfg = readZhiziNotesAuthConfig()
|
||||
const n = String(cfg.cookieName || '')
|
||||
if (n) return n
|
||||
} catch {}
|
||||
return ZHIZI_NOTES_AUTH_COOKIE
|
||||
}
|
||||
const getZhiziNotesMaxAge = () => {
|
||||
try {
|
||||
const cfg = readZhiziNotesAuthConfig()
|
||||
const days = Number(cfg.max_age_days || 0)
|
||||
if (Number.isFinite(days) && days > 0) return Math.min(days, 365) * 24 * 3600 * 1000
|
||||
} catch {}
|
||||
return 30 * 24 * 3600 * 1000
|
||||
}
|
||||
const getZhiziNotesGuidConfig = () => {
|
||||
try {
|
||||
const cfg = readZhiziNotesAuthConfig()
|
||||
const keyRaw = String(cfg.guidParam || cfg.guid_param || 'guid').trim()
|
||||
const valueRaw = String(cfg.guidValue || cfg.guid_value || '').trim()
|
||||
return { key: keyRaw || 'guid', value: valueRaw }
|
||||
} catch {
|
||||
return { key: 'guid', value: '' }
|
||||
}
|
||||
}
|
||||
const hasValidZhiziNotesGuid = (req) => {
|
||||
try {
|
||||
const cfg = readZhiziNotesAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
const { key, value } = getZhiziNotesGuidConfig()
|
||||
if (!value) return false
|
||||
const q = req && req.query ? req.query : {}
|
||||
const raw = q[key]
|
||||
if (raw === undefined || raw === null) return false
|
||||
const incoming = Array.isArray(raw) ? String(raw[0] || '') : String(raw)
|
||||
return incoming === value
|
||||
} catch { return false }
|
||||
}
|
||||
const isZhiziNotesForceGuid = () => {
|
||||
try {
|
||||
const cfg = readZhiziNotesAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, 'requireGuid')) return !!cfg.requireGuid
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, 'require_guid')) return !!cfg.require_guid
|
||||
return false
|
||||
} catch { return false }
|
||||
}
|
||||
const hasZhiziNotesAuth = (req) => {
|
||||
try {
|
||||
const config = readZhiziNotesAuthConfig()
|
||||
if (config.enable_auth === false) return true
|
||||
const cookies = parseCookie(req.headers.cookie || '')
|
||||
if (cookies[getZhiziNotesCookieName()] === '1') return true
|
||||
return false
|
||||
} catch { return false }
|
||||
}
|
||||
|
||||
// 知资札记 入口守卫
|
||||
app.get('/tools/zhizi_notes', (req, res, next) => {
|
||||
setNoCache(res)
|
||||
try {
|
||||
const forceGuid = isZhiziNotesForceGuid()
|
||||
if (hasZhiziNotesAuth(req)) return next()
|
||||
const guidKey = getZhiziNotesGuidConfig().key
|
||||
const hasGuidInput = req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)
|
||||
if (hasValidZhiziNotesGuid(req)) {
|
||||
const maxAge = getZhiziNotesMaxAge()
|
||||
const cookieName = getZhiziNotesCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/zhizi_notes/index.html')
|
||||
}
|
||||
if (hasGuidInput) return res.status(401).send('未授权')
|
||||
const token = String(req.query.token || '')
|
||||
if (!token) {
|
||||
if (forceGuid) return res.status(401).send('未授权')
|
||||
return next()
|
||||
}
|
||||
const payload = verifyJwtWithKeys(token, jwkKeys(readZhiziNotesJwk()))
|
||||
if (!payload) return res.status(401).send('未授权')
|
||||
{
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-zhizi_notes')) return res.status(401).send('未授权')
|
||||
}
|
||||
const maxAge = getZhiziNotesMaxAge()
|
||||
const cookieName = getZhiziNotesCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/zhizi_notes/index.html')
|
||||
} catch (e) { return res.status(401).send('未授权') }
|
||||
})
|
||||
|
||||
// 知资札记 子路径守卫
|
||||
app.use('/tools/zhizi_notes', (req, res, next) => {
|
||||
const forceGuid = isZhiziNotesForceGuid()
|
||||
if (hasZhiziNotesAuth(req)) return next()
|
||||
const isHandshake = req.method === 'GET' && (req.path === '/' || req.path === '' || req.path === '/index.html')
|
||||
const guidKey = getZhiziNotesGuidConfig().key
|
||||
const hasGuidInput = isHandshake && req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)
|
||||
if (isHandshake && hasValidZhiziNotesGuid(req)) {
|
||||
const maxAge = getZhiziNotesMaxAge()
|
||||
const cookieName = getZhiziNotesCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/zhizi_notes/index.html')
|
||||
}
|
||||
if (hasGuidInput) return res.status(401).send('未授权')
|
||||
if (isHandshake && String(req.query.token || '')) {
|
||||
const payload = verifyJwtWithKeys(String(req.query.token), jwkKeys(readZhiziNotesJwk()))
|
||||
if (payload) {
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-zhizi_notes')) return res.status(401).send('未授权')
|
||||
const maxAge = getZhiziNotesMaxAge()
|
||||
const cookieName = getZhiziNotesCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/zhizi_notes/index.html')
|
||||
}
|
||||
}
|
||||
if (isHandshake && forceGuid) return res.status(401).send('未授权')
|
||||
return res.status(401).send('未授权')
|
||||
})
|
||||
|
||||
// 知资札记 API 鉴权
|
||||
app.use('/api/zhizi_notes', (req, res, next) => {
|
||||
if (hasZhiziNotesAuth(req)) return next()
|
||||
return res.status(401).json({ ok: false, error: 'unauthorized' })
|
||||
})
|
||||
|
||||
const ensureZhiziNotesLoaded = () => {
|
||||
let mod = null
|
||||
try { mod = require('./zhizi_notes') } catch (e) { try { logJSON('zhizi_notes.require.error', { error: String(e.message || e) }, 'zhizi_notes') } catch {}; setTimeout(ensureZhiziNotesLoaded, 2000); return }
|
||||
try {
|
||||
if (mod && mod.initDb) {
|
||||
mod.initDb(path.join(__dirname, '..', '..', 'data', 'zhizi_notes.db'))
|
||||
}
|
||||
if (mod && mod.bindRoutes) {
|
||||
mod.bindRoutes(app)
|
||||
try { logJSON('zhizi_notes.bind_routes.done', { ok: true }, 'zhizi_notes') } catch {}
|
||||
}
|
||||
} catch (e) {
|
||||
try { logJSON('zhizi_notes.bind_routes.error', { error: String(e.message || e) }, 'zhizi_notes') } catch {}
|
||||
}
|
||||
}
|
||||
ensureZhiziNotesLoaded()
|
||||
|
||||
const PLANT_HOME_AUTH_COOKIE = 'plant_home_gate'
|
||||
const readPlantHomeAuthConfig = () => {
|
||||
try {
|
||||
@@ -1406,6 +1570,7 @@ const mapSystemIdToUrl = (systemId) => {
|
||||
'Tools-calendar_voice': '/tools/calendar_voice',
|
||||
'TRAE-PSC': '/tools/psc',
|
||||
'FitnessDiary': '/tools/fitness_diary',
|
||||
'Tools-zhizi_notes': '/tools/zhizi_notes',
|
||||
'Tools-doc_cloud_keeper': '/tools/doc_cloud_keeper',
|
||||
'Tools-yuanzhupai': '/tools/yuanzhupai',
|
||||
'Tools-oss_file_cabinet': '/tools/oss_file_cabinet',
|
||||
|
||||
Reference in New Issue
Block a user