This commit is contained in:
2026-05-14 01:11:58 +08:00
parent b13870f71b
commit 5a55180b78
61 changed files with 5050 additions and 1057 deletions

View File

@@ -8,9 +8,18 @@ const apiServerExePath = resolve(
repoRoot,
'server-rs/target/debug/api-server.exe',
);
const shellEnvKeys = new Set(Object.keys(process.env));
const LOCAL_ENV_FILES = ['.env', '.env.local', '.env.secrets.local'];
function buildProtectedEnvKeys(baseEnv) {
return new Set(
Object.entries(baseEnv)
.filter(([, value]) => String(value ?? '').trim())
.map(([key]) => key),
);
}
const shellEnvKeys = buildProtectedEnvKeys(process.env);
function loadEnvFile(path, target, protectedKeys = shellEnvKeys) {
if (!existsSync(path)) {
return;
@@ -54,7 +63,7 @@ export function loadApiServerEnv(
export function mergeApiServerEnv(repoRootPath, baseEnv = process.env) {
const mergedEnv = { ...baseEnv };
loadApiServerEnv(repoRootPath, mergedEnv, new Set(Object.keys(baseEnv)));
loadApiServerEnv(repoRootPath, mergedEnv, buildProtectedEnvKeys(baseEnv));
return mergedEnv;
}

View File

@@ -63,4 +63,32 @@ describe('api-server-dev env merge', () => {
},
);
});
test('空外层 shell 变量不会遮蔽本地私密配置', () => {
withTempEnvFiles(
{
'.env.local': [
'ALIYUN_OSS_BUCKET=dev-bucket',
'ALIYUN_OSS_ENDPOINT=oss-cn-shanghai.aliyuncs.com',
].join('\n'),
'.env.secrets.local': [
'ALIYUN_OSS_ACCESS_KEY_ID=local-access-key',
'ALIYUN_OSS_ACCESS_KEY_SECRET=local-access-secret',
].join('\n'),
},
(_env, tempDir) => {
const env = mergeApiServerEnv(tempDir, {
ALIYUN_OSS_BUCKET: '',
ALIYUN_OSS_ENDPOINT: ' ',
ALIYUN_OSS_ACCESS_KEY_ID: 'shell-access-key',
ALIYUN_OSS_ACCESS_KEY_SECRET: '',
});
expect(env.ALIYUN_OSS_BUCKET).toBe('dev-bucket');
expect(env.ALIYUN_OSS_ENDPOINT).toBe('oss-cn-shanghai.aliyuncs.com');
expect(env.ALIYUN_OSS_ACCESS_KEY_ID).toBe('shell-access-key');
expect(env.ALIYUN_OSS_ACCESS_KEY_SECRET).toBe('local-access-secret');
},
);
});
});

View File

@@ -0,0 +1,51 @@
import { mergeApiServerEnv } from './api-server-dev.mjs';
const REQUIRED_FOR_PUZZLE_GENERATION = [
'VECTOR_ENGINE_BASE_URL',
'VECTOR_ENGINE_API_KEY',
'ALIYUN_OSS_BUCKET',
'ALIYUN_OSS_ENDPOINT',
'ALIYUN_OSS_ACCESS_KEY_ID',
'ALIYUN_OSS_ACCESS_KEY_SECRET',
];
const COMMON_MISTYPED_KEYS = [
'ALIYUN_0SS_BUCKET',
'ALIYUN_0SS_ENDPOINT',
'ALIYUN_0SS_ACCESS_KEY_ID',
'ALIYUN_0SS_ACCESS_KEY_SECRET',
];
function hasValue(value) {
return typeof value === 'string' && value.trim().length > 0;
}
function printStatus(key, present) {
console.log(`${key}: ${present ? 'present' : 'missing'}`);
}
const env = mergeApiServerEnv(process.cwd(), process.env);
const missing = [];
console.log('[api-server-env] 拼图真实生成配置检查');
for (const key of REQUIRED_FOR_PUZZLE_GENERATION) {
const present = hasValue(env[key]);
printStatus(key, present);
if (!present) {
missing.push(key);
}
}
const typoKeys = COMMON_MISTYPED_KEYS.filter((key) => hasValue(env[key]));
if (typoKeys.length > 0) {
console.log(
`[api-server-env] 检测到疑似拼错的 OSS 键名:${typoKeys.join(', ')}`,
);
}
if (missing.length > 0) {
console.error(`[api-server-env] 缺少:${missing.join(', ')}`);
process.exit(1);
}
console.log('[api-server-env] 配置齐全。重启 npm run api-server 或 npm run dev 后生效。');

