feat: 完成分类、worksheet 接口开发,资源迁移到七牛
This commit is contained in:
@@ -10,7 +10,7 @@ export const SERVER_CONFIG = Symbol('SERVER_CONFIG');
|
||||
export const API_PREFIX = 'api/v1';
|
||||
export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
||||
|
||||
/** 规范化的启动配置;仅在服务端使用,包含不得下发或记录的 JWT 密钥。 */
|
||||
/** 规范化的启动配置;仅在服务端使用,JWT 和微信密钥不得下发或记录。 */
|
||||
export interface ServerConfig extends RuntimeOptions {
|
||||
nodeEnv: 'development' | 'test' | 'production';
|
||||
logLevel: LogLevel;
|
||||
@@ -22,6 +22,13 @@ export interface ServerConfig extends RuntimeOptions {
|
||||
jwtIssuer: string;
|
||||
jwtAudience: string;
|
||||
jwtExpiresInSeconds: number;
|
||||
/** 微信凭据仅服务端持有,登录接入后两项必须完整。 */
|
||||
wechatMiniAppId: string;
|
||||
wechatMiniAppSecret: string;
|
||||
databaseUrl: string;
|
||||
/** 新品依据首次发布时间判断;公共资源域名缺失时不返回七牛对象地址。 */
|
||||
worksheetNewWindowDays: number;
|
||||
qiniuPublicDomain: string | null;
|
||||
}
|
||||
|
||||
/** 按十进制整数字符串解析并检查范围,防止空串、科学计数法等被 Number 宽松接受。 */
|
||||
@@ -78,6 +85,41 @@ export function getServerConfig(
|
||||
if (!jwtIssuer || !jwtAudience) {
|
||||
throw new Error('JWT_ISSUER and JWT_AUDIENCE must not be empty');
|
||||
}
|
||||
const wechatMiniAppId = (env.WECHAT_MINI_APP_ID ?? '').trim();
|
||||
const wechatMiniAppSecret = (env.WECHAT_MINI_APP_SECRET ?? '').trim();
|
||||
// 已接入真实登录;缺失凭据必须启动失败,错误不回显配置值。
|
||||
if (!wechatMiniAppId || !wechatMiniAppSecret) {
|
||||
throw new Error(
|
||||
'WECHAT_MINI_APP_ID and WECHAT_MINI_APP_SECRET are required',
|
||||
);
|
||||
}
|
||||
if (/\s/.test(wechatMiniAppId) || /\s/.test(wechatMiniAppSecret)) {
|
||||
throw new Error(
|
||||
'WECHAT_MINI_APP_ID and WECHAT_MINI_APP_SECRET must not contain whitespace',
|
||||
);
|
||||
}
|
||||
const databaseUrl = env.DATABASE_URL ?? '';
|
||||
try {
|
||||
const url = new URL(databaseUrl);
|
||||
if (
|
||||
!['postgresql:', 'postgres:'].includes(url.protocol) ||
|
||||
!url.hostname ||
|
||||
url.pathname.length < 2 ||
|
||||
url.hash ||
|
||||
/\s/.test(databaseUrl)
|
||||
)
|
||||
throw new Error();
|
||||
// 本阶段连接 public 中的既有 SQL 表,拒绝悄悄连接到其他 Prisma schema。
|
||||
if (
|
||||
url.searchParams.has('schema') &&
|
||||
url.searchParams.get('schema') !== 'public'
|
||||
)
|
||||
throw new Error();
|
||||
} catch {
|
||||
throw new Error(
|
||||
'DATABASE_URL must be a PostgreSQL connection URL using the public schema',
|
||||
);
|
||||
}
|
||||
// 只接受可直接比较的完整 origin;路径、凭证、通配符不属于白名单项。
|
||||
const corsOrigins = (env.CORS_ORIGINS ?? '')
|
||||
.split(',')
|
||||
@@ -113,6 +155,27 @@ export function getServerConfig(
|
||||
'TRUSTED_PROXY_CIDRS must contain explicit proxy addresses or CIDRs',
|
||||
);
|
||||
}
|
||||
const rawPublicDomain = (env.QINIU_PUBLIC_DOMAIN ?? '').trim();
|
||||
let qiniuPublicDomain: string | null = null;
|
||||
if (rawPublicDomain) {
|
||||
try {
|
||||
const url = new URL(rawPublicDomain);
|
||||
if (
|
||||
url.protocol !== 'https:' ||
|
||||
url.username ||
|
||||
url.password ||
|
||||
url.pathname !== '/' ||
|
||||
url.search ||
|
||||
url.hash
|
||||
)
|
||||
throw new Error();
|
||||
qiniuPublicDomain = url.origin;
|
||||
} catch {
|
||||
throw new Error(
|
||||
'QINIU_PUBLIC_DOMAIN must be an HTTPS origin without credentials, paths or query',
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...getRuntimeOptions(env),
|
||||
nodeEnv: nodeEnv as ServerConfig['nodeEnv'],
|
||||
@@ -130,6 +193,17 @@ export function getServerConfig(
|
||||
jwtSecret,
|
||||
jwtIssuer,
|
||||
jwtAudience,
|
||||
wechatMiniAppId,
|
||||
wechatMiniAppSecret,
|
||||
databaseUrl,
|
||||
worksheetNewWindowDays: integer(
|
||||
env,
|
||||
'WORKSHEET_NEW_WINDOW_DAYS',
|
||||
30,
|
||||
1,
|
||||
365,
|
||||
),
|
||||
qiniuPublicDomain,
|
||||
jwtExpiresInSeconds: integer(
|
||||
env,
|
||||
'JWT_EXPIRES_IN_SECONDS',
|
||||
|
||||
Reference in New Issue
Block a user