feat(mp, web, calendar): 新增微信收藏夹与日历批量生成功能
- 新增微信小程序版个人收藏夹全套功能,包含聚合首页、收藏列表、编辑、详情页面与配套后端API - 新增web版时光助手门户与个人收藏夹工具,支持与小程序共享后端数据 - 优化日历提醒工具:替换本地bootstrap资源为公共CDN版本,新增批量生成循环提醒的前端界面与后端逻辑,新增数据库batch_id字段,更新鉴权配置文件 - 补充项目配置、站点地图、调试诊断脚本等配套文件 - 完善各模块的页面配置与样式文件
This commit is contained in:
+160
-1
@@ -304,6 +304,20 @@ const ensureAppOrderBoxLoaded = () => {
|
||||
}
|
||||
ensureAppOrderBoxLoaded()
|
||||
|
||||
const ensureMpTimeAssistantLoaded = () => {
|
||||
let mod = null
|
||||
try { mod = require('./mp_time_assistant') } catch (e) { try { logJSON('mp_time_assistant.require.error', { error: String(e.message || e) }, 'mp_time_assistant') } catch {}; setTimeout(ensureMpTimeAssistantLoaded, 2000); return }
|
||||
try {
|
||||
if (mod && mod.bindRoutes) {
|
||||
mod.bindRoutes(app)
|
||||
try { logJSON('mp_time_assistant.bind_routes.done', { ok: true }, 'mp_time_assistant') } catch {}
|
||||
}
|
||||
} catch (e) {
|
||||
try { logJSON('mp_time_assistant.bind_routes.error', { error: String(e.message || e) }, 'mp_time_assistant') } catch {}
|
||||
}
|
||||
}
|
||||
ensureMpTimeAssistantLoaded()
|
||||
|
||||
const CALENDAR_VOICE_AUTH_COOKIE = 'calendar_voice_gate'
|
||||
const readCalendarVoiceAuthConfig = () => {
|
||||
try {
|
||||
@@ -819,6 +833,150 @@ const ensureZhiziNotesLoaded = () => {
|
||||
}
|
||||
ensureZhiziNotesLoaded()
|
||||
|
||||
const WEB_MP_TIME_ASSISTANT_AUTH_COOKIE = 'web_mp_time_assistant_gate'
|
||||
const readWebMpTimeAssistantAuthConfig = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'public', 'tools', 'web_mp_time_assistant', '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 readWebMpTimeAssistantJwk = () => {
|
||||
try {
|
||||
const p = path.join(process.cwd(), 'config', 'web_mp_time_assistant.jwk.json')
|
||||
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'))
|
||||
} catch {}
|
||||
return { kty: 'RSA', n: '', e: '' }
|
||||
}
|
||||
const getWebMpTimeAssistantCookieName = () => {
|
||||
try {
|
||||
const cfg = readWebMpTimeAssistantAuthConfig()
|
||||
const n = String(cfg.cookieName || '')
|
||||
if (n) return n
|
||||
} catch {}
|
||||
return WEB_MP_TIME_ASSISTANT_AUTH_COOKIE
|
||||
}
|
||||
const getWebMpTimeAssistantMaxAge = () => {
|
||||
try {
|
||||
const cfg = readWebMpTimeAssistantAuthConfig()
|
||||
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 getWebMpTimeAssistantGuidConfig = () => {
|
||||
try {
|
||||
const cfg = readWebMpTimeAssistantAuthConfig()
|
||||
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 hasValidWebMpTimeAssistantGuid = (req) => {
|
||||
try {
|
||||
const cfg = readWebMpTimeAssistantAuthConfig()
|
||||
if (cfg.enable_auth === false) return false
|
||||
const { key, value } = getWebMpTimeAssistantGuidConfig()
|
||||
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 isWebMpTimeAssistantForceGuid = () => {
|
||||
try {
|
||||
const cfg = readWebMpTimeAssistantAuthConfig()
|
||||
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 hasWebMpTimeAssistantAuth = (req) => {
|
||||
try {
|
||||
const config = readWebMpTimeAssistantAuthConfig()
|
||||
if (config.enable_auth === false) return true
|
||||
const cookies = parseCookie(req.headers.cookie || '')
|
||||
if (cookies[getWebMpTimeAssistantCookieName()] === '1') return true
|
||||
return false
|
||||
} catch { return false }
|
||||
}
|
||||
app.get('/tools/web_mp_time_assistant', (req, res, next) => {
|
||||
setNoCache(res)
|
||||
try {
|
||||
const forceGuid = isWebMpTimeAssistantForceGuid()
|
||||
if (hasWebMpTimeAssistantAuth(req)) return next()
|
||||
const guidKey = getWebMpTimeAssistantGuidConfig().key
|
||||
const hasGuidInput = req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)
|
||||
if (hasValidWebMpTimeAssistantGuid(req)) {
|
||||
const maxAge = getWebMpTimeAssistantMaxAge()
|
||||
const cookieName = getWebMpTimeAssistantCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/web_mp_time_assistant/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(readWebMpTimeAssistantJwk()))
|
||||
if (!payload) return res.status(401).send('未授权')
|
||||
{
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-web_mp_time_assistant')) return res.status(401).send('未授权')
|
||||
}
|
||||
const maxAge = getWebMpTimeAssistantMaxAge()
|
||||
const cookieName = getWebMpTimeAssistantCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/web_mp_time_assistant/index.html')
|
||||
} catch (e) { return res.status(401).send('未授权') }
|
||||
})
|
||||
app.use('/tools/web_mp_time_assistant', (req, res, next) => {
|
||||
const forceGuid = isWebMpTimeAssistantForceGuid()
|
||||
if (hasWebMpTimeAssistantAuth(req)) return next()
|
||||
const isHandshake = req.method === 'GET' && (req.path === '/' || req.path === '' || req.path === '/index.html' || req.path === '/hub.html')
|
||||
const guidKey = getWebMpTimeAssistantGuidConfig().key
|
||||
const hasGuidInput = isHandshake && req && req.query && Object.prototype.hasOwnProperty.call(req.query, guidKey)
|
||||
if (isHandshake && hasValidWebMpTimeAssistantGuid(req)) {
|
||||
const maxAge = getWebMpTimeAssistantMaxAge()
|
||||
const cookieName = getWebMpTimeAssistantCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/web_mp_time_assistant/index.html')
|
||||
}
|
||||
if (hasGuidInput) return res.status(401).send('未授权')
|
||||
if (isHandshake && String(req.query.token || '')) {
|
||||
const payload = verifyJwtWithKeys(String(req.query.token), jwkKeys(readWebMpTimeAssistantJwk()))
|
||||
if (payload) {
|
||||
const iss = String(payload.iss || '')
|
||||
const navIss = getNavIssFromFlags()
|
||||
if (iss === navIss && !audMatch(payload, 'Tools-web_mp_time_assistant')) return res.status(401).send('未授权')
|
||||
const maxAge = getWebMpTimeAssistantMaxAge()
|
||||
const cookieName = getWebMpTimeAssistantCookieName()
|
||||
res.cookie(cookieName, '1', { httpOnly: true, sameSite: 'lax', maxAge, path: '/' })
|
||||
return res.redirect('/tools/web_mp_time_assistant/index.html')
|
||||
}
|
||||
}
|
||||
if (isHandshake && forceGuid) return res.status(401).send('未授权')
|
||||
return res.status(401).send('未授权')
|
||||
})
|
||||
app.use('/api/mp_time_assistant', (req, res, next) => {
|
||||
if (hasWebMpTimeAssistantAuth(req)) return next()
|
||||
return res.status(401).json({ ok: false, error: 'unauthorized' })
|
||||
})
|
||||
app.use('/uploads/mp_time_assistant', (req, res, next) => {
|
||||
if (hasWebMpTimeAssistantAuth(req)) return next()
|
||||
return res.status(401).send('未授权')
|
||||
})
|
||||
|
||||
const PLANT_HOME_AUTH_COOKIE = 'plant_home_gate'
|
||||
const readPlantHomeAuthConfig = () => {
|
||||
try {
|
||||
@@ -1643,7 +1801,8 @@ const mapSystemIdToUrl = (systemId) => {
|
||||
'Tools-short_link': '/tools/short_link',
|
||||
'Tools-web_mobile_calendar_reminder': '/tools/web_mobile_calendar_reminder',
|
||||
'Tools-greedy_snake': '/tools/greedy_snake',
|
||||
'Tools-oss_browser_link': '/tools/oss_browser_link'
|
||||
'Tools-oss_browser_link': '/tools/oss_browser_link',
|
||||
'Tools-web_mp_time_assistant': '/tools/web_mp_time_assistant'
|
||||
}
|
||||
if (m[systemId]) return m[systemId]
|
||||
if (systemId.startsWith('Tools-')) {
|
||||
|
||||
Reference in New Issue
Block a user