文章摘要: 本篇从一台尚未配置项目的开发电脑开始,建立原始资料、工作副本与备份目录,检查Node.js和包管理器,安装教学工程依赖,编写配置读取与HTTP启动入口,验证健康检查、端口监听及环境报告,并说明Cocos客户端、数据库和后台在后续搭建中的位置。
文章目录
-
- 前言
- 一、先理解全端工程各部分的位置
- 二、建立可回退的目录
- 三、确认Node.js与包管理器
- 四、逐项认识目录和依赖
- 五、创建本地配置文件
- 六、读取配置时尽早发现错误
- 七、编写最小HTTP启动入口
- 八、启动并核对三个结果
- 九、检查端口与工作进程
- 十、生成可用于后续排错的环境报告
- 十一、为Cocos和数据库预留正确入口
- 十二、做一次从关闭到恢复的完整练习
-
- [1. 不依赖"昨天还开着的终端"](#1. 不依赖“昨天还开着的终端”)
- [2. 明确安装、运行和开发是三类操作](#2. 明确安装、运行和开发是三类操作)
- [3. 认识地址栏里的三个组成部分](#3. 认识地址栏里的三个组成部分)
- [4. 用一次可控错误理解配置校验](#4. 用一次可控错误理解配置校验)
- [5. 保存一份真正能重新开始的材料](#5. 保存一份真正能重新开始的材料)
- 十三、常见错误与本篇验收
前言

拿到一套棋牌工程,直接寻找"启动全部服务"的按钮,往往会同时遇到依赖缺失、端口占用和数据库连接失败。多个问题混在一起,新手很难判断应该先修哪一项。更有效的做法是先让一个最小服务正常启动,再逐层接入数据库、账号、房间和客户端。
本系列围绕百游类棋牌的开发搭建需求,建立一套独立教学工程。这里的路径、模块和接口都由本系列定义,不冒充百游原版源码。各篇使用同一套文件,前后端字段与数据库结构相互对应,读者可以先运行,再按章节理解和修改。
为了把多人状态、断线恢复与结算做成可复现的完整例子,业务验证使用双人五子棋G1:十五路棋盘、黑先、无禁手、五连及以上获胜。它用于验证棋牌共用底座;地方麻将、跑得快等有隐藏手牌和不同仲裁规则,不能仅换皮肤就直接套用。本系列会在相应章节说明扩展边界。
本篇完成后,读者应能打开一个本地HTTP服务、查询版本、检查端口并生成环境报告。数据库就绪和完整牌局属于后续步骤,不把第一篇的HTTP成功当成全部系统已经搭建完成。
一、先理解全端工程各部分的位置
客户端负责页面、输入和网络请求;服务端验证操作并更新房间;数据库保存账号、成员、状态、事件与结算;后台通过受控接口管理组织与配置。客户端不直接连接数据库,也不保存数据库密码。
text
Cocos 客户端 / 浏览器联调页
├─ HTTP:注册、登录、创建房间、提交动作、查询战绩
└─ WebSocket:认证、订阅房间、接收最新快照
│
Node.js 服务端
├─ 账号与会话
├─ 房间与规则
├─ 结算与记录
└─ 俱乐部权限
│
PostgreSQL
本系列让业务动作通过HTTP提交,用WebSocket通知最新状态。这样读者可以用PowerShell独立验证每个动作,再接入客户端。HTTP与长连接不是两套业务规则,最终都围绕同一份服务端状态工作。
数据库暂未启动时,最小服务仍可以证明Node、依赖和端口正常;但它必须明确报告尚未就绪。将存活和就绪分开,能减少"页面能打开,所以数据库肯定正常"的误判。
二、建立可回退的目录
打开PowerShell,执行以下命令。本系列统一使用用户目录,避免电脑没有D盘造成路径错误:
powershell
$labRoot = Join-Path $env:USERPROFILE 'BaiyouLab'
'00-original','01-working','02-backup' | ForEach-Object {
New-Item -ItemType Directory -Force -Path (Join-Path $labRoot $_) | Out-Null
}
Get-ChildItem -LiteralPath $labRoot
把本系列附件中的"示例工程"先复制到00-original,再把它的内容复制到01-working。工作目录顶层应该直接包含package.json,不能再多套一层"示例工程"文件夹。原始副本保持不动,之后所有命令都在工作副本执行。
powershell
Set-Location (Join-Path $env:USERPROFILE 'BaiyouLab\01-working')
Get-Location
Test-Path -LiteralPath '.\package.json'
最后一条应返回True。若返回False,先调整目录,不要立即重新安装软件。很多"找不到package.json"的错误只是当前目录错误,与Node版本无关。
三、确认Node.js与包管理器
本系列使用Node.js 24 LTS。安装时从官方获取对应系统与架构的版本,完成后重新打开终端,检查:
powershell
node --version
npm --version
Get-Command node,npm | Select-Object Name,Source
Node输出应属于v24.x.x。Get-Command用来确认实际执行路径;电脑上存在多个版本时,显示在控制面板中的版本未必是当前终端使用的版本。Node的LTS状态与生命周期可在官方版本页面核对。Node版本说明
附件使用pnpm-lock.yaml锁定依赖。安装与锁文件匹配的包管理器后,不要混用npm生成另一份锁文件:
powershell
npm install --global pnpm@11.19.0
pnpm --version
pnpm install --frozen-lockfile
pnpm list --depth 0
第一条会安装包管理器,后面的命令在工作目录执行。--frozen-lockfile要求依赖声明与锁文件一致,失败时应先检查附件是否完整,而不是删除锁文件重新生成一套未知依赖。
网络错误、证书错误与版本不匹配是不同问题。不要为了下载成功关闭TLS校验;先确认网络、代理与可信证书配置。安装成功后,node_modules属于本机生成目录,不需要与文章一起复制给其他读者。
四、逐项认识目录和依赖
text
01-working/
├─ package.json 依赖与运行命令
├─ pnpm-lock.yaml 本系列依赖锁文件
├─ .env.example 配置模板,不是真实凭据
├─ lessons/01-bootstrap.mjs 本篇最小启动入口
├─ src/ 后续完整教学服务端
├─ sql/001-initial.sql 第二篇数据库迁移
├─ scripts/ 迁移、报告和联调脚本
├─ web/demo.html 浏览器联调页面
├─ cocos/ Cocos组件示例
└─ test/ 可独立运行的测试
package.json完整内容如下。附件已经包含此文件,阅读时重点对应脚本和依赖,不需要再次覆盖:
json
{
"name": "baiyou-tutorial",
"version": "1.0.0",
"private": true,
"type": "module",
"engines": {
"node": ">=24 <25"
},
"scripts": {
"lesson:01": "node --env-file=.env lessons/01-bootstrap.mjs",
"report": "node scripts/environment-report.mjs",
"dev": "node --watch --env-file=.env src/main.mjs",
"start": "node --env-file=.env src/main.mjs",
"migrate": "node --env-file=.env scripts/migrate.mjs",
"test": "node --test test/*.test.mjs"
},
"dependencies": {
"@fastify/websocket": "^11.2.0",
"fastify": "^5.6.1",
"pg": "^8.16.3"
},
"devDependencies": {
"@electric-sql/pglite": "^0.5.8"
}
}
lesson:01启动当前最小入口,start启动后续完整示例,两者不是同一个阶段。migrate只执行数据库结构迁移,不会启动游戏。test运行自动化检查,也不表示数据库或Cocos已经验证。
服务端示例使用Node可直接执行的JavaScript ES模块,文件扩展名为.mjs;Cocos组件使用TypeScript。这样第一篇不额外引入编译工具链,但仍保持清晰的模块边界。若实际项目采用服务端TypeScript,需要补充类型检查与构建步骤,不能仅修改扩展名。
五、创建本地配置文件
先确认是否已有.env。已有配置不要覆盖;首次准备时复制模板:
powershell
if (-not (Test-Path -LiteralPath '.\.env')) {
Copy-Item -LiteralPath '.\.env.example' -Destination '.\.env'
}
notepad .env
本篇只使用监听地址、端口和允许来源。先把数据库连接设置为空,第二篇配置好数据库后再填写:
dotenv
APP_HOST=127.0.0.1
APP_PORT=3000
DATABASE_URL=
ALLOWED_ORIGINS=http://localhost:7456,http://127.0.0.1:7456,http://localhost:3000,http://127.0.0.1:3000
注意文件名是.env,不是.env.txt。编辑器另存为时应显示文件扩展名。环境变量在进程启动时读取,修改文件后需要重启服务;终端中已经设置的同名环境变量也可能影响结果,应记录并检查实际来源。
127.0.0.1只接收本机访问。后续手机联调才改为监听局域网接口,并配合防火墙范围。0.0.0.0可以是监听地址,但不是手机应填写的连接地址。
六、读取配置时尽早发现错误
创建或核对src/config.mjs:
javascript
export function loadConfig(env = process.env) {
const port = Number(env.APP_PORT ?? 3000);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('APP_PORT must be an integer between 1 and 65535');
}
return {
host: env.APP_HOST ?? '127.0.0.1',
port,
databaseUrl: env.DATABASE_URL ?? '',
origins: (env.ALLOWED_ORIGINS ?? '').split(',').map(x => x.trim()).filter(Boolean)
};
}
端口从环境变量读出时是字符串,先转换,再判断是否为有效整数。否则abc、零或超范围数值可能在更后面的网络启动位置报错,让新手误以为是框架问题。
允许来源按逗号拆分并去除空格,供后续浏览器跨域和WebSocket校验使用。它不等同于玩家权限,来源通过后仍需登录与房间成员检查。原生客户端没有浏览器同样的Origin行为,也不能用Origin代替身份认证。
配置输出应只显示必要信息。数据库URL可能包含密码,不要为了"检查加载成功"把整份.env打印进日志或截图。
七、编写最小HTTP启动入口
核对lessons/01-bootstrap.mjs完整内容:
javascript
import Fastify from 'fastify';
import { loadConfig } from '../src/config.mjs';
const config = loadConfig();
const app = Fastify({ logger: true });
app.get('/health/live', async () => ({ status: 'alive', service: 'baiyou-tutorial', phase: '01' }));
app.get('/health/ready', async (req, reply) => reply.code(503).send({ status: 'not_ready', reason: 'DATABASE_NOT_CONNECTED_IN_LESSON_01' }));
app.get('/api/version', async () => ({ protocolVersion: 1, ruleVersion: 'G1', lesson: 1 }));
for (const signal of ['SIGINT','SIGTERM']) {
process.once(signal, () => { void app.close(); });
}
try { await app.listen({ host: config.host, port: config.port }); }
catch (error) { app.log.error({ code: error.code }, 'startup failed'); await app.close(); process.exitCode = 1; }
三个接口分别回答进程是否存活、是否达到当前业务就绪条件、当前教学协议是什么。第一篇的就绪检查固定返回503并说明数据库尚未接入,这是刻意设计的阶段结果,不是部署故障。
启动时捕获端口冲突等错误,正常退出时关闭服务。不要把启动失败吞掉后仍打印"启动成功"。Fastify提供listen和close等生命周期接口,实际使用时应等待它们完成。Fastify服务接口
八、启动并核对三个结果
在第一个终端执行:
powershell
pnpm run lesson:01
终端保持运行,日志应出现本机3000端口的监听信息。再打开第二个PowerShell,分别查询:
powershell
Invoke-RestMethod -Uri 'http://127.0.0.1:3000/health/live'
Invoke-RestMethod -Uri 'http://127.0.0.1:3000/api/version'
curl.exe -i 'http://127.0.0.1:3000/health/ready'
预期前两个接口返回200,分别出现alive和协议版本1;第三个接口返回HTTP503,并包含DATABASE_NOT_CONNECTED_IN_LESSON_01。明确调用curl.exe,避免旧版PowerShell把curl解释成其他命令。
json
{"status":"alive","service":"baiyou-tutorial","phase":"01"}
不要把示例输出逐字当成全部运行日志。日志时间、进程编号和请求编号会变化,检查重点是HTTP状态与业务字段。若返回HTML错误页,先确认访问到了正确服务,可能是端口上运行着另一套程序。
九、检查端口与工作进程
powershell
$listeners = Get-NetTCPConnection -LocalPort 3000 -State Listen -ErrorAction SilentlyContinue
$listeners | Select-Object LocalAddress,LocalPort,OwningProcess
$listeners | ForEach-Object { Get-Process -Id $_.OwningProcess }
看到监听还要看所属进程。EADDRINUSE表示目标地址和端口已被占用,先找到原进程用途,再决定停止自己启动的旧服务或更换测试端口。不要直接结束所有Node进程,这可能影响其他项目。
需要使用3001时,修改.env中的APP_PORT并同步修改访问地址;如果后续浏览器页面也改用3001,还要更新允许来源。端口改了但客户端仍连接3000,是常见的"服务运行却连接失败"。
关闭第一个终端中的服务可按Ctrl+C,随后再次查询监听,应不再出现该实例。退出后端口仍存在时,检查是否还有第二个实例,而不是认定退出逻辑一定失效。
十、生成可用于后续排错的环境报告
报告只记录版本和文件检查,不读取真实凭据。核对 scripts/environment-report.mjs:
javascript
import { readFile, writeFile, mkdir, access } from 'node:fs/promises';
import { resolve } from 'node:path';
import os from 'node:os';
const root = process.cwd();
const pkg = JSON.parse(await readFile(resolve(root,'package.json'),'utf8'));
const required = ['src','sql','scripts','web','test','pnpm-lock.yaml'];
const checks = [];
for (const name of required) {
try { await access(resolve(root,name)); checks.push({ name, exists: true }); }
catch { checks.push({ name, exists: false }); }
}
const report = {
generatedAt: new Date().toISOString(), node: process.version,
platform: process.platform, arch: process.arch, memoryGiB: Math.round(os.totalmem()/1024**3),
package: pkg.name, dependencies: pkg.dependencies, checks
};
await mkdir(resolve(root,'runtime'),{recursive:true});
await writeFile(resolve(root,'runtime/environment-report.json'),JSON.stringify(report,null,2));
console.log('Saved runtime/environment-report.json');
if (checks.some(c => !c.exists)) process.exitCode=1;
执行并打开结果:
powershell
pnpm run report
Get-Content -LiteralPath '.\runtime\environment-report.json'
报告中必需目录应显示exists: true。它证明文件存在,不证明数据库或所有功能已运行。后续反馈问题时,还应附具体命令、HTTP状态和已经脱敏的错误信息。
十一、为Cocos和数据库预留正确入口
Cocos通过Dashboard创建或打开与约定版本一致的工程,确认实际目录包含assets和项目配置。附件cocos目录只有教学脚本,不是完整Cocos工程,不能直接把它作为工程打开。
本篇暂不安装全部原生平台工具,先完成编辑器预览与HTTP联调。Android SDK、NDK和iOS构建环境放到最后一篇按目标引擎与平台配置,以免环境准备阶段同时混入多个不相关问题。
数据库准备也按同样原则推进:第二篇先启动PostgreSQL、创建测试业务账号、执行迁移,确认就绪接口后再开启注册登录。切换到完整服务时,先停止lesson:01,否则它仍占用3000端口。
十二、做一次从关闭到恢复的完整练习
1. 不依赖"昨天还开着的终端"
第一次成功之后,主动结束自己启动的最小服务,再关闭当前终端,重新打开一个干净的PowerShell。从定位工作目录开始,依次检查package.json、Node版本、配置文件和启动命令。这一步很有价值:如果只有昨天的终端能运行,说明某个必要条件可能只保存在临时环境变量或偶然的工作目录中,还没有写进工程配置。
重新启动后再查三个接口,结果应与前面的阶段预期一致。第一篇的ready依然是503,不能为了让检查全部显示绿色而修改为200。阶段验收需要保留真实含义,否则到第二篇时就无法区分数据库到底有没有接入。记录响应字段即可,不必保存大量重复的请求日志。
2. 明确安装、运行和开发是三类操作
安装依赖是把锁文件指定的软件准备到本机;启动服务是运行当前代码;修改代码后重新启动或启用开发监视才会使用新逻辑。新手容易在服务已运行时反复执行安装命令,希望接口自动修好。实际上,如果错误来自配置或业务代码,重新下载同一份依赖通常不会改变结果,只会增加排错变量。
本篇使用普通启动命令,修改入口或环境文件后手动停止并重新启动。后续dev命令带有代码监视,但也不应该依赖它处理所有环境变化。尤其涉及端口、数据库地址和认证配置时,明确重启并观察新进程更容易确认修改确实生效。
3. 认识地址栏里的三个组成部分
访问地址包含协议、主机和端口,后面才是接口路径。localhost与127.0.0.1通常都能指向本机,但对浏览器来源比较而言是不同的字符串;3000与3001也属于不同来源。路径写对而端口写错,可能访问到另一个项目,甚至得到一个看起来很正常的200响应。
因此第一次核对接口时要同时观察service和phase字段。它们不是安全凭证,只是帮助开发者识别当前实例。请求返回404时先比较路径与当前入口,不要因为完整服务将来有/demo,就假定第一篇的最小入口现在也应存在这个页面。
4. 用一次可控错误理解配置校验
先记录当前端口,再把工作副本.env中的APP_PORT临时改成非数字内容,停止服务后重新启动。预期启动被拒绝,而不是悄悄改用默认端口。恢复原值后再次启动,应恢复正常。这个练习只修改本地工作副本,结束时确认文件已经恢复,避免把故意制造的错误带到下一篇。
这也说明配置检查为什么放在程序入口:错误越早暴露,读者越容易知道问题来自哪里。若服务已经接受用户操作才发现连接参数不完整,排查就会同时涉及网络、业务和数据库。本系列后续的输入校验也沿用同样思路,先拒绝明显不合法的输入,再进入有副作用的步骤。
5. 保存一份真正能重新开始的材料
阶段备份应包含源码、迁移、锁文件和配置模板,另行记录你使用的工具版本。真实.env可以在自己控制的备份位置保存,但不要混进要公开发送的文章包。node_modules可以重新安装,原始业务资料和自己修改过的代码却未必能够重新获得,两者的备份优先级不同。
反馈问题时用一句话说明"在哪个目录执行哪个命令,预期是什么,实际是什么",再附第一条错误和环境报告。只说"打不开"无法判断是终端、端口还是浏览器问题。按这个方式记录,后面的数据库和客户端联调也能沿用同一份问题模板,减少反复询问环境的时间。
十三、常见错误与本篇验收
找不到Node时,先重开终端并检查安装路径;找不到依赖时,确认当前目录和锁文件安装结果;.env读取失败时,检查文件名和启动目录;端口冲突时查询进程;接口无法访问时先确认监听,再检查地址,不立即修改业务代码。
若pnpm run test失败,保留第一条失败用例和错误堆栈,不要把测试文件删除。附件测试覆盖若干可独立验证的逻辑,数据库联调、Cocos运行与移动端构建另行检查,不能用某一组测试的成功替代全端验收。
完成本篇后应具备:独立原始副本、正确工作目录、Node24与包管理器记录、成功安装的锁定依赖、有效配置、HTTP存活响应、明确的未就绪响应、可识别的监听进程和环境报告。第一阶段备份时保留源码与锁文件,不把node_modules当成唯一依赖来源。
下一篇将启动PostgreSQL,创建账号、房间、事件和结算所需的表,执行带版本与校验值的迁移,并把完整服务的就绪检查从503推进到200。