Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
LICENSE

# 仓库根本地配置与 IDE 用户状态不进入构建上下文(镜像配置来自 GameFrameX.Config/json 与运行时挂载)
Configs/
Server.sln.DotSettings.user
8 changes: 5 additions & 3 deletions .github/workflows/docs-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ on:
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup .NET Core
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x

Expand All @@ -31,7 +33,7 @@ jobs:
docfx build docfx.json

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/_site
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ on:

jobs:
release:
# GITHUB_TOKEN 权限由复用工作流内部统一声明(contents: write / packages: write),调用方无需重复声明。
uses: GameFrameX/public-github-actions/.github/workflows/publish-dotnet-release.yml@main
with:
dotnet_version: "10.0.x"
version_props_path: "Version.props"
# 上游 publish-dotnet-release.yml 未声明 on.workflow_call.secrets
# (NUGET_API_KEY / DOCKERHUB_* / BLUESKY_APP_PASSWORD 靠 inherit 透传),
# 改显式列表会导致发布链路拿不到凭据;如需收敛,先在 public-github-actions 补 secrets 声明。
secrets: inherit
3 changes: 2 additions & 1 deletion .github/workflows/sonar-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ jobs:
sonar_organization: ${{ vars.SONAR_ORGANIZATION || 'gameframex' }}
sonar_branch: main
github_repository: ${{ github.repository }}
secrets: inherit
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
6 changes: 6 additions & 0 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ on:
branches: [main]
workflow_dispatch:

# 镜像同步仅需读取仓库信息(Gitee/CNB 推送走专用 token)
permissions:
contents: read

jobs:
sync:
uses: GameFrameX/public-github-actions/.github/workflows/sync.yml@main
with:
target_branch: ${{ github.ref_name }}
repository_name: ${{ github.repository }}
# 上游 sync.yml 未声明 on.workflow_call.secrets(GITEE/CNB token 靠 inherit 透传),
# 改显式列表会导致同步链路拿不到凭据;如需收敛,先在 public-github-actions 补 secrets 声明。
secrets: inherit
6 changes: 4 additions & 2 deletions .github/workflows/topology-equivalence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ on:
jobs:
topology-equivalence:
runs-on: ubuntu-latest
permissions:
contents: read
services:
mongo:
image: mongo:7
Expand Down Expand Up @@ -44,10 +46,10 @@ jobs:
GAMEFRAMEX_TEST_POSTGRESQL_CONNECTION_STRING: 'Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=postgres'
steps:
- name: Checkout Repository
uses: actions/checkout@v3
uses: actions/checkout@v7

- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.x

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ gfx-doc

# C221:gfx-role 模板产出的 .proto 源暂存(移入 Protobuf 仓后即弃,不入库)
scripts/scaffold-out/

# 本地环境变量(compose 凭据注入用),不入库
.env
.env.*
!.env.example
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
* GameFrameX.Foundation.Logger ≥ 4.1.1 的 nuspec 无条件声明 MongoDB.Driver 与 Serilog.Sinks.MongoDB(运行时 IsWriteToMongoDb 默认关闭)。已解除——Foundation 5.0.0 将 Mongo/Loki sink 拆为可选组件包,随 C224 全量升级后传递依赖归零,验证 feed 补丁包不再需要

## [Unreleased]
### Security(仓库与部署面修复)

* 默认编排与 CI 供应链收紧:docker-compose.yml 的 Mongo 端口绑定 127.0.0.1;docker-compose.multi.yml 12 个实例的 TCP/HTTP 端口改由生成器统一绑定 127.0.0.1;GitHub Actions 升级至当前最新 major(actions/checkout v7、actions/setup-dotnet v6、peaceiris/actions-gh-pages v4),docs-publish / topology-equivalence / sync 显式声明最小 permissions,sonar-issues 改显式传 SONAR_TOKEN(release / sync 因上游 public-github-actions 未声明 on.workflow_call.secrets 暂保留 secrets: inherit,已在文件内注释说明);.gitignore 补 .env / .env.* 排除;.dockerignore 排除仓库根 Configs/ 与 IDE 用户设置;deploy-game.sh 含凭据请求体改用 mktemp 私有临时文件并 trap 退出清理(不再落可预测的 /tmp/deploy-body.json)

### Changed

