Files
pixelheros/put/server/db.js
panFD 9c9cc99e64 feat: 重构服务支持双模式并接入 MongoDB 数据库
- 新增 MongoDB 存储层实现英雄、预设和版本数据的持久化
- 新增 push-meta 和 migrate-to-mongo 脚本支持数据同步迁移
- 支持开发版和线上设计版双模式运行
- 新增版本闸门机制,线上设计版落后时进入只读模式
- 优化前端 API 配置适配不同部署环境
- 新增线上设计版静态资源托管和路由回退
2026-10-03 12:02:47 +08:00

76 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 英雄/预设存储层:开发版与线上设计版统一读写线上 MongoDB。
* - heroes 集合:每个英雄一条文档,uuid 唯一索引
* - templates 集合:预设库(_id='presets' 单文档)
* - meta 集合:版本闸门(_id='version' 单文档,schemaVersion 由开发版推送 meta 时写入)
* 技能/Buff 底座不进数据库,开发版走 TS 编译,线上版走 meta.json 快照。
*/
import { MongoClient } from 'mongodb'
const MONGO_URL = process.env.MONGO_URL || ''
const DB_NAME = process.env.MONGO_DB || 'heroconfig'
let client = null
let db = null
/** 惰性连接 MongoDB(单例复用) */
async function mongo() {
if (db) return db
if (!MONGO_URL) throw new Error('未配置 MONGO_URL 环境变量')
client = new MongoClient(MONGO_URL)
await client.connect()
db = client.db(DB_NAME)
// uuid 唯一索引:保证英雄按 uuid 幂等覆盖
await db.collection('heroes').createIndex({ uuid: 1 }, { unique: true })
return db
}
/** 读取全部英雄(按 uuid 升序,剥离 Mongo 内部 _id) */
export async function getHeroes() {
const docs = await (await mongo()).collection('heroes').find().sort({ uuid: 1 }).toArray()
return docs.map(({ _id, ...h }) => h)
}
/** 全量覆盖:upsert 传入英雄,并删除库中多余的 uuid */
export async function saveHeroes(heroes) {
const col = (await mongo()).collection('heroes')
for (const h of heroes) {
await col.replaceOne({ uuid: h.uuid }, h, { upsert: true })
}
await col.deleteMany({ uuid: { $nin: heroes.map((h) => h.uuid) } })
}
/** 读取预设库(无文档时返回空池) */
export async function getTemplates() {
const doc = await (await mongo()).collection('templates').findOne({ _id: 'presets' })
return {
triggerPresets: Array.isArray(doc?.triggerPresets) ? doc.triggerPresets : [],
fieldPresets: Array.isArray(doc?.fieldPresets) ? doc.fieldPresets : [],
}
}
/** 全量覆盖预设库 */
export async function saveTemplates(triggerPresets, fieldPresets) {
await (await mongo())
.collection('templates')
.replaceOne({ _id: 'presets' }, { _id: 'presets', triggerPresets, fieldPresets }, { upsert: true })
}
/** 读取库中数据结构版本(首次为 0) */
export async function getSchemaVersion() {
const doc = await (await mongo()).collection('meta').findOne({ _id: 'version' })
return doc?.schemaVersion ?? 0
}
/**
* 推送数据结构版本:只允许升不允许降,避免旧部署误把版本刷低导致线上版解锁写入。
* @returns {Promise<number>} 写入后的实际版本
*/
export async function bumpSchemaVersion(version) {
const col = (await mongo()).collection('meta')
const cur = await getSchemaVersion()
const next = Math.max(cur, version)
await col.replaceOne({ _id: 'version' }, { _id: 'version', schemaVersion: next, updatedAt: new Date() }, { upsert: true })
return next
}