View File

@@ -63,7 +63,11 @@ load_api_server_env_files() {
done < <(
node - "${env_files[@]}" <<'NODE'
const fs = require('fs');
const shellEnvKeys = new Set(Object.keys(process.env));
const shellEnvKeys = new Set(
Object.entries(process.env)
.filter(([, value]) => String(value ?? '').trim())
.map(([key]) => key),
);
const values = new Map();
for (const filePath of process.argv.slice(2)) {

View File

@@ -1,6 +1,7 @@
import {spawn} from 'node:child_process';
import {existsSync, readFileSync} from 'node:fs';
import {resolve} from 'node:path';
import {
findAvailablePort,
formatPortDecision,
@@ -8,7 +9,11 @@ import {
} from './dev-stack-port-utils.mjs';
const repoRoot = process.cwd();
const shellEnvKeys = new Set(Object.keys(process.env));
const shellEnvKeys = new Set(
Object.entries(process.env)
.filter(([, value]) => String(value ?? '').trim())
.map(([key]) => key),
);
function loadEnvFile(path, target) {
if (!existsSync(path)) {

View File

@@ -25,7 +25,7 @@ const styleTemplates = [
id: 'pixel-retro',
title: '像素复古',
prompt:
'复古像素游戏道具素材风格,有限色板,清晰像素边缘,主体轮廓稳定,像 32-bit 休闲游戏图标。',
'真正复古像素游戏道具 sprite 风格,先以约 64x64 低分辨率像素块绘制再按整数倍放大,硬边方块像素清晰可见,有限色板 12-24 色,主体轮廓稳定,禁止抗锯齿、柔焦、平滑渐变、真实 3D 渲染、PBR 材质和摄影光照。',
},
{
id: 'watercolor',
@@ -117,14 +117,15 @@ function buildVectorEngineImagesGenerationUrl(baseUrl) {
: `${baseUrl}/v1/images/generations`;
}
// 中文注释:入口缩略图只用于比较画风,必须展示单个代表道具,避免误导为一组待切割物品。
function buildPrompt(template) {
return [
'请生成一张 1:1 方形抓大鹅入口 2D 素材风格参考图。',
'画面是一组 5 个小型游戏道具样张,题材统一为水果、甜点、玩具和宝石的混合展示。',
'画面只允许出现 1 个完整独立的游戏道具主体,题材固定为一颗红苹果,不要出现第二个物品。',
`整体风格:${template.prompt}`,
'要求:个道具是独立 2D 素材示例,主体集中,轮廓清晰,适合被切成抓大鹅局内物品素材。',
'构图:浅色干净背景,散点排列,留有呼吸感,不要九宫格边框,不要 UI 面板,不要按钮。',
'避免文字、水印、logo、教程标注、真实照片、复杂场景、人物、动物、3D 模型视口、明显透视地面、厚重阴影。',
'要求:个道具是独立 2D 素材示例,主体集中,轮廓清晰,适合作为抓大鹅局内物品素材。',
'构图:浅色干净背景,单物体居中放大,四周留少量呼吸感,不要九宫格边框,不要 UI 面板,不要按钮。',
'避免:多个物品、5 个物品、物品组合、重复视角、散点排列、文字、水印、logo、教程标注、真实照片、复杂场景、人物、动物、3D 模型视口、明显透视地面、厚重阴影。',
].join('');
}