* Dockerfile 构建层优化:restore 前先分层 COPY 全部工程与属性文件(仅项目结构/版本清单变化才重新 restore,源码变更实测 restore 层命中 CACHED);删除 final 阶段重复的 publish COPY;热更 DLL 同步移除 `|| true` 吞错(glob 未匹配或复制失败改为构建期 fail fast);scripts/acceptance.sh 仓库根改为按脚本自身位置推导并修正解决方案为 Server.slnx(原硬编码个人绝对路径且引用已不存在的 Server.sln)

### Fixed
* HTTP 请求管线正确性与响应一致性(C458,GFX-1180,GitHub #926):ProtoBuf 成功响应写出前更正 Content-Type 为 `application/x-protobuf`(原恒为 JSON,按媒体类型分派的客户端/网关解析二进制体失败);参数字典改 OrdinalIgnoreCase 并同步把 Query 提取改 TryAdd——大小写差异同名键(Query↔Body、Query↔Query)一律显式 `ParamErr` 拒绝,消除「被校验/被签名的 Query 值被 Body 静默覆盖」窗口;非 JSON 对象 Body(null 字面量/数组/标量/空体)以参数错误拒绝(新键 `Network.Http.RequestBodyNotJsonObject`),不再落入顶层 catch 记服务器错误日志;非 GET 缺 Content-Type 改统一 JSON envelope;DataAnnotations 校验失败业务码由字面量 400 统一为 `GameHttpStatusCode.ParamErr`;Swagger 路由匹配改末段精确比较(互为后缀命令不再错配文档,驼峰路由可匹配)且 GET 参数必填性跟随 `RequiredAttribute`;AOP 拒绝且未自行写响应时管线回写校验失败 envelope(契约写入 `IHttpAopHandler.Run`);`CheckSign` 缺 sign/timestamp 改本地化消息。对外破坏性:依赖 `code == 400` 判定校验失败的客户端改判 `code == 13`;`HttpStatusMessage.Success`/`NotFoundCommand`/`CheckFailedCommand` 及孤儿键 `Network.Http.NotFoundCommand`/`CheckFailedCommand` 删除(公共 API,下游如有引用需迁移)。
Expand Down
44 changes: 39 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,46 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

COPY . .
# 先复制 restore 所需的工程与属性文件:仅当项目结构/版本清单变化时才重新 restore,源码变更不再使依赖层缓存失效
COPY Directory.Build.props Directory.Build.targets Version.props global.json ./
COPY Capability/GameFrameX.Capability.Hosting.Abstractions/GameFrameX.Capability.Hosting.Abstractions.csproj Capability/GameFrameX.Capability.Hosting.Abstractions/
COPY Capability/GameFrameX.Capability.Hosting.AdminApi/GameFrameX.Capability.Hosting.AdminApi.csproj Capability/GameFrameX.Capability.Hosting.AdminApi/
COPY Capability/GameFrameX.Capability.Hosting.Core/GameFrameX.Capability.Hosting.Core.csproj Capability/GameFrameX.Capability.Hosting.Core/
COPY Capability/GameFrameX.Capability.Online.Abstractions/GameFrameX.Capability.Online.Abstractions.csproj Capability/GameFrameX.Capability.Online.Abstractions/
COPY Capability/GameFrameX.Capability.Online.Assets/GameFrameX.Capability.Online.Assets.csproj Capability/GameFrameX.Capability.Online.Assets/
COPY Capability/GameFrameX.Capability.Online.Competition/GameFrameX.Capability.Online.Competition.csproj Capability/GameFrameX.Capability.Online.Competition/
COPY Capability/GameFrameX.Capability.Online.Foundation/GameFrameX.Capability.Online.Foundation.csproj Capability/GameFrameX.Capability.Online.Foundation/
COPY Capability/GameFrameX.Capability.Online.Match/GameFrameX.Capability.Online.Match.csproj Capability/GameFrameX.Capability.Online.Match/
COPY Capability/GameFrameX.Capability.Online.Matchmaking/GameFrameX.Capability.Online.Matchmaking.csproj Capability/GameFrameX.Capability.Online.Matchmaking/
COPY Capability/GameFrameX.Capability.Online.Operations/GameFrameX.Capability.Online.Operations.csproj Capability/GameFrameX.Capability.Online.Operations/
COPY Capability/GameFrameX.Capability.Online.Persistence/GameFrameX.Capability.Online.Persistence.csproj Capability/GameFrameX.Capability.Online.Persistence/
COPY Capability/GameFrameX.Capability.Online.Social/GameFrameX.Capability.Online.Social.csproj Capability/GameFrameX.Capability.Online.Social/
COPY GameFrameX.Apps/GameFrameX.Apps.csproj GameFrameX.Apps/
COPY GameFrameX.Architecture.Analyzers/GameFrameX.Architecture.Analyzers.csproj GameFrameX.Architecture.Analyzers/
COPY GameFrameX.Client/GameFrameX.Client.csproj GameFrameX.Client/
COPY GameFrameX.Config/GameFrameX.Config.csproj GameFrameX.Config/
COPY GameFrameX.Core/GameFrameX.Core.csproj GameFrameX.Core/
COPY GameFrameX.DataBase/GameFrameX.DataBase.csproj GameFrameX.DataBase/
COPY GameFrameX.DataBase.Mongo/GameFrameX.DataBase.Mongo.csproj GameFrameX.DataBase.Mongo/
COPY GameFrameX.DataBase.PostgreSql/GameFrameX.DataBase.PostgreSql.csproj GameFrameX.DataBase.PostgreSql/
COPY GameFrameX.DataBase.Sqlite/GameFrameX.DataBase.Sqlite.csproj GameFrameX.DataBase.Sqlite/
COPY GameFrameX.Discovery/GameFrameX.Discovery.csproj GameFrameX.Discovery/
COPY GameFrameX.Hotfix/GameFrameX.Hotfix.csproj GameFrameX.Hotfix/
COPY GameFrameX.Hotfix.WrapperGenerator/GameFrameX.Hotfix.WrapperGenerator.csproj GameFrameX.Hotfix.WrapperGenerator/
COPY GameFrameX.Launcher/GameFrameX.Launcher.csproj GameFrameX.Launcher/
COPY GameFrameX.Localization/GameFrameX.Localization.csproj GameFrameX.Localization/
COPY GameFrameX.Network.Abstractions/GameFrameX.Network.Abstractions.csproj GameFrameX.Network.Abstractions/
COPY GameFrameX.Network/GameFrameX.Network.csproj GameFrameX.Network/
COPY GameFrameX.Network.HTTP/GameFrameX.Network.HTTP.csproj GameFrameX.Network.HTTP/
COPY GameFrameX.Network.RemoteMessaging/GameFrameX.Network.RemoteMessaging.csproj GameFrameX.Network.RemoteMessaging/
COPY GameFrameX.Proto/GameFrameX.Proto.csproj GameFrameX.Proto/
COPY GameFrameX.ProtoBuf.Net/GameFrameX.ProtoBuf.Net.csproj GameFrameX.ProtoBuf.Net/
COPY GameFrameX.StartUp/GameFrameX.StartUp.csproj GameFrameX.StartUp/
COPY GameFrameX.Templates/GameFrameX.Templates.csproj GameFrameX.Templates/
COPY GameFrameX.Utility/GameFrameX.Utility.csproj GameFrameX.Utility/
RUN dotnet restore "GameFrameX.Launcher/GameFrameX.Launcher.csproj"
RUN dotnet restore "GameFrameX.Hotfix/GameFrameX.Hotfix.csproj"
COPY . .
WORKDIR "/src/GameFrameX.Launcher"
RUN dotnet build "GameFrameX.Launcher.csproj" -c $BUILD_CONFIGURATION -o /app/build

Expand All @@ -32,15 +69,12 @@ COPY GameFrameX.Config/json ./json
RUN mkdir -p /app/hotfix
COPY --from=publish /app/build/hotfix/ /app/hotfix/
# 热更依赖加载逻辑默认从 /app 根目录查找引用程序集,这里同步 DLL 到根目录
RUN cp -f /app/hotfix/*.dll /app/ || true
RUN cp -f /app/hotfix/*.dll /app/

# 切换到root用户:创建数据目录(归属 $APP_UID,非 root 运行用户可写——SQLite 缺省文件 data/gameframex.db 落此)、写入入口脚本并赋权
USER root
RUN mkdir -p /app/data && chmod 755 /app/data && chown $APP_UID:0 /app/data

# 复制发布文件
COPY --from=publish /app/publish .

# 单一镜像多角色:镜像不固化、不检查任何业务参数。
# 启动参数由 GAMEFRAMEX_SERVER_ARGS 环境变量原样透传(含 --ServerType / --DataBaseUrl 等完整参数),
# 未设置时不传参数,由应用默认逻辑启动首个可用服务器。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public byte[] Compress(byte[] input)
/// <param name="maxOutputLength">允许的最大解压输出字节数 / Maximum allowed decompressed output length in bytes</param>
/// <returns>解压后的原始字节数组 / The decompressed byte array</returns>
/// <exception cref="System.IO.InvalidDataException">压缩数据非法、损坏、截断或解压输出超过上限时抛出 / Thrown when the data is invalid, corrupted, truncated, or exceeds the output limit</exception>
public byte[] Decompress(byte[] input, long maxOutputLength)
public byte[] Decompress(byte[] input, long maxOutputLength = CompressionHelper.DefaultMaxDecompressedOutputLength)
{
return CompressionHelper.Decompress(input, maxOutputLength);
}
Expand Down
28 changes: 15 additions & 13 deletions GameFrameX.Utility/NetHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -654,21 +654,23 @@ public static async Task<bool> IsNetworkReachableAsync(string host, int timeout
}

using (var ping = new Ping())
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
timeoutCancellationTokenSource.CancelAfter(timeout);
try
{
var reply = await ping.SendPingAsync(host, timeout).WaitAsync(timeoutCancellationTokenSource.Token);
return reply.Status == IPStatus.Success;
}
catch (OperationCanceledException)
using (var timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
return false;
}
catch
{
return false;
timeoutCancellationTokenSource.CancelAfter(timeout);
try
{
var reply = await ping.SendPingAsync(host, timeout).WaitAsync(timeoutCancellationTokenSource.Token);
return reply.Status == IPStatus.Success;
}
catch (OperationCanceledException)
{
return false;
}
catch
{
return false;
}
}
}
}
Expand Down
15 changes: 10 additions & 5 deletions build/deploy-game.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ DEPLOY_GAME_PORTS=$(echo "${DEPLOY_GAME_PORTS}" | jq -c .)
DEPLOY_GAME_VOLUMES=$(echo "${DEPLOY_GAME_VOLUMES}" | jq -c .)
DEPLOY_GAME_ENVIRONMENTS=$(echo "${DEPLOY_GAME_ENVIRONMENTS}" | jq -c .)

# 请求体含明文仓库凭据:用 mktemp 私有临时文件(0600),退出时无条件清理
DEPLOY_BODY_FILE=$(mktemp "${TMPDIR:-/tmp}/deploy-game-body.XXXXXX")
DEPLOY_RESP_FILE=$(mktemp "${TMPDIR:-/tmp}/deploy-game-resp.XXXXXX")
trap 'rm -f "${DEPLOY_BODY_FILE}" "${DEPLOY_RESP_FILE}"' EXIT

jq -n \
--arg image_name "${IMAGE_NAME}" \
--arg tag "${TAG}" \
Expand Down Expand Up @@ -117,21 +122,21 @@ jq -n \
ports: $ports,
volumes: $volumes,
environments: $environments
}' > /tmp/deploy-body.json
}' > "${DEPLOY_BODY_FILE}"

echo "[DEPLOY] 部署 Game 服务器"
echo "[DEPLOY] 请求地址: ${DEPLOY_API_URL}"
echo "[DEPLOY] 请求参数:"
jq '.Auth.Password = "****"' /tmp/deploy-body.json
jq '.Auth.Password = "****"' "${DEPLOY_BODY_FILE}"
echo ""

HTTP_CODE=$(curl -s --max-time 180 -o /tmp/deploy-resp.txt -w "%{http_code}" -X POST "${DEPLOY_API_URL}" \
HTTP_CODE=$(curl -s --max-time 180 -o "${DEPLOY_RESP_FILE}" -w "%{http_code}" -X POST "${DEPLOY_API_URL}" \
-H "Content-Type: application/json; charset=UTF-8" \
-d @/tmp/deploy-body.json)
-d @"${DEPLOY_BODY_FILE}")

echo "[DEPLOY] HTTP ${HTTP_CODE}"
echo "[DEPLOY] 响应内容:"
cat /tmp/deploy-resp.txt
cat "${DEPLOY_RESP_FILE}"
echo ""

if [ "${HTTP_CODE}" -ge 200 ] && [ "${HTTP_CODE}" -lt 300 ]; then
Expand Down
Loading
Loading