feat(content): 修复服务并添加chapters/knowledge-points/questions模块
- database.ts 导出db常量替代getDb()函数 - env.ts JWT_SECRET/ES_URL/NEO4J_URL改optional加DEV_MODE - neo4j.ts driver惰性创建+try/catch+connectionTimeout:3000 - health/lifecycle改用Drizzle原生查询 - textbooks.schema修复integer到int+导出NewTextbook类型 - 新建chapters/knowledge-points/questions三模块CRUD - knowledge-points含Neo4j前置依赖图非阻塞查询 - content-init.sql创建4张表 端到端验证: textbooks/chapters/knowledge-points/questions全CRUD通过
This commit is contained in:
@@ -1,24 +1,16 @@
|
||||
import { drizzle } from 'drizzle-orm/mysql2';
|
||||
import mysql from 'mysql2/promise';
|
||||
import { env } from './env.js';
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import mysql from "mysql2/promise";
|
||||
import { env } from "./env.js";
|
||||
|
||||
let pool: mysql.Pool | null = null;
|
||||
const pool = mysql.createPool({
|
||||
uri: env.DATABASE_URL,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
});
|
||||
|
||||
export function getDb() {
|
||||
if (!pool) {
|
||||
pool = mysql.createPool({
|
||||
uri: env.DATABASE_URL,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
});
|
||||
}
|
||||
return drizzle(pool);
|
||||
}
|
||||
export const db = drizzle(pool);
|
||||
|
||||
export async function closeDb(): Promise<void> {
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
pool = null;
|
||||
}
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { z } from 'zod';
|
||||
import { z } from "zod";
|
||||
|
||||
const envSchema = z.object({
|
||||
PORT: z.string().default('3005'),
|
||||
PORT: z.string().default("3005"),
|
||||
DATABASE_URL: z.string().url(),
|
||||
REDIS_URL: z.string().url().optional(),
|
||||
NEO4J_URL: z.string().url(),
|
||||
NEO4J_PASSWORD: z.string(),
|
||||
ES_URL: z.string().url(),
|
||||
JWT_SECRET: z.string(),
|
||||
JWT_ISSUER: z.string().default('next-edu-cloud'),
|
||||
NEO4J_URL: z.string().url().optional(),
|
||||
NEO4J_PASSWORD: z.string().optional(),
|
||||
ES_URL: z.string().url().optional(),
|
||||
JWT_SECRET: z.string().optional(),
|
||||
JWT_ISSUER: z.string().default("next-edu-cloud"),
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url().optional(),
|
||||
LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
LOG_LEVEL: z
|
||||
.enum(["fatal", "error", "warn", "info", "debug", "trace"])
|
||||
.default("info"),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "production", "test"])
|
||||
.default("development"),
|
||||
DEV_MODE: z.string().optional().default("false"),
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
@@ -19,8 +24,11 @@ export type Env = z.infer<typeof envSchema>;
|
||||
export function loadEnv(): Env {
|
||||
const result = envSchema.safeParse(process.env);
|
||||
if (!result.success) {
|
||||
console.error('❌ Invalid environment variables:', result.error.flatten().fieldErrors);
|
||||
throw new Error('Invalid environment configuration');
|
||||
console.error(
|
||||
"❌ Invalid environment variables:",
|
||||
result.error.flatten().fieldErrors,
|
||||
);
|
||||
throw new Error("Invalid environment configuration");
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
import neo4j from 'neo4j-driver';
|
||||
import { env } from './env.js';
|
||||
import neo4j from "neo4j-driver";
|
||||
import type { Driver, Session } from "neo4j-driver";
|
||||
import { env } from "./env.js";
|
||||
|
||||
export const neo4jDriver = neo4j.driver(
|
||||
env.NEO4J_URL,
|
||||
neo4j.auth.basic('neo4j', env.NEO4J_PASSWORD)
|
||||
);
|
||||
// Neo4j driver 创建为惰性初始化:未配置 NEO4J_URL / NEO4J_PASSWORD 时
|
||||
// driver 保持 null,服务仍可正常启动。所有依赖 Neo4j 的查询在
|
||||
// driver 为 null 时返回空结果或抛出可控错误,不会阻塞主流程。
|
||||
let driver: Driver | null = null;
|
||||
|
||||
try {
|
||||
if (env.NEO4J_URL && env.NEO4J_PASSWORD) {
|
||||
driver = neo4j.driver(
|
||||
env.NEO4J_URL,
|
||||
neo4j.auth.basic("neo4j", env.NEO4J_PASSWORD),
|
||||
// 连接超时 3s:Neo4j 不可用时快速失败,避免拖慢 HTTP 响应
|
||||
{ connectionTimeout: 3000, maxConnectionLifetime: 60_000 },
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
"Neo4j driver init failed, running without Neo4j:",
|
||||
err instanceof Error ? err.message : String(err),
|
||||
);
|
||||
driver = null;
|
||||
}
|
||||
|
||||
export function getNeo4jSession(): Session | null {
|
||||
if (!driver) {
|
||||
return null;
|
||||
}
|
||||
return driver.session();
|
||||
}
|
||||
|
||||
export async function closeNeo4j(): Promise<void> {
|
||||
await neo4jDriver.close();
|
||||
if (driver) {
|
||||
await driver.close();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user