feat(mysql): 接入只读电池数据源

This commit is contained in:
2026-05-11 20:51:24 +08:00
parent 8b6339f34b
commit ebe0970df1
3 changed files with 79 additions and 4 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ import { z } from 'zod'
export const env = createEnv({ export const env = createEnv({
server: { server: {
DATABASE_URL: z.url({ protocol: /^postgres(ql)?$/ }), DATABASE_URL: z.url({ protocol: /^mysql$/ }),
LOG_DB: z.stringbool().default(false), LOG_DB: z.stringbool().default(false),
LOG_FORMAT: z.enum(['pretty', 'json']).optional(), LOG_FORMAT: z.enum(['pretty', 'json']).optional(),
LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warning', 'error', 'fatal']).default('info'), LOG_LEVEL: z.enum(['trace', 'debug', 'info', 'warning', 'error', 'fatal']).default('info'),
+75
View File
@@ -0,0 +1,75 @@
import mysql, { type Pool, type RowDataPacket } from 'mysql2/promise'
import { type BatteryInfo, type BatteryInfoSourceRow, toBatteryInfo } from '@/domain/battery'
import { env } from '@/env'
const historyLimit = 500
type BatteryInfoMysqlRow = RowDataPacket & BatteryInfoSourceRow
let pool: Pool | undefined
function getBatteryPool() {
pool ??= mysql.createPool({
uri: env.DATABASE_URL,
waitForConnections: true,
connectionLimit: 5,
namedPlaceholders: true,
})
return pool
}
export async function closeBatteryPool() {
if (!pool) return
await pool.end()
pool = undefined
}
const sourceColumns = `
id,
user_id AS userId,
mac,
dev_model AS devModel,
dev_name AS devName,
is_low_power AS isLowPower,
power_status AS powerStatus,
power,
create_time AS createTime,
remark
`
export async function getBatteryHistory(mac: string): Promise<BatteryInfo[]> {
const [rows] = await getBatteryPool().query<BatteryInfoMysqlRow[]>(
`
SELECT ${sourceColumns}
FROM ls_battery_info
WHERE mac = :mac
ORDER BY create_time DESC, id DESC
LIMIT :limit
`,
{ mac, limit: historyLimit },
)
return rows.map(toBatteryInfo)
}
export async function getLatestBatteryPerDevice(): Promise<BatteryInfo[]> {
const [rows] = await getBatteryPool().query<BatteryInfoMysqlRow[]>(`
SELECT ${sourceColumns}
FROM ls_battery_info AS current_record
WHERE NOT EXISTS (
SELECT 1
FROM ls_battery_info AS newer_record
WHERE newer_record.mac = current_record.mac
AND (
newer_record.create_time > current_record.create_time
OR (newer_record.create_time = current_record.create_time AND newer_record.id > current_record.id)
)
)
ORDER BY current_record.create_time DESC, current_record.id DESC
`)
return rows.map(toBatteryInfo)
}
+3 -3
View File
@@ -1,4 +1,4 @@
import { db } from '@/server/db' import { closeBatteryPool } from '@/server/battery/mysql'
import { getLogger } from '@/server/logger' import { getLogger } from '@/server/logger'
export default () => { export default () => {
@@ -17,8 +17,8 @@ export default () => {
await Bun.sleep(500) await Bun.sleep(500)
try { try {
await db.$client.end() await closeBatteryPool()
logger.info('DB pool closed, exiting') logger.info('Battery MySQL pool closed, exiting')
} catch (error) { } catch (error) {
logger.error('DB pool close failed during shutdown', { error }) logger.error('DB pool close failed during shutdown', { error })
} }