百游棋牌源代码开发搭建教程(一):全端工程结构、开发环境配置与首次启动验证

文章摘要: 本篇从一台尚未配置项目的开发电脑开始,建立原始资料、工作副本与备份目录,检查Node.js和包管理器,安装教学工程依赖,编写配置读取与HTTP启动入口,验证健康检查、端口监听及环境报告,并说明Cocos客户端、数据库和后台在后续搭建中的位置。

文章目录


前言

拿到一套棋牌工程,直接寻找"启动全部服务"的按钮,往往会同时遇到依赖缺失、端口占用和数据库连接失败。多个问题混在一起,新手很难判断应该先修哪一项。更有效的做法是先让一个最小服务正常启动,再逐层接入数据库、账号、房间和客户端。

本系列围绕百游类棋牌的开发搭建需求,建立一套独立教学工程。这里的路径、模块和接口都由本系列定义,不冒充百游原版源码。各篇使用同一套文件,前后端字段与数据库结构相互对应,读者可以先运行,再按章节理解和修改。

为了把多人状态、断线恢复与结算做成可复现的完整例子,业务验证使用双人五子棋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.xGet-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提供listenclose等生命周期接口,实际使用时应等待它们完成。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。

相关推荐
妙码生花1 小时前
两个月 59 篇 AI 开发日志 + Golang 商业级实战项目收工后,得来的 AI 使用心法-上
前端·后端·gin
vx-Biye_Design1 小时前
springboot中国传统节日宣传平台49078-计算机课程设计、毕业设计
java·前端·vue.js·spring boot·后端·课程设计·idea
妙码生花1 小时前
两个月 59 篇 AI 开发日志 + Golang 商业级实战项目收工后,得来的 AI 使用心法-下
前端·后端·go
Highcharts1 小时前
常见报错排雷指南3:兼容性问题的官方解法
javascript·数据可视化
5335ld1 小时前
app版本更新(vue3+unibest+静默更新+强制更新)
开发语言·javascript·ecmascript
Cho1yon2 小时前
【AI Agent 第十五期: AI Agent 从 0 到 1 系统学习大纲】
javascript·人工智能·学习
计算机魔术师2 小时前
Beren Millidge、John Schulman、Charlie O'Neill 对谈递归自我改进离我们还有多远
前端
MetaLite2 小时前
Java 时间处理的两个坑:单位误判,半秒算成一秒
java·开发语言·前端
hiahiahia1232 小时前
SSE 到底是什么?
前端·人工智能