Web框架 – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Mon, 08 Jun 2026 12:02:44 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 从零到一:在 Trae CN + Docker 中搭建 Go + Gin 开发环境 https://www.shuijingwanwq.com/2026/06/08/16544/ https://www.shuijingwanwq.com/2026/06/08/16544/#respond Mon, 08 Jun 2026 12:02:40 +0000 https://www.shuijingwanwq.com/?p=16544 Post Views: 33

写在前面

最近我在 Ubuntu 26.04 上尝试使用 Trae CN 编辑器,配合 Docker 容器来开发一个 Go + Gin 的项目(go-gin-learning)。整个过程踩了一些坑,但也跑通了一套还算顺手的工作流。这篇文章就是一份完整的记录,希望能帮到遇到类似问题的朋友。

如果你已经看过我之前的博客(比如《在 Ubuntu 26.04 中基于 Docker Compose + Go 1.26.4 完成基础环境的搭建》),那这篇可以算是“下一篇”——从代码提交到容器运行,全部串起来。


一、宿主机上的准备工作

1.1 安装 Git 并配置用户信息

首先确保宿主机有 Git,然后设置全局用户名和邮箱(这些信息会写在每次 commit 里):

git config --global user.name "shuijingwan"
git config --global user.email "shuijingwanwq@163.com"

验证一下:

git config --global user.name
git config --global user.email

1.2 配置 GitHub 认证(使用 Token)

从 2021 年起 GitHub 不再支持密码认证,需要生成 Personal Access Token。

  • 登录 GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
  • 点击 Generate new token (classic)
  • 勾选 repo 权限,生成后复制 token(只显示一次)
  • 在宿主机终端执行:
git push origin main

当提示输入用户名时输入你的 GitHub 用户名,密码处粘贴刚才复制的 token。成功后 Git 会记住认证信息(如果配置了 credential helper)。

1.3 安装 Go(宿主机)

为了让 Trae CN 中的代码补全、语法跳转正常工作,需要在宿主机安装 Go。我选择了使用 APT 安装,简单省事:

sudo apt update
sudo apt install golang-go -y
go version

输出:go version go1.26.0 linux/amd64

其实系统“软件”中心里也有 Go 的 Snap 版本(见截图),但 APT 更轻量,且与容器内的 Go 版本兼容。

软件中心中 Go 的可用版本
软件中心中 Go 的可用版本

1.4 在 Trae CN 中安装 Go 扩展

打开 Trae CN 的扩展商店,搜索 Go,找到官方扩展(发布者为 golang)并安装。

扩展商店中的 Go 扩展
扩展商店中的 Go 扩展

安装完成后,按 Ctrl+Shift+P,输入 Go: Install/Update Tools,在弹出的列表中勾选 goplsdlvstaticcheck 等常用工具,点击确定。

选择要安装的 Go 工具
选择要安装的 Go 工具

等待安装完成,然后重新加载窗口。

至此,宿主机上的代码编辑环境已经就绪。

在 main.go 中输入 fmt. 应该能看到智能提示。
main.go 中输入 fmt. 应该能看到智能提示。

二、容器中的开发环境

2.1 让容器复用宿主机的 Git 配置

我们希望在容器内执行 git commit 时也能使用宿主机的用户名和邮箱。修改 docker-compose.yml,挂载 ~/.gitconfig

volumes:
  - ./:/code
  - ~/.gitconfig:/root/.gitconfig:ro

重启容器后,进入容器验证:

docker exec -it go-gin-learning sh
git config --global user.name   # 应显示 shuijingwan
git config --global user.email  # 应显示 shuijingwanwq@163.com

2.2 解决 Go 模块下载慢的问题

一开始容器内执行 go mod tidy 总是失败或超时,因为默认代理 goproxy.io 不稳定,而且每次容器重建都会清空缓存。

解决方案有两个:

  • 设置更稳定的代理:go env -w GOPROXY=https://goproxy.cn,direct
  • 持久化模块缓存:将宿主机的目录挂载到容器的 /go/pkg/mod

我采用了后者,在 docker-compose.yml 中添加:

volumes:
  - ~/go/pkg-mod-go-gin-learning:/go/pkg/mod

这样容器内的依赖包会存放在宿主机的一个专用目录中,即使容器删除也不会丢失。再次执行 go mod tidy 时,所有 go: downloading 都成功了。

2.3 运行 Gin 服务

在容器内执行:

go run main.go

看到如下输出说明服务启动成功:

[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080

用浏览器访问 http://localhost:8080/albums,返回 JSON 数据,状态码 200。

浏览器中的请求响应正常
浏览器中的请求响应正常

这时在 Trae CN 的 Docker 扩展面板中,可以右键容器选择 Attach Shell,方便地打开容器终端。

Docker 扩展面板的容器操作菜单
Docker 扩展面板的容器操作菜单

三、总结与标准化展望

当前工作流小结

  1. 宿主机:负责 Git 操作、代码编辑、Go 语言服务器(gopls)。
  2. 容器:负责运行 go rungo test,依赖模块通过挂载的缓存目录持久化。
  3. Trae CN:通过 Go 扩展提供智能补全,通过 Docker 扩展附加到容器终端,做到“编辑在宿主机,运行在容器”。

虽然 Dev Containers 在 Trae CN 上暂时不可用,但通过“宿主机 Go + 容器运行 + 缓存持久化”的组合,我们依然获得了一个高效、可靠的开发环境。
(顺便说一句,.devcontainer/devcontainer.json 我并没有删除,因为团队里其他使用 VS Code 的成员可能还需要它。)

未来可以标准化的点

  • 使用 Makefile 封装常用命令:比如 make runmake testmake tidy,减少手动输入。
  • 集成 Air 热加载:在容器内安装 air,保存文件后自动重启服务,提升开发效率。
  • 将环境配置写成脚本:把 docker-compose.yml 的 volumes 设置、代理配置、依赖安装等写成 setup.sh,方便新成员快速加入。
  • 考虑使用 Remote – SSH:如果未来团队都在同一台服务器上开发,可以改用 SSH 远程开发,彻底统一环境。
]]>
https://www.shuijingwanwq.com/2026/06/08/16544/feed/ 0
Ubuntu 26.04 + Docker 搭建 Go Gin 开发环境(全记录) https://www.shuijingwanwq.com/2026/06/03/15587/ https://www.shuijingwanwq.com/2026/06/03/15587/#respond Wed, 03 Jun 2026 11:44:06 +0000 https://www.shuijingwanwq.com/?p=15587 Post Views: 39

从 Windows WSL2 无缝迁移到 Ubuntu 裸机,基于现有 MySQL/Redis 容器构建 Go 开发环境,并接入自定义网络。

📌 前言

之前一直在 Windows 10 + WSL2 中学习 go-gin-learning 项目,现在换到了全新的 Ubuntu 26.04 系统。希望继续使用 Docker 来隔离开发环境,同时复用已经部署好的 MySQL 8.0 和 Redis 7.2 容器(位于自定义网络 services_dev-network 中)。

本文记录了从零开始配置的全部步骤,包含:

  • 系统工具检查与安装
  • Docker 环境验证
  • 项目代码克隆
  • 编写 Dockerfile 预装 git
  • 编写 docker-compose.yml 接入现有网络
  • 容器内运行 Gin 应用并测试连通性
  • 常见问题与避坑建议

🧰 一、准备工作:检查系统已有工具

在 Ubuntu 中养成“先检查,后安装”的习惯,可以避免重复操作。

1.1 检查 Git

git --version

输出示例(已安装):

git version 2.53.0

若未安装,执行:

sudo apt install git -y

1.2 检查 Docker

docker --version
docker compose version

输出示例

Docker version 29.5.2, build 79eb04c
Docker Compose version v5.1.4

若未安装,请参考 Docker 官方文档 安装。

建议:将当前用户加入 docker 组,避免每次输入 sudo

sudo usermod -aG docker $USER

然后注销并重新登录


🌐 二、确认已有数据库网络环境

之前已经在 ~/docker/services 中通过 docker-compose.yml 运行了 MySQL 8.0 和 Redis 7.2,并创建了自定义网络 services_dev-network

2.1 查看网络

docker network ls | grep dev-network

输出

71f462c6ef91   services_dev-network   bridge    local

2.2 查看数据库容器状态

docker ps --filter "name=mysql80" --filter "name=redis72"

输出

CONTAINER ID   IMAGE       ...   NAMES
9ec38ca384b6   redis:7.2   ...   redis72
4e93fd2e2c3f   mysql:8.0   ...   mysql80

💡 关键信息

  • MySQL 容器名:mysql80,内部端口 3306,宿主机映射端口 3307
  • Redis 容器名:redis72,内部端口 6379
  • 网络名:services_dev-network注意前缀,由 ~/docker/services 目录名派生)

📁 三、创建代码目录并克隆项目

选择一个语义清晰的目录存放项目代码,这里使用 ~/code(全小写,符合 Linux 惯例)。

mkdir -p ~/code
cd ~/code
git clone https://github.com/shuijingwan/go-gin-learning.git
cd go-gin-learning

检查项目结构:

ls -la

🐳 四、编写 Dockerfile(预装 git)

为了不在每次进入容器时手动安装 git,我们创建一个自定义镜像。

创建 Dockerfile

FROM golang:1.26-alpine

# 安装 git(go mod 下载某些依赖时需要)
RUN apk add --no-cache git

# 设置工作目录,与后面的 docker-compose 保持一致
WORKDIR /code

# 保持容器运行(开发模式)
CMD ["tail", "-f", "/dev/null"]

📌 说明

  • 使用 Alpine 版本镜像,体积小
  • apk add --no-cache 不保留缓存,减小镜像大小
  • WORKDIR /code 后续可以省略 docker-compose 中的 working_dir,但为了明确,两边都保留也没问题

⚙️ 五、编写 docker-compose.yml(接入现有网络)

关键点:

  • 使用 build: . 而不是 image: ...
  • 加入外部网络 services_dev-network
  • 传递数据库连接所需的环境变量
  • 删除已过时的 version 字段

最终 docker-compose.yml 内容:

services:
  go:
    build: .
    container_name: go-gin-learning
    working_dir: /code
    volumes:
      - ./:/code
    ports:
      - "8080:8080"
    tty: true
    stdin_open: true
    environment:
      - GOPROXY=https://goproxy.io,direct
      - GOSUMDB=sum.golang.google.cn
      - CGO_ENABLED=0
      # 数据库连接配置(使用现有网络中的服务名)
      - DB_HOST=mysql80
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=mystrongpwd80
      - DB_NAME=go_gin
      - REDIS_HOST=redis72
      - REDIS_PORT=6379
      - REDIS_PASSWORD=myredispass
    networks:
      - services_dev-network

networks:
  services_dev-network:
    external: true

⚠️ 注意

  • DB_PASSWORDREDIS_PASSWORD 要与 ~/docker/services/docker-compose.yml 中的密码完全一致
  • 容器间通信使用内部端口 3306 和 6379,而不是宿主机映射的 3307。

🚀 六、构建并启动开发容器

# 停止并删除旧容器(如果有)
docker compose down

# 构建镜像(--no-cache 确保重新安装 git)
docker compose build --no-cache

# 启动容器(后台运行)
docker compose up -d

查看运行状态:

docker ps --filter "name=go-gin-learning"

🧪 七、进入容器并验证连接

7.1 进入容器

docker exec -it go-gin-learning sh

此时提示符变为 /code #

7.2 验证 git 已安装

git --version

应输出类似 git version 2.52.0

7.3 测试网络连通性

# 安装测试工具(仅调试用,正式镜像可不加)
apk add iputils mysql-client redis

# ping 数据库容器
ping -c 2 mysql80
ping -c 2 redis72

7.4 测试 MySQL 连接(禁用 SSL)

MySQL 8.0 默认要求 SSL,但在容器内部使用 --ssl=0 即可绕过证书验证:

mariadb -h mysql80 -P 3306 -u root -pmystrongpwd80 -e "SHOW DATABASES;" --ssl=0

预期输出:如图1

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

💡 若使用 mysql 命令,请用 --ssl-mode=DISABLED

7.5 测试 Redis 连接

redis-cli -h redis72 -a myredispass PING

预期输出:如图2

预期输出:如图2
PONG

🏃 八、运行 Gin 应用

在容器内执行:

go run main.go

看到如下日志表示成功:如图3

看到如下日志表示成功:如图3
go: downloading github.com/go-playground/locales v0.14.1
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /albums                   --> main.getAlbums (3 handlers)
[GIN-debug] GET    /albums/:id               --> main.getAlbumByID (3 handlers)
[GIN-debug] POST   /albums                   --> main.postAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080
[GIN] 2026/06/03 - 11:19:14 | 404 |  1.219µs |      172.18.0.1 | GET      "/"
[GIN] 2026/06/03 - 11:19:14 | 404 |    778ns |      172.18.0.1 | GET      "/favicon.ico"
[GIN] 2026/06/03 - 11:19:45 | 200 | 147.412µs |      172.18.0.1 | GET      "/albums"


在宿主机测试 API

打开另一个终端,执行:

curl http://localhost:8080/albums

或直接在浏览器访问 http://localhost:8080/albums

📸 浏览器显示 JSON 数据,如图4


🧹 九、常用管理命令

操作命令
启动开发容器cd ~/code/go-gin-learning && docker compose up -d
停止容器docker compose down
重新构建镜像docker compose build --no-cache
查看日志docker compose logs -f
进入容器docker exec -it go-gin-learning sh
重启 MySQL/Redis(若需要)cd ~/docker/services && docker compose restart

⚠️ 十、避坑指南

10.1 容器内 git 缺失导致 go run 失败

现象

unable to resolve git version: exec: "git": executable file not found in $PATH

解决:使用自定义 Dockerfile 安装 git,见第四章。

10.2 MySQL 连接报 TLS/SSL 错误

现象

ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure

解决:在连接命令中添加 --ssl=0(mariadb)或 --ssl-mode=DISABLED(mysql)。

10.3 容器无法访问 MySQL/Redis

原因:未加入正确的网络,或网络名称写错。

解决

  • 确认 docker network ls 中有 services_dev-network
  • 检查 docker-compose.yml 中 networks 名称是否完全一致(包括下划线)
  • 在容器内 ping mysql80 测试

10.4 宿主机无法访问 8080 端口

原因:Gin 监听的是 127.0.0.1:8080 而不是 0.0.0.0:8080,导致端口映射失效。

解决:在代码中设置 router.Run(":8080")router.Run("0.0.0.0:8080")


📝 十一、总结

通过本次实践,我实现了:

  1. ✅ 在 Ubuntu 26.04 上搭建完整的 Go 开发环境
  2. ✅ 利用已有的 MySQL/Redis 容器(独立部署)和自定义网络
  3. ✅ 构建带 git 的 Go 1.26 Alpine 开发镜像
  4. ✅ 编写 docker-compose.yml 挂载代码并接入外部网络
  5. ✅ 在容器内成功运行 Gin 应用并验证数据库连通性

这种“数据库与代码分离容器”的模式非常适合多项目共用基础设施,既节省资源,又保持环境一致性。

下一步,便可以:

  • main.go 中编写连接 MySQL/Redis 的业务逻辑
  • 使用任意代码编辑器(如 VS Code、Sublime 等)直接编辑项目文件,保存后容器内实时同步
  • docker-compose.yml 加入版本控制,与队友共享环境配置

🔗 参考资料

]]>
https://www.shuijingwanwq.com/2026/06/03/15587/feed/ 0
当 where 中的字段包含 shipping_at_gmt 与 shipping_status|shipping_type|operated_source 时,则强制使用组合索引,否则不强制使用组合索引 https://www.shuijingwanwq.com/2026/05/14/10479/ https://www.shuijingwanwq.com/2026/05/14/10479/#respond Thu, 14 May 2026 03:09:40 +0000 https://www.shuijingwanwq.com/?p=10479 Post Views: 128

1、参考:列表接口响应超时的优化

2、参考:在 Spatie\QueryBuilder 中,查询 SQL 强制使用索引

3、但是,当 where 中的字段包含 shipping_at_gmt 与 shipping_status、operator_user_id 时,强制使用组合索引后,是一个负优化了。 SQL 执行耗时整理如下

#################################
# 耗时 24 秒
select
  count(*) as aggregate
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (20000463)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:27:52'
  and `shipping_at_gmt` <= '2024-10-23 03:27:52';

# 耗时 29 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (20000463)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:27:52'
  and `shipping_at_gmt` <= '2024-10-23 03:27:52'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 耗时 1.5 秒
select
  count(*) as aggregate
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (20000463)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:27:52'
  and `shipping_at_gmt` <= '2024-10-23 03:27:52';

# 耗时 1.4 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (20000463)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:27:52'
  and `shipping_at_gmt` <= '2024-10-23 03:27:52'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 以上的 4 条 SQL,强制使用组合索引属于负优化。原因在于 `operator_user_id` in (20000463) 的数据量(2338)很小

#################################
# 耗时 27 秒
select
  count(*) as aggregate
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (20000250)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:43:11'
  and `shipping_at_gmt` <= '2024-10-23 03:43:11';

# 耗时 32 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (20000250)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:43:11'
  and `shipping_at_gmt` <= '2024-10-23 03:43:11'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 耗时 102 秒
select
  count(*) as aggregate
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (20000250)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:43:11'
  and `shipping_at_gmt` <= '2024-10-23 03:43:11';

# 耗时 67 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (20000250)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 03:43:11'
  and `shipping_at_gmt` <= '2024-10-23 03:43:11'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 以上的 4 条 SQL,强制使用组合索引属于正优化。原因在于 `operator_user_id` in (20000463) 的数据量(18060318)很大

#################################
# 耗时 24 秒
select
  count(*) as aggregate
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:28:36'
  and `shipping_at_gmt` <= '2024-10-23 07:28:36';

# 耗时 30 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:28:36'
  and `shipping_at_gmt` <= '2024-10-23 07:28:36'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 耗时 7 秒
select
  count(*) as aggregate
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:28:36'
  and `shipping_at_gmt` <= '2024-10-23 07:28:36';

# 耗时 6 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:28:36'
  and `shipping_at_gmt` <= '2024-10-23 07:28:36'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 以上的 4 条 SQL,强制使用组合索引属于负优化。原因在于 `operator_user_id` in (4) 的数据量(323230)不大

#################################
# 耗时 24 秒
select
  count(*) as aggregate
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`logistic_channel_id` in (2775)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:40:53'
  and `shipping_at_gmt` <= '2024-10-23 07:40:53';

# 耗时 29 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`logistic_channel_id` in (2775)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:40:53'
  and `shipping_at_gmt` <= '2024-10-23 07:40:53'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 耗时 11 秒
select
  count(*) as aggregate
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`logistic_channel_id` in (2775)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:40:53'
  and `shipping_at_gmt` <= '2024-10-23 07:40:53';

# 耗时 10 秒
select
  `order_shipping_logs`.*
from
  order_shipping_logs
where
  `order_shipping_logs`.`operator_user_id` in (4)
  and `order_shipping_logs`.`logistic_channel_id` in (2775)
  and `order_shipping_logs`.`shipping_type` = 1
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-23 07:40:53'
  and `shipping_at_gmt` <= '2024-10-23 07:40:53'
order by
  `operated_at_gmt` desc
limit
  100 offset 0;

# 以上的 4 条 SQL,强制使用组合索引属于负优化。原因在于 `operator_user_id` in (4) 的数据量(323230)不大

4、初步总结出规律,当 where 中的字段包含 shipping_at_gmt 与 shipping_status|shipping_type|operated_source 时,则强制使用组合索引,否则不强制使用组合索引。针对 `operator_user_id` in (20000463) 的数据量(18060318)很大 的情况不用考虑,因为生产环境中不存在如此极端的情况。最终实现如下

$whereColumns = array_unique(Arr::pluck($builder->getQuery()->wheres, 'column'));
$filteredWhereColumns = Arr::where($whereColumns, function ($value, $key) {
	return $value != 'shipping_at_gmt';
});
// 当筛选字段包含 交运时间 与 交运状态|交运类型|操作来源 时,强制使用组合索引 order_shipping_logs_sag_os_ss_st_index
if (in_array('shipping_at_gmt', $whereColumns) && !empty($filteredWhereColumns) && empty(array_diff($filteredWhereColumns, ['order_shipping_logs.shipping_status', 'order_shipping_logs.shipping_type', 'order_shipping_logs.operated_source']))) {
	$builder->from(DB::raw('order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)'));
}

5、最终实现的规则,打印调试结果符合预期

Array
(
    [0] => order_shipping_logs.operator_user_id
    [1] => order_shipping_logs.logistic_channel_id
    [2] => order_shipping_logs.shipping_type
    [3] => order_shipping_logs.shipping_status
    [4] => shipping_at_gmt
)
0 不强制使用组合索引

Array
(
    [0] => order_shipping_logs.shipping_type
    [1] => order_shipping_logs.shipping_status
    [2] => shipping_at_gmt
)
1 强制使用组合索引

Array
(
    [0] => order_shipping_logs.operated_source
    [1] => order_shipping_logs.shipping_type
    [2] => order_shipping_logs.shipping_status
    [3] => shipping_at_gmt
)
1 强制使用组合索引

Array
(
    [0] => order_shipping_logs.shipping_type
    [1] => shipping_at_gmt
)
1 强制使用组合索引

Array
(
    [0] => shipping_at_gmt
)
0 不强制使用组合索引

Array
(
    [0] => order_shipping_logs.operator_user_id
    [1] => shipping_at_gmt
)
0 不强制使用组合索引

Array
(
    [0] => order_shipping_logs.shipping_type
    [1] => operated_at_gmt
)
0 不强制使用组合索引

]]>
https://www.shuijingwanwq.com/2026/05/14/10479/feed/ 0
在 Spatie\QueryBuilder 中,查询 SQL 强制使用索引 https://www.shuijingwanwq.com/2026/04/25/9583/ https://www.shuijingwanwq.com/2026/04/25/9583/#comments Sat, 25 Apr 2026 03:56:41 +0000 https://www.shuijingwanwq.com/?p=9583 Post Views: 214

1、参考:列表接口响应超时的优化 。在 Spatie\QueryBuilder 中,查询 SQL 强制使用索引 。实现如下

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Arr;

        // 当筛选字段包含:交运状态、交运类型、操作来源 时,强制使用组合索引 order_shipping_logs_sag_os_ss_st_index
        if (Arr::hasAny($criteria['filter'], ['shipping_status','shipping_type', 'operated_source'])) {
            Log::info(
                '$criteria1',
                $criteria['filter']
            );
            $builder->from(DB::raw('order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)'));
        }

2、生成的 SQL 如下,符合预期。如图1

生成的 SQL 如下,符合预期
select
  count(*) as aggregate
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`shipping_type` = 2
  and `shipping_at_gmt` >= '2024-07-22 03:39:54'
  and `shipping_at_gmt` <= '2024-10-22 03:39:54';


select
  count(*) as aggregate
from
  order_shipping_logs FORCE INDEX (order_shipping_logs_sag_os_ss_st_index)
where
  `order_shipping_logs`.`operated_source` in (3)
  and `order_shipping_logs`.`shipping_type` = 2
  and `order_shipping_logs`.`shipping_status` = 3
  and `shipping_at_gmt` >= '2024-07-22 03:47:16'
  and `shipping_at_gmt` <= '2024-10-22 03:47:16'
]]>
https://www.shuijingwanwq.com/2026/04/25/9583/feed/ 1
WSL-Ubuntu 下 Docker 搭建 Hyperf:从启动失败到最佳开发结构 https://www.shuijingwanwq.com/2026/04/08/9440/ https://www.shuijingwanwq.com/2026/04/08/9440/#respond Wed, 08 Apr 2026 08:41:34 +0000 https://www.shuijingwanwq.com/?p=9440 Post Views: 204

1、参考:在 WSL-Ubuntu 中 Docker 下安装 Hyperf。现在启动失败:[ERROR] RedisException: Connection refused。如图1

参考:在 WSL-Ubuntu 中 Docker 下安装 Hyperf。现在启动失败:[ERROR] RedisException: Connection refused。


[ERROR] RedisException: Connection refused in /data/project/hyperf-skeleton/vendor/hyperf/redis/src/RedisConnection.php:318
Stack trace:
<h1>0 /data/project/hyperf-skeleton/vendor/hyperf/redis/src/RedisConnection.php(318): Redis-&gt;connect()</h1>
<h1>1 /data/project/hyperf-skeleton/vendor/hyperf/redis/src/RedisConnection.php(129): Hyperf\Redis\RedisConnection-&gt;createRedis()</h1>
<h1>2 /data/project/hyperf-skeleton/vendor/hyperf/redis/src/RedisConnection.php(83): Hyperf\Redis\RedisConnection-&gt;reconnect()</h1>
<h1>3 /data/project/hyperf-skeleton/vendor/hyperf/redis/src/Pool/RedisPool.php(58): Hyperf\Redis\RedisConnection-&gt;__construct()</h1>
<h1>4 /data/project/hyperf-skeleton/vendor/hyperf/pool/src/Pool.php(160): Hyperf\Redis\Pool\RedisPool-&gt;createConnection()</h1>
<h1>5 /data/project/hyperf-skeleton/vendor/hyperf/pool/src/Pool.php(45): Hyperf\Pool\Pool-&gt;getConnection()</h1>
<h1>6 /data/project/hyperf-skeleton/vendor/hyperf/redis/src/Redis.php(126): Hyperf\Pool\Pool-&gt;get()</h1>
<h1>7 /data/project/hyperf-skeleton/vendor/hyperf/redis/src/Redis.php(40): Hyperf\Redis\Redis-&gt;getConnection()</h1>
<h1>8 /data/project/hyperf-skeleton/vendor/hyperf/async-queue/src/Driver/RedisDriver.php(188): Hyperf\Redis\Redis-&gt;__call()</h1>
<h1>9 /data/project/hyperf-skeleton/vendor/hyperf/async-queue/src/Driver/RedisDriver.php(80): Hyperf\AsyncQueue\Driver\RedisDriver-&gt;move()</h1>
<h1>10 /data/project/hyperf-skeleton/vendor/hyperf/async-queue/src/Driver/Driver.php(68): Hyperf\AsyncQueue\Driver\RedisDriver-&gt;pop()</h1>
<h1>11 /data/project/hyperf-skeleton/vendor/hyperf/async-queue/src/Process/ConsumerProcess.php(51): Hyperf\AsyncQueue\Driver\Driver-&gt;consume()</h1>
<h1>12 /data/project/hyperf-skeleton/vendor/hyperf/process/src/AbstractProcess.php(101): Hyperf\AsyncQueue\Process\ConsumerProcess-&gt;handle()</h1>
<h1>13 [internal function]: Hyperf\Process\AbstractProcess-&gt;Hyperf\Process{closure}()</h1>
<h1>14 {main}</h1>


2、HyPerf 连接 Redis 失败,大概率是以下三者之一导致(Redis 没启动 / 地址写错 / 端口不通)。我现在是本地开发环境,最终决定架构如下:

WSL(开发环境)

├── dev-mysql(共用)
├── dev-redis(共用)

├── hyperf 项目
├── laravel 项目
├── 其他 PHP 项目

3、创建一个公共网络



ubuntu@DESKTOP-H4MGQIU:~$ docker network create dev-net
4b1b11f4edbeb6ea2dc17600e5366ddffa4e1fd1eec9d0c6da546764d9fe919c


4、启动 Redis



ubuntu@DESKTOP-H4MGQIU:~$ docker network create dev-net
4b1b11f4edbeb6ea2dc17600e5366ddffa4e1fd1eec9d0c6da546764d9fe919c
ubuntu@DESKTOP-H4MGQIU:~$ docker run -d \
--name dev-redis \
--network dev-net \
-p 6379:6379 \
redis:alpine
Unable to find image 'redis:alpine' locally
alpine: Pulling from library/redis
911c6948605d: Pull complete
80ad90552b45: Pull complete
4f4fb700ef54: Pull complete
794ebd49c5d4: Pull complete
e14f845a52ff: Pull complete
8b960ad4ebe4: Pull complete
589002ba0eae: Pull complete
1eff8ef023e3: Download complete
3130250ed578: Download complete
Digest: sha256:2afba59292f25f5d1af200496db41bea2c6c816b059f57ae74703a50a03a27d0
Status: Downloaded newer image for redis:alpine
01c8cedc5e855b78766d3232fe7df6b55ca822256c94ece0cd510e1bcab246ef


5、启动 MySQL 8



ubuntu@DESKTOP-H4MGQIU:~$ docker run -d \
--name dev-mysql8 \
--network dev-net \
-e MYSQL_ROOT_PASSWORD=root \
-p 3306:3306 \
mysql:8
Unable to find image 'mysql:8' locally
8: Pulling from library/mysql
91c980086743: Pull complete
eba8eed90d75: Pull complete
d4c7048d1cf1: Pull complete
50200b0c89a3: Pull complete
3cd28adefbd9: Pull complete
03f8ae82fc56: Pull complete
43105b2d4e4a: Pull complete
4d14d7bf02a4: Pull complete
1a7d4892b7d2: Pull complete
68420c358694: Pull complete
09ed159fbec4: Download complete
0ad5e3bd9c41: Download complete
Digest: sha256:da906917ca4ace3ba55538b7c2ee97a9bc865ef14a4b6920b021f0249d603f3d
Status: Downloaded newer image for mysql:8
232e8abe63d581fcbf5effab51e84eb4bb046255c8851635e58cfe1d8a85c7e6


6、让 Hyperf 加入这个网络,验证是否成功,验证是否成功。符合预期。如图2

让 Hyperf 加入这个网络,验证是否成功,验证是否成功


ubuntu@DESKTOP-H4MGQIU:~$ docker network connect dev-net hyperf
ubuntu@DESKTOP-H4MGQIU:~$ docker inspect hyperf
[
{
"Id": "9232c1fa90d72165f31c1e1adc4fdc897a665a1e9e580242af4a342765f7154a",
"Created": "2026-03-19T11:07:00.928914099Z",
"Path": "/bin/sh",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 2188,
"ExitCode": 0,
"Error": "",
"StartedAt": "2026-03-23T09:23:00.14362394Z",
"FinishedAt": "2026-03-23T09:16:50.3053747Z"
},
"Image": "sha256:3cbda08f3892507d760d31bba8b68aeb4b916333055bbf05cf46ea18c67fc935",
"ResolvConfPath": "/var/lib/docker/containers/9232c1fa90d72165f31c1e1adc4fdc897a665a1e9e580242af4a342765f7154a/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/9232c1fa90d72165f31c1e1adc4fdc897a665a1e9e580242af4a342765f7154a/hostname",
"HostsPath": "/var/lib/docker/containers/9232c1fa90d72165f31c1e1adc4fdc897a665a1e9e580242af4a342765f7154a/hosts",
"LogPath": "/var/lib/docker/containers/9232c1fa90d72165f31c1e1adc4fdc897a665a1e9e580242af4a342765f7154a/9232c1fa90d72165f31c1e1adc4fdc897a665a1e9e580242af4a342765f7154a-json.log",
"Name": "/hyperf",
"RestartCount": 0,
"Driver": "overlayfs",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": [
"/home/ubuntu/wwwroot/hyperf-skeleton:/data/project"
],
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "bridge",
"PortBindings": {
"9501/tcp": [
{
"HostIp": "",
"HostPort": "9501"
}
]
},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"ConsoleSize": [
49,
188
],
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "private",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": true,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": [
"label=disable"
],
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": [],
"BlkioDeviceWriteBps": [],
"BlkioDeviceReadIOps": [],
"BlkioDeviceWriteIOps": [],
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": null,
"PidsLimit": null,
"Ulimits": [],
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": null,
"ReadonlyPaths": null
},
"Storage": {
"RootFS": {
"Snapshot": {
"Name": "overlayfs"
}
}
},
"Mounts": [
{
"Type": "bind",
"Source": "/home/ubuntu/wwwroot/hyperf-skeleton",
"Destination": "/data/project",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
"Config": {
"Hostname": "9232c1fa90d7",
"Domainname": "",
"User": "root",
"AttachStdin": true,
"AttachStdout": true,
"AttachStderr": true,
"ExposedPorts": {
"9501/tcp": {}
},
"Tty": true,
"OpenStdin": true,
"StdinOnce": true,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"SW_VERSION=v6.1.7",
"COMPOSER_VERSION=2.9.4",
"COMPOSER_ALLOW_SUPERUSER=1",
"PHPIZE_DEPS=autoconf dpkg-dev dpkg file g++ gcc libc-dev make php81-dev php81-pear pkgconf re2c pcre-dev pcre2-dev zlib-dev libtool automake libaio-dev openssl-dev curl-dev"
],
"Cmd": null,
"Image": "hyperf/hyperf:8.1-alpine-v3.18-swoole",
"Volumes": null,
"WorkingDir": "/data/project",
"Entrypoint": [
"/bin/sh"
],
"Labels": {
"license": "MIT",
"maintainer": "Hyperf Developers <a href="&#109;&#x61;&#x69;&#x6c;&#x74;&#111;&#x3a;&#103;&#x72;&#111;&#x75;&#112;&#x40;&#104;&#121;&#112;&#101;r&#x66;&#x2e;&#105;&#x6f;">&#103;&#x72;&#x6f;&#117;&#112;&#x40;&#104;&#x79;p&#101;&#114;&#102;&#46;&#x69;&#111;</a>",
"version": "1.0"
}
},
"NetworkSettings": {
"SandboxID": "31c7e20513fa9eb5bc38e9e5225b797b4cd2391ed9f0a9700e4f3b3460190dfd",
"SandboxKey": "/var/run/docker/netns/31c7e20513fa",
"Ports": {
"9501/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "9501"
},
{
"HostIp": "::",
"HostPort": "9501"
}
]
},
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"DriverOpts": null,
"GwPriority": 0,
"NetworkID": "b874ea6a38fefb11a4bf500d54ffe2365d3e22de5fc57610d7379bbb177b6be5",
"EndpointID": "1ecfdc26abb9841bca3f3f158d0d2fae70bad4db686d7837ed1fada1a177da55",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"MacAddress": "16:1e:f4:8e:99:5f",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": null
},
"dev-net": {
"IPAMConfig": {},
"Links": null,
"Aliases": [],
"DriverOpts": {},
"GwPriority": 0,
"NetworkID": "4b1b11f4edbeb6ea2dc17600e5366ddffa4e1fd1eec9d0c6da546764d9fe919c",
"EndpointID": "c03c1b28859f6e6dd736ce2c891e33226a428a9e8ca5232971626df2dfd9b907",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.5",
"MacAddress": "ce:80:6d:68:f9:90",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DNSNames": [
"hyperf",
"9232c1fa90d7"
]
}
}
},
"ImageManifestDescriptor": {
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:2852fa7c75e5f7c8b36dbc0ffaff8c6d7f1227f8454dbb0ab3cbccecb617da9c",
"size": 1055,
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
}
]
ubuntu@DESKTOP-H4MGQIU:~$ docker exec -it hyperf sh
/data/project # ping dev-redis
PING dev-redis (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.393 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.203 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.360 ms
64 bytes from 172.18.0.2: seq=3 ttl=64 time=0.136 ms
64 bytes from 172.18.0.2: seq=4 ttl=64 time=0.104 ms
64 bytes from 172.18.0.2: seq=5 ttl=64 time=0.250 ms
64 bytes from 172.18.0.2: seq=6 ttl=64 time=0.080 ms
64 bytes from 172.18.0.2: seq=7 ttl=64 time=0.077 ms
64 bytes from 172.18.0.2: seq=8 ttl=64 time=0.110 ms
64 bytes from 172.18.0.2: seq=9 ttl=64 time=0.080 ms
^C
--- dev-redis ping statistics ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 0.077/0.179/0.393 ms
/data/project # ping dev-mysql8
PING dev-mysql8 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.392 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.338 ms
64 bytes from 172.18.0.3: seq=2 ttl=64 time=0.230 ms
64 bytes from 172.18.0.3: seq=3 ttl=64 time=0.355 ms
^C
--- dev-mysql8 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.230/0.328/0.392 ms
/data/project #


7、修改 Hyperf 配置,让它连到 Redis / MySQL。修改 Redis 配置,打开文件:.env ,修改相关变量。如图3

修改 Hyperf 配置,让它连到 Redis / MySQL。修改 Redis 配置,打开文件:.env ,修改相关变量。

8、再次启动 Hyperf 服务,启动成功。如图4

再次启动 Hyperf 服务,启动成功


/data/project/hyperf-skeleton # php bin/hyperf.php start
[DEBUG] [command] Commands registered by Hyperf\Command\Listener\RegisterCommandListener
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Command\Listener\RegisterCommandListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\Config\Listener\RegisterPropertyHandlerListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\DbConnection\Listener\RegisterConnectionResolverListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\ExceptionHandler\Listener\ExceptionHandlerListener listener.
[DEBUG] Event Hyperf\Framework\Event\BootApplication handled by Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler listener.
[DEBUG] Event Hyperf\Framework\Event\BeforeMainServerStart handled by Hyperf\Process\Listener\BootProcessListener listener.
[INFO] Worker#1 started.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\AfterWorkerStartListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[INFO] Worker#2 started.
[INFO] Worker#3 started.
[INFO] Worker#0 started.
[INFO] HTTP Server listening at 0.0.0.0:9501
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\AfterWorkerStartListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\AfterWorkerStartListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\AfterWorkerStartListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[DEBUG] Event Hyperf\Framework\Event\OnManagerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[DEBUG] Event Hyperf\Framework\Event\AfterWorkerStart handled by Hyperf\Server\Listener\InitProcessTitleListener listener.
[INFO] Process[queue.default.0] start.
[DEBUG] Event Hyperf\Process\Event\BeforeProcessHandle handled by Hyperf\Process\Listener\LogBeforeProcessStartListener listener.
[DEBUG] Event Hyperf\Process\Event\BeforeProcessHandle handled by Hyperf\Server\Listener\InitProcessTitleListener listener.



9、HTTP 服务已经启动,可以通过 http://localhost:9501 访问。如图5

HTTP 服务已经启动,可以通过 http://localhost:9501 访问
]]>
https://www.shuijingwanwq.com/2026/04/08/9440/feed/ 0
在 WSL-Ubuntu 中 Docker 下安装 Hyperf https://www.shuijingwanwq.com/2026/04/07/9435/ https://www.shuijingwanwq.com/2026/04/07/9435/#comments Tue, 07 Apr 2026 09:50:54 +0000 https://www.shuijingwanwq.com/?p=9435 Post Views: 297

1、第一步:在 WSL 里创建本地项目目录(比如 ~/wwwroot/hyperf-skeleton)



ubuntu@DESKTOP-H4MGQIU:~/wwwroot$ mkdir -p ~/wwwroot/hyperf-skeleton


2、修改挂载目录,适配本地目录的路径。参考:Hyperf 官方文档 Docker 下开发



ubuntu@DESKTOP-H4MGQIU:~/wwwroot$ docker run --name hyperf \
-v ~/wwwroot/hyperf-skeleton:/data/project \
-w /data/project \
-p 9501:9501 -it \
--privileged -u root \
--entrypoint /bin/sh \
hyperf/hyperf:8.1-alpine-v3.18-swoole
Unable to find image 'hyperf/hyperf:8.1-alpine-v3.18-swoole' locally
8.1-alpine-v3.18-swoole: Pulling from hyperf/hyperf
5715dfbf9dd7: Pull complete
bfa571bff938: Pull complete
58de83433912: Pull complete
44cf07d57ee4: Pull complete
471a4d85c85c: Download complete
Digest: sha256:3cbda08f3892507d760d31bba8b68aeb4b916333055bbf05cf46ea18c67fc935
Status: Downloaded newer image for hyperf/hyperf:8.1-alpine-v3.18-swoole
/data/project #


3、在容器内创建项目 ,创建项目失败,报错:Your requirements could not be resolved to an installable set of packages.。如图1

在容器内创建项目 ,创建项目失败,报错:Your requirements could not be resolved to an installable set of packages.


/data/project # composer create-project hyperf/hyperf-skeleton
Creating a "hyperf/hyperf-skeleton" project at "./hyperf-skeleton"
Installing hyperf/hyperf-skeleton (v3.1.3)
<ul>
 	<li>Downloading hyperf/hyperf-skeleton (v3.1.3)</li>
 	<li>Installing hyperf/hyperf-skeleton (v3.1.3): Extracting archive
Created project in /data/project/hyperf-skeleton</li>
</ul>
<blockquote>@php -r "file_exists('.env') || copy('.env.example', '.env');"
Installer\Script::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies</blockquote>
What time zone do you want to setup ?
[n] Default time zone for php.ini
Make your selection or type a time zone name, like Asia/Shanghai (n):
Asia/Shanghai

Do you want to use Database (MySQL Client) ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y
<ul>
 	<li>Adding package hyperf/database (~3.1.0)</li>
 	<li>Adding package hyperf/db-connection (~3.1.0)

Do you want to use Redis Client ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y</li>
 	<li>Adding package hyperf/redis (~3.1.0)</li>
 	<li>Copying config/autoload/redis.php

Which RPC protocol do you want to use ?
[1] JSON RPC with Service Governance
[2] JSON RPC
[3] gRPC
[n] None of the above
Make your selection or type a composer package name and version (n): n

Which config center do you want to use ?
[1] Apollo
[2] Aliyun ACM
[3] ETCD
[4] Nacos
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/constants component ?
[y] yes
[n] None of the above
Make your selection (n): y</li>
 	<li>Adding package hyperf/constants (~3.1.0)</li>
 	<li>Copying app/Constants/ErrorCode.php</li>
 	<li>Copying app/Exception/BusinessException.php

Do you want to use hyperf/async-queue component ? (A simple redis queue component)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): y</li>
 	<li>Adding package hyperf/async-queue (~3.1.0)</li>
 	<li>Copying config/autoload/async_queue.php</li>
 	<li>Copying app/Process/AsyncQueueConsumer.php</li>
 	<li>Copying app/Listener/QueueHandleListener.php</li>
 	<li>Copying config/autoload/redis.php

Do you want to use hyperf/amqp component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/model-cache component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/elasticsearch component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/tracer component ? (An open tracing protocol component, adapte with Zipkin etc.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use pestphp/pest component ? (Pest is a testing framework with a focus on simplicity,
meticulously designed to bring back the joy of testing in PHP.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): y</li>
 	<li>Adding package pestphp/pest (^2.34)</li>
 	<li>Copying test/Feature/ExampleTest.php</li>
 	<li>Copying test/Unit/ExampleTest.php</li>
 	<li>Copying test/Pest.php</li>
 	<li>Copying test/TestCase.php
Remove installer
Removing composer.lock from .gitignore
Removing Expressive installer classes, configuration, tests and docs
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

Problem 1
<ul>
 	<li>Root composer.json requires pestphp/pest ^2.34 -&gt; satisfiable by pestphp/pest[v2.34.0, …, 2.x-dev].</li>
 	<li>brianium/paratest[v7.9.0, …, v7.11.2] require php ~8.3.0 || ~8.4.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.12.0, …, 7.x-dev] require php ~8.3.0 || ~8.4.0 || ~8.5.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.4.3, …, v7.5.7] require php ~8.2.0 || ~8.3.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.4.8, …, v7.8.4] require php ~8.2.0 || ~8.3.0 || ~8.4.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.8.5, …, 7.8.x-dev] require php ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>pestphp/pest v2.34.0 conflicts with phpunit/phpunit 10.5.63.</li>
 	<li>pestphp/pest v2.34.0 conflicts with phpunit/phpunit 10.5.x-dev.</li>
 	<li>pestphp/pest v2.34.0 requires phpunit/phpunit ^10.5.10 -&gt; satisfiable by phpunit/phpunit[10.5.62, 10.5.63, 10.5.x-dev].</li>
 	<li>pestphp/pest v2.34.3 requires brianium/paratest ^7.4.3 -&gt; satisfiable by brianium/paratest[v7.4.3, …, 7.x-dev].</li>
 	<li>pestphp/pest[v2.36.1, …, 2.x-dev] require php ^8.2.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.1 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.2 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.4 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.5 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.6 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.9 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.35.0 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.35.1 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.36.0 (conflict analysis result)</li>
</ul>
</li>
</ul>
/data/project #


4、报错核心原因如下:
PHP 版本不匹配:容器里的 PHP 是 8.1.27,但 Pest(测试框架)的新版本要求 PHP ≥ 8.2,我的 PHP 8.1 满足不了;
依赖连锁冲突:Pest 依赖的 brianium/paratest、phpunit/phpunit 等组件,也要求更高版本的 PHP,最终导致 Composer 无法解析出一套能安装的依赖包。

5、最快解决方法(放弃安装 Pest,先跑通项目),退出当前失败的安装流程:重新执行创建项目命令(跳过 Pest),选择 n。提示: Project directory “/data/project/hyperf-skeleton” is not empty.。在容器的 /data/project 目录下,执行清空命令:rm -rf ./* ./.??* 。如图2

最快解决方法(放弃安装 Pest,先跑通项目)


/data/project # composer create-project hyperf/hyperf-skeleton
Creating a "hyperf/hyperf-skeleton" project at "./hyperf-skeleton"

In CreateProjectCommand.php line 368:

Project directory "/data/project/hyperf-skeleton" is not empty.

create-project [-s|--stability STABILITY] [--prefer-source] [--prefer-dist] [--prefer-install PREFER-INSTALL] [--repository REPOSITORY] [--repository-url REPOSITORY-URL] [--add-repository] [--dev] [--no-dev] [--no-custom-installers] [--no-scripts] [--no-progress] [--no-secure-http] [--keep-vcs] [--remove-vcs] [--no-install] [--no-audit] [--audit-format AUDIT-FORMAT] [--no-security-blocking] [--ignore-platform-req IGNORE-PLATFORM-REQ] [--ignore-platform-reqs] [--ask] [--] [<package> [<directory> [<version>]]]



6、在容器内创建项目 在 遇见 pestphp/pest 时,选择 n,创建项目成功。



/data/project # composer create-project hyperf/hyperf-skeleton
Creating a "hyperf/hyperf-skeleton" project at "./hyperf-skeleton"
Installing hyperf/hyperf-skeleton (v3.1.3)
<ul>
 	<li>Installing hyperf/hyperf-skeleton (v3.1.3): Extracting archive
Created project in /data/project/hyperf-skeleton</li>
</ul>
<blockquote>@php -r "file_exists('.env') || copy('.env.example', '.env');"
Installer\Script::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies</blockquote>
What time zone do you want to setup ?
[n] Default time zone for php.ini
Make your selection or type a time zone name, like Asia/Shanghai (n):
Asia/Shanghai

Do you want to use Database (MySQL Client) ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y
<ul>
 	<li>Adding package hyperf/database (~3.1.0)</li>
 	<li>Adding package hyperf/db-connection (~3.1.0)

Do you want to use Redis Client ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y</li>
 	<li>Adding package hyperf/redis (~3.1.0)</li>
 	<li>Copying config/autoload/redis.php

Which RPC protocol do you want to use ?
[1] JSON RPC with Service Governance
[2] JSON RPC
[3] gRPC
[n] None of the above
Make your selection or type a composer package name and version (n): n

Which config center do you want to use ?
[1] Apollo
[2] Aliyun ACM
[3] ETCD
[4] Nacos
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/constants component ?
[y] yes
[n] None of the above
Make your selection (n): y</li>
 	<li>Adding package hyperf/constants (~3.1.0)</li>
 	<li>Copying app/Constants/ErrorCode.php</li>
 	<li>Copying app/Exception/BusinessException.php

Do you want to use hyperf/async-queue component ? (A simple redis queue component)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): y</li>
 	<li>Adding package hyperf/async-queue (~3.1.0)</li>
 	<li>Copying config/autoload/async_queue.php</li>
 	<li>Copying app/Process/AsyncQueueConsumer.php</li>
 	<li>Copying app/Listener/QueueHandleListener.php</li>
 	<li>Copying config/autoload/redis.php

Do you want to use hyperf/amqp component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/model-cache component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/elasticsearch component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/tracer component ? (An open tracing protocol component, adapte with Zipkin etc.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use pestphp/pest component ? (Pest is a testing framework with a focus on simplicity,
meticulously designed to bring back the joy of testing in PHP.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n
Remove installer
Removing composer.lock from .gitignore
Removing Expressive installer classes, configuration, tests and docs
Loading composer repositories with package information
Updating dependencies
Lock file operations: 135 installs, 0 updates, 0 removals</li>
 	<li>Locking carbonphp/carbon-doctrine-types (3.2.0)</li>
 	<li>Locking clue/ndjson-react (v1.3.0)</li>
 	<li>Locking composer/pcre (3.3.2)</li>
 	<li>Locking composer/semver (3.4.4)</li>
 	<li>Locking composer/xdebug-handler (3.0.5)</li>
 	<li>Locking doctrine/inflector (2.1.0)</li>
 	<li>Locking doctrine/instantiator (1.5.0)</li>
 	<li>Locking evenement/evenement (v3.0.2)</li>
 	<li>Locking fidry/cpu-core-counter (1.3.0)</li>
 	<li>Locking fig/http-message-util (1.1.5)</li>
 	<li>Locking friendsofphp/php-cs-fixer (v3.94.2)</li>
 	<li>Locking graham-campbell/result-type (v1.1.4)</li>
 	<li>Locking guzzlehttp/guzzle (7.10.0)</li>
 	<li>Locking guzzlehttp/promises (2.3.0)</li>
 	<li>Locking guzzlehttp/psr7 (2.9.0)</li>
 	<li>Locking hamcrest/hamcrest-php (v2.1.1)</li>
 	<li>Locking hyperf/async-queue (v3.1.64)</li>
 	<li>Locking hyperf/cache (v3.1.67)</li>
 	<li>Locking hyperf/code-parser (v3.1.63)</li>
 	<li>Locking hyperf/codec (v3.1.63)</li>
 	<li>Locking hyperf/collection (v3.1.64)</li>
 	<li>Locking hyperf/command (v3.1.64)</li>
 	<li>Locking hyperf/conditionable (v3.1.63)</li>
 	<li>Locking hyperf/config (v3.1.63)</li>
 	<li>Locking hyperf/constants (v3.1.63)</li>
 	<li>Locking hyperf/context (v3.1.63)</li>
 	<li>Locking hyperf/contract (v3.1.63)</li>
 	<li>Locking hyperf/coordinator (v3.1.63)</li>
 	<li>Locking hyperf/coroutine (v3.1.65)</li>
 	<li>Locking hyperf/database (v3.1.67)</li>
 	<li>Locking hyperf/db-connection (v3.1.66)</li>
 	<li>Locking hyperf/devtool (v3.1.66)</li>
 	<li>Locking hyperf/di (v3.1.67)</li>
 	<li>Locking hyperf/dispatcher (v3.1.63)</li>
 	<li>Locking hyperf/engine (v2.15.0)</li>
 	<li>Locking hyperf/engine-contract (v1.14.0)</li>
 	<li>Locking hyperf/event (v3.1.63)</li>
 	<li>Locking hyperf/exception-handler (v3.1.63)</li>
 	<li>Locking hyperf/framework (v3.1.63)</li>
 	<li>Locking hyperf/guzzle (v3.1.66)</li>
 	<li>Locking hyperf/http-message (v3.1.65)</li>
 	<li>Locking hyperf/http-server (v3.1.65)</li>
 	<li>Locking hyperf/laminas-mime (v3.0.0)</li>
 	<li>Locking hyperf/logger (v3.1.63)</li>
 	<li>Locking hyperf/macroable (v3.1.63)</li>
 	<li>Locking hyperf/memory (v3.1.63)</li>
 	<li>Locking hyperf/model-listener (v3.1.63)</li>
 	<li>Locking hyperf/pipeline (v3.1.63)</li>
 	<li>Locking hyperf/pool (v3.1.66)</li>
 	<li>Locking hyperf/process (v3.1.63)</li>
 	<li>Locking hyperf/redis (v3.1.66)</li>
 	<li>Locking hyperf/serializer (v3.1.63)</li>
 	<li>Locking hyperf/server (v3.1.63)</li>
 	<li>Locking hyperf/stdlib (v3.1.63)</li>
 	<li>Locking hyperf/stringable (v3.1.65)</li>
 	<li>Locking hyperf/support (v3.1.65)</li>
 	<li>Locking hyperf/tappable (v3.1.63)</li>
 	<li>Locking hyperf/testing (v3.1.63)</li>
 	<li>Locking laminas/laminas-stdlib (3.20.0)</li>
 	<li>Locking mockery/mockery (1.6.12)</li>
 	<li>Locking monolog/monolog (3.10.0)</li>
 	<li>Locking myclabs/deep-copy (1.13.4)</li>
 	<li>Locking nesbot/carbon (2.73.0)</li>
 	<li>Locking nikic/fast-route (v1.3.0)</li>
 	<li>Locking nikic/php-parser (v4.19.5)</li>
 	<li>Locking phar-io/manifest (2.0.4)</li>
 	<li>Locking phar-io/version (3.2.1)</li>
 	<li>Locking php-di/phpdoc-reader (2.2.1)</li>
 	<li>Locking phpoption/phpoption (1.9.5)</li>
 	<li>Locking phpstan/phpstan (1.12.33)</li>
 	<li>Locking phpunit/php-code-coverage (10.1.16)</li>
 	<li>Locking phpunit/php-file-iterator (4.1.0)</li>
 	<li>Locking phpunit/php-invoker (4.0.0)</li>
 	<li>Locking phpunit/php-text-template (3.0.1)</li>
 	<li>Locking phpunit/php-timer (6.0.0)</li>
 	<li>Locking phpunit/phpunit (10.5.63)</li>
 	<li>Locking psr/clock (1.0.0)</li>
 	<li>Locking psr/container (2.0.2)</li>
 	<li>Locking psr/event-dispatcher (1.0.0)</li>
 	<li>Locking psr/http-client (1.0.3)</li>
 	<li>Locking psr/http-factory (1.1.0)</li>
 	<li>Locking psr/http-message (2.0)</li>
 	<li>Locking psr/http-server-handler (1.0.2)</li>
 	<li>Locking psr/http-server-middleware (1.0.2)</li>
 	<li>Locking psr/log (3.0.2)</li>
 	<li>Locking psr/simple-cache (3.0.0)</li>
 	<li>Locking ralouphie/getallheaders (3.0.3)</li>
 	<li>Locking react/cache (v1.2.0)</li>
 	<li>Locking react/child-process (v0.6.7)</li>
 	<li>Locking react/dns (v1.14.0)</li>
 	<li>Locking react/event-loop (v1.6.0)</li>
 	<li>Locking react/promise (v3.3.0)</li>
 	<li>Locking react/socket (v1.17.0)</li>
 	<li>Locking react/stream (v1.4.0)</li>
 	<li>Locking sebastian/cli-parser (2.0.1)</li>
 	<li>Locking sebastian/code-unit (2.0.0)</li>
 	<li>Locking sebastian/code-unit-reverse-lookup (3.0.0)</li>
 	<li>Locking sebastian/comparator (5.0.5)</li>
 	<li>Locking sebastian/complexity (3.2.0)</li>
 	<li>Locking sebastian/diff (5.1.1)</li>
 	<li>Locking sebastian/environment (6.1.0)</li>
 	<li>Locking sebastian/exporter (5.1.4)</li>
 	<li>Locking sebastian/global-state (6.0.2)</li>
 	<li>Locking sebastian/lines-of-code (2.0.2)</li>
 	<li>Locking sebastian/object-enumerator (5.0.0)</li>
 	<li>Locking sebastian/object-reflector (3.0.0)</li>
 	<li>Locking sebastian/recursion-context (5.0.1)</li>
 	<li>Locking sebastian/type (4.0.0)</li>
 	<li>Locking sebastian/version (4.0.1)</li>
 	<li>Locking swoole/ide-helper (5.1.8)</li>
 	<li>Locking swow/psr7-plus (v1.1.2)</li>
 	<li>Locking symfony/console (v6.4.35)</li>
 	<li>Locking symfony/deprecation-contracts (v3.6.0)</li>
 	<li>Locking symfony/event-dispatcher (v6.4.32)</li>
 	<li>Locking symfony/event-dispatcher-contracts (v3.6.0)</li>
 	<li>Locking symfony/filesystem (v6.4.34)</li>
 	<li>Locking symfony/finder (v6.4.34)</li>
 	<li>Locking symfony/http-foundation (v6.4.35)</li>
 	<li>Locking symfony/options-resolver (v6.4.30)</li>
 	<li>Locking symfony/polyfill-ctype (v1.33.0)</li>
 	<li>Locking symfony/polyfill-intl-grapheme (v1.33.0)</li>
 	<li>Locking symfony/polyfill-intl-normalizer (v1.33.0)</li>
 	<li>Locking symfony/polyfill-mbstring (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php80 (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php81 (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php83 (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php84 (v1.33.0)</li>
 	<li>Locking symfony/process (v6.4.33)</li>
 	<li>Locking symfony/service-contracts (v3.6.1)</li>
 	<li>Locking symfony/stopwatch (v6.4.24)</li>
 	<li>Locking symfony/string (v6.4.34)</li>
 	<li>Locking symfony/translation (v6.4.34)</li>
 	<li>Locking symfony/translation-contracts (v3.6.1)</li>
 	<li>Locking theseer/tokenizer (1.3.1)</li>
 	<li>Locking vlucas/phpdotenv (v5.6.3)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 135 installs, 0 updates, 0 removals</li>
 	<li>Downloading composer/pcre (3.3.2)</li>
 	<li>Downloading doctrine/inflector (2.1.0)</li>
 	<li>Downloading doctrine/instantiator (1.5.0)</li>
 	<li>Downloading symfony/deprecation-contracts (v3.6.0)</li>
 	<li>Downloading psr/container (2.0.2)</li>
 	<li>Downloading symfony/service-contracts (v3.6.1)</li>
 	<li>Downloading symfony/stopwatch (v6.4.24)</li>
 	<li>Downloading symfony/process (v6.4.33)</li>
 	<li>Downloading symfony/polyfill-php84 (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-php81 (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-php80 (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-mbstring (v1.33.0)</li>
 	<li>Downloading symfony/options-resolver (v6.4.30)</li>
 	<li>Downloading symfony/finder (v6.4.34)</li>
 	<li>Downloading symfony/polyfill-ctype (v1.33.0)</li>
 	<li>Downloading symfony/filesystem (v6.4.34)</li>
 	<li>Downloading psr/event-dispatcher (1.0.0)</li>
 	<li>Downloading symfony/event-dispatcher-contracts (v3.6.0)</li>
 	<li>Downloading symfony/event-dispatcher (v6.4.32)</li>
 	<li>Downloading symfony/polyfill-intl-normalizer (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-intl-grapheme (v1.33.0)</li>
 	<li>Downloading symfony/string (v6.4.34)</li>
 	<li>Downloading symfony/console (v6.4.35)</li>
 	<li>Downloading sebastian/diff (5.1.1)</li>
 	<li>Downloading react/event-loop (v1.6.0)</li>
 	<li>Downloading evenement/evenement (v3.0.2)</li>
 	<li>Downloading react/stream (v1.4.0)</li>
 	<li>Downloading react/promise (v3.3.0)</li>
 	<li>Downloading react/cache (v1.2.0)</li>
 	<li>Downloading react/dns (v1.14.0)</li>
 	<li>Downloading react/socket (v1.17.0)</li>
 	<li>Downloading react/child-process (v0.6.7)</li>
 	<li>Downloading fidry/cpu-core-counter (1.3.0)</li>
 	<li>Downloading psr/log (3.0.2)</li>
 	<li>Downloading composer/xdebug-handler (3.0.5)</li>
 	<li>Downloading composer/semver (3.4.4)</li>
 	<li>Downloading clue/ndjson-react (v1.3.0)</li>
 	<li>Downloading friendsofphp/php-cs-fixer (v3.94.2)</li>
 	<li>Downloading guzzlehttp/promises (2.3.0)</li>
 	<li>Downloading ralouphie/getallheaders (3.0.3)</li>
 	<li>Downloading psr/http-message (2.0)</li>
 	<li>Downloading psr/http-factory (1.1.0)</li>
 	<li>Downloading guzzlehttp/psr7 (2.9.0)</li>
 	<li>Downloading hyperf/tappable (v3.1.63)</li>
 	<li>Downloading hyperf/macroable (v3.1.63)</li>
 	<li>Downloading hyperf/conditionable (v3.1.63)</li>
 	<li>Downloading hyperf/stringable (v3.1.65)</li>
 	<li>Downloading hyperf/contract (v3.1.63)</li>
 	<li>Downloading hyperf/collection (v3.1.64)</li>
 	<li>Downloading hyperf/engine-contract (v1.14.0)</li>
 	<li>Downloading hyperf/engine (v2.15.0)</li>
 	<li>Downloading hyperf/context (v3.1.63)</li>
 	<li>Downloading hyperf/coroutine (v3.1.65)</li>
 	<li>Downloading hyperf/support (v3.1.65)</li>
 	<li>Downloading phpoption/phpoption (1.9.5)</li>
 	<li>Downloading graham-campbell/result-type (v1.1.4)</li>
 	<li>Downloading vlucas/phpdotenv (v5.6.3)</li>
 	<li>Downloading php-di/phpdoc-reader (2.2.1)</li>
 	<li>Downloading nikic/php-parser (v4.19.5)</li>
 	<li>Downloading hyperf/stdlib (v3.1.63)</li>
 	<li>Downloading hyperf/pipeline (v3.1.63)</li>
 	<li>Downloading hyperf/code-parser (v3.1.63)</li>
 	<li>Downloading hyperf/di (v3.1.67)</li>
 	<li>Downloading hyperf/coordinator (v3.1.63)</li>
 	<li>Downloading hyperf/command (v3.1.64)</li>
 	<li>Downloading hyperf/codec (v3.1.63)</li>
 	<li>Downloading hyperf/async-queue (v3.1.64)</li>
 	<li>Downloading psr/simple-cache (3.0.0)</li>
 	<li>Downloading hyperf/cache (v3.1.67)</li>
 	<li>Downloading hyperf/config (v3.1.63)</li>
 	<li>Downloading hyperf/constants (v3.1.63)</li>
 	<li>Downloading hyperf/pool (v3.1.66)</li>
 	<li>Downloading hyperf/event (v3.1.63)</li>
 	<li>Downloading symfony/translation-contracts (v3.6.1)</li>
 	<li>Downloading symfony/translation (v6.4.34)</li>
 	<li>Downloading psr/clock (1.0.0)</li>
 	<li>Downloading carbonphp/carbon-doctrine-types (3.2.0)</li>
 	<li>Downloading nesbot/carbon (2.73.0)</li>
 	<li>Downloading hyperf/database (v3.1.67)</li>
 	<li>Downloading hyperf/model-listener (v3.1.63)</li>
 	<li>Downloading fig/http-message-util (1.1.5)</li>
 	<li>Downloading hyperf/framework (v3.1.63)</li>
 	<li>Downloading hyperf/db-connection (v3.1.66)</li>
 	<li>Downloading hyperf/devtool (v3.1.66)</li>
 	<li>Downloading psr/http-client (1.0.3)</li>
 	<li>Downloading swow/psr7-plus (v1.1.2)</li>
 	<li>Downloading laminas/laminas-stdlib (3.20.0)</li>
 	<li>Downloading hyperf/laminas-mime (v3.0.0)</li>
 	<li>Downloading hyperf/http-message (v3.1.65)</li>
 	<li>Downloading psr/http-server-handler (1.0.2)</li>
 	<li>Downloading psr/http-server-middleware (1.0.2)</li>
 	<li>Downloading hyperf/dispatcher (v3.1.63)</li>
 	<li>Downloading hyperf/exception-handler (v3.1.63)</li>
 	<li>Downloading guzzlehttp/guzzle (7.10.0)</li>
 	<li>Downloading hyperf/guzzle (v3.1.66)</li>
 	<li>Downloading monolog/monolog (3.10.0)</li>
 	<li>Downloading hyperf/logger (v3.1.63)</li>
 	<li>Downloading hyperf/memory (v3.1.63)</li>
 	<li>Downloading hyperf/process (v3.1.63)</li>
 	<li>Downloading hyperf/redis (v3.1.66)</li>
 	<li>Downloading hyperf/serializer (v3.1.63)</li>
 	<li>Downloading hyperf/server (v3.1.63)</li>
 	<li>Downloading symfony/polyfill-php83 (v1.33.0)</li>
 	<li>Downloading symfony/http-foundation (v6.4.35)</li>
 	<li>Downloading sebastian/version (4.0.1)</li>
 	<li>Downloading sebastian/type (4.0.0)</li>
 	<li>Downloading sebastian/recursion-context (5.0.1)</li>
 	<li>Downloading sebastian/object-reflector (3.0.0)</li>
 	<li>Downloading sebastian/object-enumerator (5.0.0)</li>
 	<li>Downloading sebastian/global-state (6.0.2)</li>
 	<li>Downloading sebastian/exporter (5.1.4)</li>
 	<li>Downloading sebastian/environment (6.1.0)</li>
 	<li>Downloading sebastian/comparator (5.0.5)</li>
 	<li>Downloading sebastian/code-unit (2.0.0)</li>
 	<li>Downloading sebastian/cli-parser (2.0.1)</li>
 	<li>Downloading phpunit/php-timer (6.0.0)</li>
 	<li>Downloading phpunit/php-text-template (3.0.1)</li>
 	<li>Downloading phpunit/php-invoker (4.0.0)</li>
 	<li>Downloading phpunit/php-file-iterator (4.1.0)</li>
 	<li>Downloading theseer/tokenizer (1.3.1)</li>
 	<li>Downloading sebastian/lines-of-code (2.0.2)</li>
 	<li>Downloading sebastian/complexity (3.2.0)</li>
 	<li>Downloading sebastian/code-unit-reverse-lookup (3.0.0)</li>
 	<li>Downloading phpunit/php-code-coverage (10.1.16)</li>
 	<li>Downloading phar-io/version (3.2.1)</li>
 	<li>Downloading phar-io/manifest (2.0.4)</li>
 	<li>Downloading myclabs/deep-copy (1.13.4)</li>
 	<li>Downloading phpunit/phpunit (10.5.63)</li>
 	<li>Downloading nikic/fast-route (v1.3.0)</li>
 	<li>Downloading hyperf/http-server (v3.1.65)</li>
 	<li>Downloading hyperf/testing (v3.1.63)</li>
 	<li>Downloading hamcrest/hamcrest-php (v2.1.1)</li>
 	<li>Downloading mockery/mockery (1.6.12)</li>
 	<li>Downloading phpstan/phpstan (1.12.33)</li>
 	<li>Downloading swoole/ide-helper (5.1.8)</li>
 	<li>Installing composer/pcre (3.3.2): Extracting archive</li>
 	<li>Installing doctrine/inflector (2.1.0): Extracting archive</li>
 	<li>Installing doctrine/instantiator (1.5.0): Extracting archive</li>
 	<li>Installing symfony/deprecation-contracts (v3.6.0): Extracting archive</li>
 	<li>Installing psr/container (2.0.2): Extracting archive</li>
 	<li>Installing symfony/service-contracts (v3.6.1): Extracting archive</li>
 	<li>Installing symfony/stopwatch (v6.4.24): Extracting archive</li>
 	<li>Installing symfony/process (v6.4.33): Extracting archive</li>
 	<li>Installing symfony/polyfill-php84 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-php81 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-php80 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-mbstring (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/options-resolver (v6.4.30): Extracting archive</li>
 	<li>Installing symfony/finder (v6.4.34): Extracting archive</li>
 	<li>Installing symfony/polyfill-ctype (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/filesystem (v6.4.34): Extracting archive</li>
 	<li>Installing psr/event-dispatcher (1.0.0): Extracting archive</li>
 	<li>Installing symfony/event-dispatcher-contracts (v3.6.0): Extracting archive</li>
 	<li>Installing symfony/event-dispatcher (v6.4.32): Extracting archive</li>
 	<li>Installing symfony/polyfill-intl-normalizer (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-intl-grapheme (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/string (v6.4.34): Extracting archive</li>
 	<li>Installing symfony/console (v6.4.35): Extracting archive</li>
 	<li>Installing sebastian/diff (5.1.1): Extracting archive</li>
 	<li>Installing react/event-loop (v1.6.0): Extracting archive</li>
 	<li>Installing evenement/evenement (v3.0.2): Extracting archive</li>
 	<li>Installing react/stream (v1.4.0): Extracting archive</li>
 	<li>Installing react/promise (v3.3.0): Extracting archive</li>
 	<li>Installing react/cache (v1.2.0): Extracting archive</li>
 	<li>Installing react/dns (v1.14.0): Extracting archive</li>
 	<li>Installing react/socket (v1.17.0): Extracting archive</li>
 	<li>Installing react/child-process (v0.6.7): Extracting archive</li>
 	<li>Installing fidry/cpu-core-counter (1.3.0): Extracting archive</li>
 	<li>Installing psr/log (3.0.2): Extracting archive</li>
 	<li>Installing composer/xdebug-handler (3.0.5): Extracting archive</li>
 	<li>Installing composer/semver (3.4.4): Extracting archive</li>
 	<li>Installing clue/ndjson-react (v1.3.0): Extracting archive</li>
 	<li>Installing friendsofphp/php-cs-fixer (v3.94.2): Extracting archive</li>
 	<li>Installing guzzlehttp/promises (2.3.0): Extracting archive</li>
 	<li>Installing ralouphie/getallheaders (3.0.3): Extracting archive</li>
 	<li>Installing psr/http-message (2.0): Extracting archive</li>
 	<li>Installing psr/http-factory (1.1.0): Extracting archive</li>
 	<li>Installing guzzlehttp/psr7 (2.9.0): Extracting archive</li>
 	<li>Installing hyperf/tappable (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/macroable (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/conditionable (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/stringable (v3.1.65): Extracting archive</li>
 	<li>Installing hyperf/contract (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/collection (v3.1.64): Extracting archive</li>
 	<li>Installing hyperf/engine-contract (v1.14.0): Extracting archive</li>
 	<li>Installing hyperf/engine (v2.15.0): Extracting archive</li>
 	<li>Installing hyperf/context (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/coroutine (v3.1.65): Extracting archive</li>
 	<li>Installing hyperf/support (v3.1.65): Extracting archive</li>
 	<li>Installing phpoption/phpoption (1.9.5): Extracting archive</li>
 	<li>Installing graham-campbell/result-type (v1.1.4): Extracting archive</li>
 	<li>Installing vlucas/phpdotenv (v5.6.3): Extracting archive</li>
 	<li>Installing php-di/phpdoc-reader (2.2.1): Extracting archive</li>
 	<li>Installing nikic/php-parser (v4.19.5): Extracting archive</li>
 	<li>Installing hyperf/stdlib (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/pipeline (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/code-parser (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/di (v3.1.67): Extracting archive</li>
 	<li>Installing hyperf/coordinator (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/command (v3.1.64): Extracting archive</li>
 	<li>Installing hyperf/codec (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/async-queue (v3.1.64): Extracting archive</li>
 	<li>Installing psr/simple-cache (3.0.0): Extracting archive</li>
 	<li>Installing hyperf/cache (v3.1.67): Extracting archive</li>
 	<li>Installing hyperf/config (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/constants (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/pool (v3.1.66): Extracting archive</li>
 	<li>Installing hyperf/event (v3.1.63): Extracting archive</li>
 	<li>Installing symfony/translation-contracts (v3.6.1): Extracting archive</li>
 	<li>Installing symfony/translation (v6.4.34): Extracting archive</li>
 	<li>Installing psr/clock (1.0.0): Extracting archive</li>
 	<li>Installing carbonphp/carbon-doctrine-types (3.2.0): Extracting archive</li>
 	<li>Installing nesbot/carbon (2.73.0): Extracting archive</li>
 	<li>Installing hyperf/database (v3.1.67): Extracting archive</li>
 	<li>Installing hyperf/model-listener (v3.1.63): Extracting archive</li>
 	<li>Installing fig/http-message-util (1.1.5): Extracting archive</li>
 	<li>Installing hyperf/framework (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/db-connection (v3.1.66): Extracting archive</li>
 	<li>Installing hyperf/devtool (v3.1.66): Extracting archive</li>
 	<li>Installing psr/http-client (1.0.3): Extracting archive</li>
 	<li>Installing swow/psr7-plus (v1.1.2): Extracting archive</li>
 	<li>Installing laminas/laminas-stdlib (3.20.0): Extracting archive</li>
 	<li>Installing hyperf/laminas-mime (v3.0.0): Extracting archive</li>
 	<li>Installing hyperf/http-message (v3.1.65): Extracting archive</li>
 	<li>Installing psr/http-server-handler (1.0.2): Extracting archive</li>
 	<li>Installing psr/http-server-middleware (1.0.2): Extracting archive</li>
 	<li>Installing hyperf/dispatcher (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/exception-handler (v3.1.63): Extracting archive</li>
 	<li>Installing guzzlehttp/guzzle (7.10.0): Extracting archive</li>
 	<li>Installing hyperf/guzzle (v3.1.66): Extracting archive</li>
 	<li>Installing monolog/monolog (3.10.0): Extracting archive</li>
 	<li>Installing hyperf/logger (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/memory (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/process (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/redis (v3.1.66): Extracting archive</li>
 	<li>Installing hyperf/serializer (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/server (v3.1.63): Extracting archive</li>
 	<li>Installing symfony/polyfill-php83 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/http-foundation (v6.4.35): Extracting archive</li>
 	<li>Installing sebastian/version (4.0.1): Extracting archive</li>
 	<li>Installing sebastian/type (4.0.0): Extracting archive</li>
 	<li>Installing sebastian/recursion-context (5.0.1): Extracting archive</li>
 	<li>Installing sebastian/object-reflector (3.0.0): Extracting archive</li>
 	<li>Installing sebastian/object-enumerator (5.0.0): Extracting archive</li>
 	<li>Installing sebastian/global-state (6.0.2): Extracting archive</li>
 	<li>Installing sebastian/exporter (5.1.4): Extracting archive</li>
 	<li>Installing sebastian/environment (6.1.0): Extracting archive</li>
 	<li>Installing sebastian/comparator (5.0.5): Extracting archive</li>
 	<li>Installing sebastian/code-unit (2.0.0): Extracting archive</li>
 	<li>Installing sebastian/cli-parser (2.0.1): Extracting archive</li>
 	<li>Installing phpunit/php-timer (6.0.0): Extracting archive</li>
 	<li>Installing phpunit/php-text-template (3.0.1): Extracting archive</li>
 	<li>Installing phpunit/php-invoker (4.0.0): Extracting archive</li>
 	<li>Installing phpunit/php-file-iterator (4.1.0): Extracting archive</li>
 	<li>Installing theseer/tokenizer (1.3.1): Extracting archive</li>
 	<li>Installing sebastian/lines-of-code (2.0.2): Extracting archive</li>
 	<li>Installing sebastian/complexity (3.2.0): Extracting archive</li>
 	<li>Installing sebastian/code-unit-reverse-lookup (3.0.0): Extracting archive</li>
 	<li>Installing phpunit/php-code-coverage (10.1.16): Extracting archive</li>
 	<li>Installing phar-io/version (3.2.1): Extracting archive</li>
 	<li>Installing phar-io/manifest (2.0.4): Extracting archive</li>
 	<li>Installing myclabs/deep-copy (1.13.4): Extracting archive</li>
 	<li>Installing phpunit/phpunit (10.5.63): Extracting archive</li>
 	<li>Installing nikic/fast-route (v1.3.0): Extracting archive</li>
 	<li>Installing hyperf/http-server (v3.1.65): Extracting archive</li>
 	<li>Installing hyperf/testing (v3.1.63): Extracting archive</li>
 	<li>Installing hamcrest/hamcrest-php (v2.1.1): Extracting archive</li>
 	<li>Installing mockery/mockery (1.6.12): Extracting archive</li>
 	<li>Installing phpstan/phpstan (1.12.33): Extracting archive</li>
 	<li>Installing swoole/ide-helper (5.1.8): Extracting archive
27 package suggestions were added by new dependencies, use <code>composer suggest</code> to see details.
Generating optimized autoload files</li>
</ul>
<blockquote>rm -rf runtime/container
114 packages you are using are looking for funding.
Use the <code>composer fund</code> command to find out more!
No security vulnerability advisories found.
/data/project #


]]>
https://www.shuijingwanwq.com/2026/04/07/9435/feed/ 1
Go + Gin 实战 RESTful API:从环境搭建(WSL/Docker/VS Code)到 Git 提交与 GitHub 托管 https://www.shuijingwanwq.com/2026/04/03/9416/ https://www.shuijingwanwq.com/2026/04/03/9416/#respond Fri, 03 Apr 2026 04:11:30 +0000 https://www.shuijingwanwq.com/?p=9416 Post Views: 197

1、参考:使用 Go 和 Gin 构建 RESTful API(Go.dev) https://go.dev/doc/tutorial/web-service-gin — 使用 Gin 构建简单 Web 服务的 Go 官方教程。

2、安装 Go。Docker Compose 一键启动(参考 Hyperf 项目化)。我想要 “写好配置,一条命令启动整个开发环境”,用 docker-compose.yml 最为方便,和 Hyperf 的 Docker Compose 用法几乎一样。创建项目文件夹:web-service-gin



ubuntu@DESKTOP-H4MGQIU:~/wwwroot$ mkdir web-service-gin
ubuntu@DESKTOP-H4MGQIU:~/wwwroot$ cd web-service-gin
ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ pwd
/home/ubuntu/wwwroot/web-service-gin


3、创建 docker-compose.yml



version: '3.8'

services:
  go:
    image: golang:1.23-alpine
    container_name: go-gin-tutorial
    working_dir: /app
    volumes:
      - ./:/app         # 本地代码挂载
    ports:
      - "8080:8080"     # 映射端口最好加引号
    tty: true
    stdin_open: true
    environment:
      - GOPROXY=https://goproxy.io,direct
    command: tail -f /dev/null   # 容器启动后保持运行状态


4、启动容器(进入 Go 环境), 报错:WSL Ubuntu 里只装了 Docker,没装 Docker Compose。



ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ docker-compose up -d
Command 'docker-compose' not found, but can be installed with:
sudo snap install docker          # version 28.4.0, or
sudo apt  install docker-compose  # version 1.29.2-1
See 'snap info docker' for additional versions.
ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ sudo apt update && sudo apt install -y docker-compose
[sudo] password for ubuntu:
Get:1 http://security.ubuntu.com/ubuntu jammy-security InRelease [129 kB]
Hit:2 https://download.docker.com/linux/ubuntu jammy InRelease
Hit:3 http://archive.ubuntu.com/ubuntu jammy InRelease
Hit:4 https://packages.redis.io/deb jammy InRelease
Get:5 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [128 kB]
Get:6 http://archive.ubuntu.com/ubuntu jammy-backports InRelease [127 kB]
Get:7 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [3328 kB]
Get:8 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 c-n-f Metadata [30.4 kB]
Fetched 3743 kB in 7s (575 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
62 packages can be upgraded. Run 'apt list --upgradable' to see them.
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  docker-ce docker-ce-cli python3-attr python3-certifi python3-chardet python3-distutils python3-docker python3-dockerpty python3-docopt python3-dotenv python3-idna python3-jsonschema
  python3-lib2to3 python3-pyrsistent python3-requests python3-setuptools python3-texttable python3-urllib3 python3-websocket
Suggested packages:
  cgroupfs-mount | cgroup-lite python-attr-doc python-jsonschema-doc python3-openssl python3-socks python-requests-doc python-setuptools-doc
Recommended packages:
  docker.io
The following NEW packages will be installed:
  docker-compose python3-attr python3-certifi python3-chardet python3-distutils python3-docker python3-dockerpty python3-docopt python3-dotenv python3-idna python3-jsonschema
  python3-lib2to3 python3-pyrsistent python3-requests python3-setuptools python3-texttable python3-urllib3 python3-websocket
The following packages will be upgraded:
  docker-ce docker-ce-cli
2 upgraded, 18 newly installed, 0 to remove and 60 not upgraded.
Need to get 40.4 MB of archives.
After this operation, 7757 kB of additional disk space will be used.
Get:1 https://download.docker.com/linux/ubuntu jammy/stable amd64 docker-ce-cli amd64 5:29.3.1-1~ubuntu.22.04~jammy [16.4 MB]
Get:2 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-lib2to3 all 3.10.8-1~22.04 [77.6 kB]
Get:3 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-distutils all 3.10.8-1~22.04 [139 kB]
Get:4 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-certifi all 2020.6.20-1 [150 kB]
Get:5 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-chardet all 4.0.0-1 [98.0 kB]
Get:6 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-idna all 3.3-1ubuntu0.1 [52.1 kB]
Get:7 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-urllib3 all 1.26.5-1~exp1ubuntu0.6 [98.7 kB]
Get:8 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-requests all 2.25.1+dfsg-2ubuntu0.3 [48.8 kB]
Get:9 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-websocket all 1.2.3-1 [34.7 kB]
Get:10 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-docker all 5.0.3-1 [89.3 kB]
Get:11 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-dockerpty all 0.4.1-2 [11.1 kB]
Get:12 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-docopt all 0.6.2-4 [26.9 kB]
Get:13 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-dotenv all 0.19.2-1 [20.5 kB]
Get:14 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-attr all 21.2.0-1ubuntu1 [43.9 kB]
Get:15 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 python3-setuptools all 59.6.0-1.2ubuntu0.22.04.3 [340 kB]
Get:16 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-pyrsistent amd64 0.18.1-1build1 [55.5 kB]
Get:17 http://archive.ubuntu.com/ubuntu jammy/main amd64 python3-jsonschema all 3.2.0-0ubuntu2 [43.1 kB]
Get:18 http://archive.ubuntu.com/ubuntu jammy/universe amd64 python3-texttable all 1.6.4-1 [11.4 kB]
Get:19 http://archive.ubuntu.com/ubuntu jammy/universe amd64 docker-compose all 1.29.2-1 [95.8 kB]
Get:20 https://download.docker.com/linux/ubuntu jammy/stable amd64 docker-ce amd64 5:29.3.1-1~ubuntu.22.04~jammy [22.6 MB]
Fetched 40.4 MB in 10s (3969 kB/s)
(Reading database ... 24579 files and directories currently installed.)
Preparing to unpack .../00-docker-ce-cli_5%3a29.3.1-1~ubuntu.22.04~jammy_amd64.deb ...
Unpacking docker-ce-cli (5:29.3.1-1~ubuntu.22.04~jammy) over (5:29.3.0-1~ubuntu.22.04~jammy) ...
Preparing to unpack .../01-docker-ce_5%3a29.3.1-1~ubuntu.22.04~jammy_amd64.deb ...
Unpacking docker-ce (5:29.3.1-1~ubuntu.22.04~jammy) over (5:29.3.0-1~ubuntu.22.04~jammy) ...
Selecting previously unselected package python3-lib2to3.
Preparing to unpack .../02-python3-lib2to3_3.10.8-1~22.04_all.deb ...
Unpacking python3-lib2to3 (3.10.8-1~22.04) ...
Selecting previously unselected package python3-distutils.
Preparing to unpack .../03-python3-distutils_3.10.8-1~22.04_all.deb ...
Unpacking python3-distutils (3.10.8-1~22.04) ...
Selecting previously unselected package python3-certifi.
Preparing to unpack .../04-python3-certifi_2020.6.20-1_all.deb ...
Unpacking python3-certifi (2020.6.20-1) ...
Selecting previously unselected package python3-chardet.
Preparing to unpack .../05-python3-chardet_4.0.0-1_all.deb ...
Unpacking python3-chardet (4.0.0-1) ...
Selecting previously unselected package python3-idna.
Preparing to unpack .../06-python3-idna_3.3-1ubuntu0.1_all.deb ...
Unpacking python3-idna (3.3-1ubuntu0.1) ...
Selecting previously unselected package python3-urllib3.
Preparing to unpack .../07-python3-urllib3_1.26.5-1~exp1ubuntu0.6_all.deb ...
Unpacking python3-urllib3 (1.26.5-1~exp1ubuntu0.6) ...
Selecting previously unselected package python3-requests.
Preparing to unpack .../08-python3-requests_2.25.1+dfsg-2ubuntu0.3_all.deb ...
Unpacking python3-requests (2.25.1+dfsg-2ubuntu0.3) ...
Selecting previously unselected package python3-websocket.
Preparing to unpack .../09-python3-websocket_1.2.3-1_all.deb ...
Unpacking python3-websocket (1.2.3-1) ...
Selecting previously unselected package python3-docker.
Preparing to unpack .../10-python3-docker_5.0.3-1_all.deb ...
Unpacking python3-docker (5.0.3-1) ...
Selecting previously unselected package python3-dockerpty.
Preparing to unpack .../11-python3-dockerpty_0.4.1-2_all.deb ...
Unpacking python3-dockerpty (0.4.1-2) ...
Selecting previously unselected package python3-docopt.
Preparing to unpack .../12-python3-docopt_0.6.2-4_all.deb ...
Unpacking python3-docopt (0.6.2-4) ...
Selecting previously unselected package python3-dotenv.
Preparing to unpack .../13-python3-dotenv_0.19.2-1_all.deb ...
Unpacking python3-dotenv (0.19.2-1) ...
Selecting previously unselected package python3-attr.
Preparing to unpack .../14-python3-attr_21.2.0-1ubuntu1_all.deb ...
Unpacking python3-attr (21.2.0-1ubuntu1) ...
Selecting previously unselected package python3-setuptools.
Preparing to unpack .../15-python3-setuptools_59.6.0-1.2ubuntu0.22.04.3_all.deb ...
Unpacking python3-setuptools (59.6.0-1.2ubuntu0.22.04.3) ...
Selecting previously unselected package python3-pyrsistent:amd64.
Preparing to unpack .../16-python3-pyrsistent_0.18.1-1build1_amd64.deb ...
Unpacking python3-pyrsistent:amd64 (0.18.1-1build1) ...
Selecting previously unselected package python3-jsonschema.
Preparing to unpack .../17-python3-jsonschema_3.2.0-0ubuntu2_all.deb ...
Unpacking python3-jsonschema (3.2.0-0ubuntu2) ...
Selecting previously unselected package python3-texttable.
Preparing to unpack .../18-python3-texttable_1.6.4-1_all.deb ...
Unpacking python3-texttable (1.6.4-1) ...
Selecting previously unselected package docker-compose.
Preparing to unpack .../19-docker-compose_1.29.2-1_all.deb ...
Unpacking docker-compose (1.29.2-1) ...
Setting up python3-dotenv (0.19.2-1) ...
Setting up python3-attr (21.2.0-1ubuntu1) ...
Setting up python3-texttable (1.6.4-1) ...
Setting up python3-docopt (0.6.2-4) ...
Setting up python3-chardet (4.0.0-1) ...
Setting up python3-certifi (2020.6.20-1) ...
Setting up python3-idna (3.3-1ubuntu0.1) ...
Setting up python3-urllib3 (1.26.5-1~exp1ubuntu0.6) ...
Setting up docker-ce-cli (5:29.3.1-1~ubuntu.22.04~jammy) ...
Setting up python3-pyrsistent:amd64 (0.18.1-1build1) ...
Setting up python3-lib2to3 (3.10.8-1~22.04) ...
Setting up python3-websocket (1.2.3-1) ...
Setting up python3-dockerpty (0.4.1-2) ...
Setting up python3-distutils (3.10.8-1~22.04) ...
Setting up python3-setuptools (59.6.0-1.2ubuntu0.22.04.3) ...
Setting up python3-jsonschema (3.2.0-0ubuntu2) ...
Setting up docker-ce (5:29.3.1-1~ubuntu.22.04~jammy) ...
Setting up python3-requests (2.25.1+dfsg-2ubuntu0.3) ...
Setting up python3-docker (5.0.3-1) ...
Setting up docker-compose (1.29.2-1) ...
Processing triggers for man-db (2.10.2-1) ...
ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ docker-compose --version
docker-compose version 1.29.2, build unknown
ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ docker-compose up -d
Creating network "web-service-gin_default" with the default driver
Pulling go (golang:1.23-alpine)...
1.23-alpine: Pulling from library/golang
4f4fb700ef54: Pull complete
9824c27679d3: Pull complete
8371a51cbc44: Pull complete
d5791340ef18: Pull complete
d3178a7b2709: Pull complete
28f6f92c256d: Download complete
d1ac4f409af1: Download complete
Digest: sha256:383395b794dffa5b53012a212365d40c8e37109a626ca30d6151c8348d380b5f
Status: Downloaded newer image for golang:1.23-alpine
Creating go-gin-tutorial ... done
ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ docker-compose exec go sh
/app # ls -la
total 12
drwxr-xr-x    2 1000     1000          4096 Mar 30 03:36 .
drwxr-xr-x    1 root     root          4096 Mar 30 03:40 ..
-rw-r--r--    1 1000     1000           272 Mar 30 03:36 docker-compose.yml


5、创建一个可以管理依赖项的模块。运行该go mod init命令,并指定代码所在模块的路径。



/app # go mod init example/web-service-gin
go: creating new go.mod: module example/web-service-gin


6、打算用 GoLand(Windows)直接编辑 WSL 里的 Go 代码,以配合我的 Docker 环境。我的 Go 代码存在 WSL Ubuntu 的文件夹里,GoLand 可以直接访问 WSL 文件,就像访问本地文件夹一样。现在的代码在这里:~/wwwroot/web-service-gin。在 Windows 里访问这个路径,就是:\wsl$\Ubuntu\home\ubuntu\wwwroot\web-service-gin。 GoLand 打开这个文件夹,打开 GoLand,点 Open(打开),把上面复制的路径直接粘贴到文件夹地址栏,按回车,选择文件夹 → OK。现在就能在 Windows GoLand 里编辑、修改、保存 WSL 里的 Go 代码了。而且修改会实时同步到 Docker 容器里!如图1

在 Windows 里访问这个路径,就是:\wsl$\Ubuntu\home\ubuntu\wwwroot\web-service-gin

7、配置 GoLand 连接 WSL 里的 Docker(关键)
重启后:
文件 → 设置 → 构建、执行、部署 → Docker
左上角 + 添加连接
连接类型选择:WSL(重点:不要选 Docker Desktop)
发行版下拉选择:Ubuntu
底部显示「连接成功」即可
应用 → 确定。如图2

配置 GoLand 连接 WSL 里的 Docker

8、文件 → 设置 → 运行目标,添加 → 选 Docker Compose,点击右上角 +选择:✅ Docker Compose。运行目标(Docker Compose)配置完成后,GoLand 会获得:只管【运行】,不管【代码提示】
它的作用只有:
点 ▶️ 运行时
代码在 Docker 容器里执行。如图3

文件 → 设置 → 运行目标,添加 → 选 Docker Compose

9、项目默认目标:go。如图4

项目默认目标:go

10、代码提示、自动补全、语法识别 必须靠 GOROOT
GoLand 要想识别:
string
int
fmt
函数跳转
自动补全
必须配置:本地 GOROOT这是 IDE 硬性规则。打开 WSL Ubuntu 终端,复制运行:



# 1. 下载 Go 1.23(和容器版本一样)
wget https://dl.google.com/go/go1.23.0.linux-amd64.tar.gz

# 2. 安装到 Linux 标准路径:/usr/local/go
sudo tar -C /usr/local -xzf go1.23.0.linux-amd64.tar.gz

# 3. 配置环境变量
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
source ~/.bashrc

# 4. 验证安装成功
go version


11、打开 GoLand 设置 → Go → GOROOT
点 + 添加
选择 WSL
自动找到 /usr/local/go
确定 → 应用。如图5

打开 GoLand 设置 → Go → GOROOT

12、创建数据,创建一个名为 main.go 的文件。将在这个文件中编写 Go 代码。



package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// album represents data about a record album.
type album struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Artist string  `json:"artist"`
	Price  float64 `json:"price"`
}

// albums slice to seed record album data.
var albums = []album{
	{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
	{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
	{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func main() {
	router := gin.Default()
	router.GET("/albums", getAlbums)

	router.Run("0.0.0.0:8080") // 监听所有网卡
}

// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
	c.IndentedJSON(http.StatusOK, albums)
}


13、现在 IDE 有一些报错的提示:

无法解析符号’github.com’
无法解析符号’gin-gonic’
无法解析符号’gin’
未解析的引用 ‘Default’。如图6

现在 IDE 有一些报错的提示

14、打开 WSL 终端,在 WSL 直接执行:go get github.com/gin-gonic/gin



ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ go get github.com/gin-gonic/gin
go: downloading github.com/gin-gonic/gin v1.12.0
go: github.com/gin-gonic/gin@v1.12.0 requires go >= 1.25.0; switching to go1.25.8
go: downloading github.com/gin-contrib/sse v1.1.0
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading github.com/quic-go/quic-go v0.59.0
go: downloading golang.org/x/net v0.51.0
go: downloading github.com/bytedance/sonic v1.15.0
go: downloading github.com/goccy/go-json v0.10.5
go: downloading github.com/json-iterator/go v1.1.12
go: downloading github.com/goccy/go-yaml v1.19.2
go: downloading github.com/pelletier/go-toml/v2 v2.2.4
go: downloading github.com/ugorji/go/codec v1.3.1
go: downloading go.mongodb.org/mongo-driver/v2 v2.5.0
go: downloading google.golang.org/protobuf v1.36.10
go: downloading github.com/go-playground/validator/v10 v10.30.1
go: downloading golang.org/x/sys v0.41.0
go: downloading github.com/quic-go/qpack v0.6.0
go: downloading github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
go: downloading github.com/modern-go/reflect2 v1.0.2
go: downloading github.com/gabriel-vasile/mimetype v1.4.12
go: downloading github.com/go-playground/universal-translator v0.18.1
go: downloading github.com/leodido/go-urn v1.4.0
go: downloading golang.org/x/crypto v0.48.0
go: downloading golang.org/x/text v0.34.0
go: downloading github.com/bytedance/gopkg v0.1.3
go: downloading github.com/cloudwego/base64x v0.1.6
go: downloading golang.org/x/arch v0.22.0
go: downloading github.com/go-playground/locales v0.14.1
go: downloading github.com/klauspost/cpuid/v2 v2.3.0
go: downloading github.com/bytedance/sonic/loader v0.5.0
go: downloading github.com/twitchyliquid64/golang-asm v0.15.1
go: updating go.mod: open /home/ubuntu/wwwroot/web-service-gin/go.mod: permission denied

15、反省一下,感觉现在这样太痛苦了。这种 “容器里装依赖、容器外装依赖” 的用法,是错误、混乱、反人类的!** 容器的优势就是环境统一、隔离、不用乱装东西。现在被用成了:一边容器、一边 WSL,精神分裂!真正正确的开发模式只有两种:只用 WSL 的 Go,不用 Docker 跑开发;全部在 Docker 里,WSL 什么都不装;

16、仔细排查原因,找到了根源,在 文件 → 设置 → 构建、执行、部署 → Docker 中,虚拟机路径:/home/ubuntu/wwwroot/web-service-gin,这个路径是 WSL 中的路径。** 正确的路径 不应该是 WSL 路径!应该是【容器内部的路径】!**。虚拟机路径修改为:/app。如图7

虚拟机路径修改为:/app

17、发现 GOROOT 中仍然不提供 Docker 的选项。GoLand 在「没有 Docker Desktop」的环境下,就是无法直接选择容器内的 GOROOT!最终决定,放弃 GoLand,改用 VS Code + Dev Containers

18、Windows 安装 VS Code,VS Code 扩展商店安装:Dev Containers(微软官方)、WSL、Docker

19、启动容器,报错:ERROR: for go ‘ContainerConfig’,这是老容器 + Python V1 的兼容性问题

<pre class="wp-block-syntaxhighlighter-code">

ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ docker-compose up -d
Recreating 814a800e96e6_go-gin-tutorial ...

ERROR: for 814a800e96e6_go-gin-tutorial  'ContainerConfig'

ERROR: for go  'ContainerConfig'
Traceback (most recent call last):
  File "/usr/bin/docker-compose", line 33, in <module>
    sys.exit(load_entry_point('docker-compose==1.29.2', 'console_scripts', 'docker-compose')())
  File "/usr/lib/python3/dist-packages/compose/cli/main.py", line 81, in main
    command_func()
  File "/usr/lib/python3/dist-packages/compose/cli/main.py", line 203, in perform_command
    handler(command, command_options)
  File "/usr/lib/python3/dist-packages/compose/metrics/decorator.py", line 18, in wrapper
    result = fn(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/compose/cli/main.py", line 1186, in up
    to_attach = up(False)
  File "/usr/lib/python3/dist-packages/compose/cli/main.py", line 1166, in up
    return self.project.up(
  File "/usr/lib/python3/dist-packages/compose/project.py", line 697, in up
    results, errors = parallel.parallel_execute(
  File "/usr/lib/python3/dist-packages/compose/parallel.py", line 108, in parallel_execute
    raise error_to_reraise
  File "/usr/lib/python3/dist-packages/compose/parallel.py", line 206, in producer
    result = func(obj)
  File "/usr/lib/python3/dist-packages/compose/project.py", line 679, in do
    return service.execute_convergence_plan(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 579, in execute_convergence_plan
    return self._execute_convergence_recreate(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 499, in _execute_convergence_recreate
    containers, errors = parallel_execute(
  File "/usr/lib/python3/dist-packages/compose/parallel.py", line 108, in parallel_execute
    raise error_to_reraise
  File "/usr/lib/python3/dist-packages/compose/parallel.py", line 206, in producer
    result = func(obj)
  File "/usr/lib/python3/dist-packages/compose/service.py", line 494, in recreate
    return self.recreate_container(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 612, in recreate_container
    new_container = self.create_container(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 330, in create_container
    container_options = self._get_container_create_options(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 921, in _get_container_create_options
    container_options, override_options = self._build_container_volume_options(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 960, in _build_container_volume_options
    binds, affinity = merge_volume_bindings(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 1548, in merge_volume_bindings
    old_volumes, old_mounts = get_container_data_volumes(
  File "/usr/lib/python3/dist-packages/compose/service.py", line 1579, in get_container_data_volumes
    container.image_config['ContainerConfig'].get('Volumes') or {}
KeyError: 'ContainerConfig'

</pre>

20、进入目录:~/wwwroot/web-service-gin,执行 :code .。是否信任此文件夹中的文件的作者? 是。如图8

进入目录:~/wwwroot/web-service-gin,执行 :code .。是否信任此文件夹中的文件的作者? 是


ubuntu@DESKTOP-H4MGQIU:~/wwwroot/web-service-gin$ code .
Installing VS Code Server for Linux x64 (cfbea10c5ffb233ea9177d34726e6056e89913dc)
Downloading: 100%
Unpacking: 100%
Unpacked 3690 files and folders to /home/ubuntu/.vscode-server/bin/cfbea10c5ffb233ea9177d34726e6056e89913dc.
Looking for compatibility check script at /home/ubuntu/.vscode-server/bin/cfbea10c5ffb233ea9177d34726e6056e89913dc/bin/helpers/check-requirements.sh
Running compatibility check script
Compatibility check successful (0)


21、删除 .idea 目录,这是 Goland 留下的。准备 Dev Container 配置文件,直接用已有 Docker 镜像,新建:.devcontainer/devcontainer.json


{
  "name": "Go Gin Dev Container",
  "dockerComposeFile": ["../docker-compose.yml"],
  "service": "go",
  "workspaceFolder": "/app",
  "settings": {
    "terminal.integrated.shell.linux": "/bin/sh"
  },
  "extensions": [
    "golang.Go"
  ],
  "forwardPorts": [8080],
  "remoteUser": "root"
}

解释:

dockerComposeFile 指向 docker-compose.yml
service 是希望 VS Code 连接的服务(这里是 go)
workspaceFolder 是代码挂载目录 /app
extensions 自动在容器里安装 Go 插件
forwardPorts 映射容器端口到 VS Code
remoteUser 可选,如果容器里默认用户不是 root

22、VS Code 打开容器
打开 VS Code,进入项目目录
按 F1 → 输入 Dev Containers: Reopen in Container
VS Code 会根据 devcontainer.json 和 docker-compose.yml 启动容器并挂载代码
打开终端,会发现直接在容器里(/app),Go 版本和 Docker 镜像一致

⚡ 提示:如果容器启动慢,可以先用 docker-compose up -d 启动容器,再用 Dev Container 连接。如图9

VS Code 打开容器

23、开发流程建议
用 Dev Container 打开项目
直接在容器终端运行 Go 或 PHP 命令
容器里安装依赖(go mod tidy、composer install 等)
在 VS Code 中调试、运行、测试
不需要关心 WSL 上是否安装 Go/PHP

24、多项目 + 多容器流程图

┌─────────────────────────────────────────────────────────────┐
│ VS Code │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Project-Go │ │ Project-PHP74 │ │ Project-PHP81 │ │
│ │ Workspace │ │ Workspace │ │ Workspace │ │
│ │ (.code-workspace) │ │ (.code-workspace) │ │ (.code-workspace) │ │
│ │ Dev Container │ │ Dev Container │ │ Dev Container │ │
│ │ Service: go │ │ Service: php74 │ │ Service: php81 │ │
│ │ Image: golang:1.23 │ │ Image: php:7.4-fpm │ │ Image: php:8.1-fpm │ │
│ │ Ports: 8080 │ │ Ports: 8000 │ │ Ports: 8001 │ │
│ └─────────────┬───────┘ └─────────────┬───────┘ └─────────────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Docker Container Go │ │ Docker Container │ │ Docker Container │ │
│ │ Version: 1.23 │ │ PHP 7.4 FPM │ │ PHP 8.1 FPM │ │
│ │ /app ← 挂载项目代码 │ │ /app ← 挂载项目代码 │ │ /app ← 挂载项目代码 │ │
│ │ 端口 8080 映射本地 │ │ 端口 8000 映射本地 │ │ 端口 8001 映射本地 │ │
│ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

说明:

  • 每个 VS Code 窗口对应一个独立 Workspace(项目)
  • 每个 Workspace 使用独立 Dev Container 连接对应 Docker 容器
  • 容器内挂载 /app 到对应项目代码,开发和运行环境完全一致
  • 不同项目端口独立映射,本地访问互不冲突

25、以下是 web-service-gin 项目的完整流程图,显示 VS Code、Dev Container、Docker 容器、挂载路径和端口映射,完全贴合本地开发环境。

┌───────────────────────────────────────────────────────────────────────────────┐
│ VS Code Window │
│ (web-service-gin Workspace) │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ Dev Container (Go) │ │
│ │ Service: go │ │
│ │ Image: golang:1.23-alpine │ │
│ │ WorkspaceFolder: /app │ │
│ │ Extensions: golang.Go │ │
│ │ ForwardPorts: 8080 │ │
│ │ RemoteUser: root │ │
│ └───────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ Docker Container (Go) │ │
│ │ Container Name: go-gin-tutorial │ │
│ │ Go Version: 1.23 │ │
│ │ Command: tail -f /dev/null │ │
│ │ Working Dir: /app │ │
│ │ Volume Mount: ./ (本地代码) → /app │ │
│ │ Port Mapping: 8080 → 8080 (本地访问 Gin Web) │ │
│ │ Environment: GOPROXY=https://goproxy.io,direct │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ 编辑流程:VS Code 编辑器 → Dev Container → Docker 容器运行 → 本地端口访问 Web │
└───────────────────────────────────────────────────────────────────────────────┘

26、以下是 宽屏版多项目组合图,展示 Go + PHP74 + PHP81 三个项目的 VS Code Workspace、Dev Container、Docker 容器、挂载路径和端口映射,横向排列,方便直观理解多项目、多语言、多容器的本地开发环境:

┌───────────────────────────────────────────────────────────────────────────────────────────────┐
│ VS Code Windows │
│ (各自 Workspace 独立窗口) │
│ │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ Project-Go Workspace │ │ Project-PHP74 Workspace │ │ Project-PHP81 Workspace │ │
│ │ (.code-workspace) │ │ (.code-workspace) │ │ (.code-workspace) │ │
│ │ Dev Container: go │ │ Dev Container: php74 │ │ Dev Container: php81 │ │
│ │ Image: golang:1.23-alpine │ │ Image: php:7.4-fpm │ │ Image: php:8.1-fpm │ │
│ │ ForwardPorts: 8080 │ │ ForwardPorts: 8000 │ │ ForwardPorts: 8001 │ │
│ │ RemoteUser: root │ │ RemoteUser: root │ │ RemoteUser: root │ │
│ └───────────────┬─────────────┘ └───────────────┬─────────────┘ └───────────────┬─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
│ │ Docker Container Go │ │ Docker Container PHP 7.4 │ │ Docker Container PHP 8.1 │ │
│ │ Name: go-gin-tutorial │ │ Name: php74-dev │ │ Name: php81-dev │ │
│ │ Version: 1.23 │ │ Version: 7.4 FPM │ │ Version: 8.1 FPM │ │
│ │ Working Dir: /app │ │ Working Dir: /app │ │ Working Dir: /app │ │
│ │ Volume Mount: ./ → /app │ │ Volume Mount: ./ → /app │ │ Volume Mount: ./ → /app │ │
│ │ Port Mapping: 8080 → 8080 │ │ Port Mapping: 8000 → 8000 │ │ Port Mapping: 8001 → 8001 │ │
│ │ Command: tail -f /dev/null │ │ Command: tail -f /dev/null │ │ Command: tail -f /dev/null │ │
│ │ Environment: GOPROXY=… │ │ │ │ │ │
│ └─────────────────────────────┘ └─────────────────────────────┘ └─────────────────────────────┘ │
│ │
│ 开发流程说明: │
│ VS Code 编辑 → Dev Container → Docker 容器运行 → 挂载代码 → 本地端口访问 Web/服务 │
│ 多项目、多语言版本独立,端口互不冲突,环境一致 │
└───────────────────────────────────────────────────────────────────────────────────────────────┘

图示说明

  1. VS Code Windows / Workspace
    每个项目对应一个 VS Code 窗口
    .code-workspace 配置独立
    插件、调试、终端完全隔离
  2. Dev Container
    连接对应 Docker 容器
    WorkspaceFolder 映射 /app
    ForwardPorts 映射本地访问端口
    插件和调试工具在容器内部运行
  3. Docker 容器
    Go / PHP 版本与镜像一致
    挂载本地代码,容器内修改即生效
    容器端口映射到本地端口,访问服务方便
    每个容器独立运行,不会影响其他项目
  4. 开发流程
    在 VS Code 编辑器编辑代码
    代码挂载到 Dev Container → Docker 容器
    容器运行服务或编译命令
    本地浏览器访问对应端口

优势

每个项目都是独立环境,类似 PHPStorm SDK + 项目隔离
多语言版本支持(Go + PHP74 + PHP81)
端口映射互不冲突,可同时运行多个服务
VS Code 窗口对应项目 Workspace,完全独立

27、我现在要如何将 APP [开发容器: GO GIN DEV CONTAINER] 转换为一个工作区呢?在 VS Code 中直接引用容器路径
打开 命令面板(Ctrl+Shift+P / Cmd+Shift+P)
输入 Save Workspace As…
点击 显示本地,保存至 C:\Users\Thinkpad\VSCodeWorkspaces\go-gin-tutorial-app.code-workspace。如图10

我现在要如何将 APP [开发容器: GO GIN DEV CONTAINER] 转换为一个工作区呢?

28、新开 vs code 后,文件 – 从文件打开工作区 – 显示本地 – C:\Users\Thinkpad\VSCodeWorkspaces\go-gin-tutorial-app.code-workspace。

29、现在在 vs code 中,问题:could not import github.com/gin-gonic/gin (no required module provides package “github.com/gin-gonic/gin”)。这是 Go Modules 依赖没安装或者未初始化 的典型错误。如图11

现在在 vs code 中,问题:could not import github.com/gin-gonic/gin (no required module provides package "github.com/gin-gonic/gin")

30、运行代码 开始将 Gin 模块作为依赖项进行跟踪。在命令行中,使用该go get 命令将 github.com/gin-gonic/gin 模块添加为模块的依赖项。使用点号参数表示“获取当前目录中代码的依赖项”。在 VS Code 里进入容器执行命令,默认打开的就是容器内的终端,路径应该是 /app,Go 已解析并下载此依赖项,以满足import 在上一步中添加的声明。如图12

在命令行中,使用该go get 命令将 github.com/gin-gonic/gin 模块添加为您的模块的依赖项。


/app # pwd
/app
/app # go get .
go: github.com/gin-gonic/gin@v1.12.0 requires go >= 1.25.0 (running go 1.23.12; GOTOOLCHAIN=local)
/app # 

31、在包含 main.go 的目录中,从命令行运行代码。使用点号参数表示“在当前目录中运行代码”。报错:


/app # go run .
main.go:6:2: no required module provides package github.com/gin-gonic/gin; to add it:
        go get github.com/gin-gonic/gin
/app # 

32、找到了根源,当前容器里的 Go 版本是 1.23.12,Gin v1.12.0 需要 Go ≥ 1.25.0,所以即使执行了 go get . 或 go get github.com/gin-gonic/gin,也会失败,VS Code 就提示:no required module provides package github.com/gin-gonic/gin。

33、编辑 docker-compose.yml,升级 Go 镜像版本,golang:1.23-alpine 修改为 golang:1.26-alpine



version: '3.8'

services:
  go:
    image: golang:1.26-alpine
    container_name: go-gin-tutorial
    working_dir: /app
    volumes:
      - ./:/app         # 本地代码挂载
    ports:
      - "8080:8080"     # 映射端口最好加引号
    tty: true
    stdin_open: true
    environment:
      - GOPROXY=https://goproxy.io,direct
    command: tail -f /dev/null   # 容器启动后保持运行状态


34、容器重建流程(VS Code 内完成)
修改 docker-compose.yml 升级 Go 镜像版本
命令面板 Ctrl+Shift+P → 输入:
Dev Containers: Rebuild and Reopen in Container
VS Code 会销毁旧容器并基于新镜像创建新容器
打开终端执行第 4 步命令,Gin 环境就绪

✅ 工作区 .code-workspace 和本地代码不会丢失
✅ 容器里的 Go 版本和 Gin 依赖都是最新

35、再次执行 go get . 命令,未提示版本问题,正确执行。


/app # go get .
go: downloading github.com/gin-gonic/gin v1.12.0
go: downloading github.com/gin-contrib/sse v1.1.0
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading golang.org/x/net v0.51.0
go: downloading github.com/quic-go/quic-go v0.59.0
go: downloading github.com/go-playground/validator/v10 v10.30.1
go: downloading github.com/goccy/go-yaml v1.19.2
go: downloading github.com/pelletier/go-toml/v2 v2.2.4
go: downloading github.com/bytedance/sonic v1.15.0
go: downloading github.com/ugorji/go/codec v1.3.1
go: downloading github.com/goccy/go-json v0.10.5
go: downloading go.mongodb.org/mongo-driver/v2 v2.5.0
go: downloading github.com/json-iterator/go v1.1.12
go: downloading google.golang.org/protobuf v1.36.10
go: downloading golang.org/x/sys v0.41.0
go: downloading github.com/quic-go/qpack v0.6.0
go: downloading github.com/gabriel-vasile/mimetype v1.4.12
go: downloading github.com/go-playground/universal-translator v0.18.1
go: downloading github.com/leodido/go-urn v1.4.0
go: downloading golang.org/x/crypto v0.48.0
go: downloading golang.org/x/text v0.34.0
go: downloading github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
go: downloading github.com/modern-go/reflect2 v1.0.2
go: downloading github.com/go-playground/locales v0.14.1
go: downloading github.com/bytedance/gopkg v0.1.3
go: downloading github.com/cloudwego/base64x v0.1.6
go: downloading golang.org/x/arch v0.22.0
go: downloading github.com/klauspost/cpuid/v2 v2.3.0
go: downloading github.com/bytedance/sonic/loader v0.5.0
go: downloading github.com/twitchyliquid64/golang-asm v0.15.1
go: upgraded go 1.23.12 => 1.25.0
go: added github.com/bytedance/gopkg v0.1.3
go: added github.com/bytedance/sonic v1.15.0
go: added github.com/bytedance/sonic/loader v0.5.0
go: added github.com/cloudwego/base64x v0.1.6
go: added github.com/gabriel-vasile/mimetype v1.4.12
go: added github.com/gin-contrib/sse v1.1.0
go: added github.com/gin-gonic/gin v1.12.0
go: added github.com/go-playground/locales v0.14.1
go: added github.com/go-playground/universal-translator v0.18.1
go: added github.com/go-playground/validator/v10 v10.30.1
go: added github.com/goccy/go-json v0.10.5
go: added github.com/goccy/go-yaml v1.19.2
go: added github.com/json-iterator/go v1.1.12
go: added github.com/klauspost/cpuid/v2 v2.3.0
go: added github.com/leodido/go-urn v1.4.0
go: added github.com/mattn/go-isatty v0.0.20
go: added github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
go: added github.com/modern-go/reflect2 v1.0.2
go: added github.com/pelletier/go-toml/v2 v2.2.4
go: added github.com/quic-go/qpack v0.6.0
go: added github.com/quic-go/quic-go v0.59.0
go: added github.com/twitchyliquid64/golang-asm v0.15.1
go: added github.com/ugorji/go/codec v1.3.1
go: added go.mongodb.org/mongo-driver/v2 v2.5.0
go: added golang.org/x/arch v0.22.0
go: added golang.org/x/crypto v0.48.0
go: added golang.org/x/net v0.51.0
go: added golang.org/x/sys v0.41.0
go: added golang.org/x/text v0.34.0
go: added google.golang.org/protobuf v1.36.10
/app # 

36、在包含 main.go 的目录中,从命令行运行代码。使用点号参数表示“在当前目录中运行代码”。



/app # go run .




代码运行后,就拥有了一个可以向其发送请求的 HTTP 服务器。

37、Windows 浏览器访问:http://localhost:8080/albums ,响应状态:(失败)net::ERR_EMPTY_RESPONSE。修改 main.go,router.Run(“0.0.0.0:8080”) // 监听所有网卡。显示预先提供给服务的数据。如图13

Windows 浏览器访问:http://localhost:8080/albums


[
    {
        "id": "1",
        "title": "Blue Train",
        "artist": "John Coltrane",
        "price": 56.99
    },
    {
        "id": "2",
        "title": "Jeru",
        "artist": "Gerry Mulligan",
        "price": 17.99
    },
    {
        "id": "3",
        "title": "Sarah Vaughan and Clifford Brown",
        "artist": "Sarah Vaughan",
        "price": 39.99
    }
]

68、生成 GitHub 的 Personal Access Token(个人访问令牌)。
我30 秒手把手教生成 Token,一次搞定,以后再也不用输密码!
一步一步生成 GitHub Token
打开 GitHub 右上角头像 → Settings
左侧最下面 → Developer settings
点击 Personal access tokens → Tokens (classic)
点击右上角 Generate new token → Generate new token (classic)
填写:
Note:填 go-gin-learning(随便写,方便自己记)
Expiration:选 90 days 或者 No expiration
Select scopes:勾选 repo(全勾第一项就行)
拉到最下面 → Generate token
复制生成的一串以 ghp_ 开头的代码(只显示一次,关掉就没了!)如图14

生成 GitHub 的 Personal Access Token(个人访问令牌)

69、决定将代码提交至 GitHub,创建仓库:https://github.com/shuijingwan/go-gin-learning 。在容器里的 /app 目录(vs code 下的终端),直接执行下面 全部命令:

<pre class="wp-block-syntaxhighlighter-code">

/app # git init
/bin/sh: git: not found
/app # apk add git
( 1/13) Installing brotli-libs (1.2.0-r0)
( 2/13) Installing c-ares (1.34.6-r0)
( 3/13) Installing libunistring (1.4.1-r0)
( 4/13) Installing libidn2 (2.3.8-r0)
( 5/13) Installing nghttp2-libs (1.68.0-r0)
( 6/13) Installing nghttp3 (1.13.1-r0)
( 7/13) Installing libpsl (0.21.5-r3)
( 8/13) Installing zstd-libs (1.5.7-r2)
( 9/13) Installing libcurl (8.17.0-r1)
(10/13) Installing libexpat (2.7.5-r0)
(11/13) Installing pcre2 (10.47-r0)
(12/13) Installing git (2.52.0-r0)
(13/13) Installing git-init-template (2.52.0-r0)
Executing busybox-1.37.0-r30.trigger
OK: 23.4 MiB in 32 packages
/app # git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: will change to "main" in Git 3.0. To configure the initial branch name
hint: to use in all of your new repositories, which will suppress this warning,
hint: call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
hint:
hint: Disable this message with "git config set advice.defaultBranchName false"
Initialized empty Git repository in /app/.git/
/app # echo "# go-gin-learning" >> README.md
/app # cat > .gitignore << 'EOF'
> /bin
> /dist
> /vendor/
> .env
> .env.test
> .idea/
> .vscode/
> .DS_Store
> EOF
/app # git add .
fatal: detected dubious ownership in repository at '/app'
To add an exception for this directory, call:

        git config --global --add safe.directory /app
/app # git config --global --add safe.directory /app
/app # git add .
/app # git commit -m "feat: 初始化Go Gin项目,集成容器开发环境"
Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got 'root@e4dccfba01fb.(none)')
/app # git config --global user.name "shuijingwan"
/app # git config --global user.email "shuijingwanwq@163.com"
/app # git commit -m "feat: 初始化Go Gin项目,集成容器开发环境"
[master (root-commit) 023e682] feat: 初始化Go Gin项目,集成容器开发环境
 7 files changed, 188 insertions(+)
 create mode 100644 .devcontainer/devcontainer.json
 create mode 100644 .gitignore
 create mode 100644 README.md
 create mode 100644 docker-compose.yml
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 main.go
/app # git branch -M main
/app # git remote add origin https://github.com/shuijingwan/go-gin-learning.git
/app # git push -u origin main
Username for 'https://github.com': shuijingwan
Password for 'https://shuijingwan@github.com': 
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 4 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (10/10), 5.21 KiB | 313.00 KiB/s, done.
Total 10 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 100% (1/1), done.
To https://github.com/shuijingwan/go-gin-learning.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.
/app # 
</pre>

70、查看 GitHub 上的仓库目录,发现 .devcontainer 被提交了。现在仓库目录结构如下

/go-gin-learning
├── .devcontainer/ ✅ 应该提交
│ └── devcontainer.json
├── .gitignore ✅ 应该提交
├── README.md ✅ 应该提交
├── docker-compose.yml ✅ 应该提交
├── go.mod
├── go.sum
└── main.go

]]>
https://www.shuijingwanwq.com/2026/04/03/9416/feed/ 0
在 WSL-Ubuntu 中 Docker 下安装 Hyperf https://www.shuijingwanwq.com/2026/03/20/9355/ https://www.shuijingwanwq.com/2026/03/20/9355/#respond Fri, 20 Mar 2026 03:32:36 +0000 https://www.shuijingwanwq.com/?p=9355 Post Views: 153

1、第一步:在 WSL 里创建本地项目目录(比如 ~/wwwroot/hyperf-skeleton)



ubuntu@DESKTOP-H4MGQIU:~/wwwroot$ mkdir -p ~/wwwroot/hyperf-skeleton


2、修改挂载目录,适配本地目录的路径。参考:Hyperf 官方文档 Docker 下开发



ubuntu@DESKTOP-H4MGQIU:~/wwwroot$ docker run --name hyperf \
-v ~/wwwroot/hyperf-skeleton:/data/project \
-w /data/project \
-p 9501:9501 -it \
--privileged -u root \
--entrypoint /bin/sh \
hyperf/hyperf:8.1-alpine-v3.18-swoole
Unable to find image 'hyperf/hyperf:8.1-alpine-v3.18-swoole' locally
8.1-alpine-v3.18-swoole: Pulling from hyperf/hyperf
5715dfbf9dd7: Pull complete
bfa571bff938: Pull complete
58de83433912: Pull complete
44cf07d57ee4: Pull complete
471a4d85c85c: Download complete
Digest: sha256:3cbda08f3892507d760d31bba8b68aeb4b916333055bbf05cf46ea18c67fc935
Status: Downloaded newer image for hyperf/hyperf:8.1-alpine-v3.18-swoole
/data/project #


3、在容器内创建项目 ,创建项目失败,报错:Your requirements could not be resolved to an installable set of packages.。如图1

在容器内创建项目 ,创建项目失败,报错:Your requirements could not be resolved to an installable set of packages.


/data/project # composer create-project hyperf/hyperf-skeleton
Creating a "hyperf/hyperf-skeleton" project at "./hyperf-skeleton"
Installing hyperf/hyperf-skeleton (v3.1.3)
<ul>
 	<li>Downloading hyperf/hyperf-skeleton (v3.1.3)</li>
 	<li>Installing hyperf/hyperf-skeleton (v3.1.3): Extracting archive
Created project in /data/project/hyperf-skeleton</li>
</ul>
<blockquote>@php -r "file_exists('.env') || copy('.env.example', '.env');"
Installer\Script::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies</blockquote>
What time zone do you want to setup ?
[n] Default time zone for php.ini
Make your selection or type a time zone name, like Asia/Shanghai (n):
Asia/Shanghai

Do you want to use Database (MySQL Client) ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y
<ul>
 	<li>Adding package hyperf/database (~3.1.0)</li>
 	<li>Adding package hyperf/db-connection (~3.1.0)

Do you want to use Redis Client ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y</li>
 	<li>Adding package hyperf/redis (~3.1.0)</li>
 	<li>Copying config/autoload/redis.php

Which RPC protocol do you want to use ?
[1] JSON RPC with Service Governance
[2] JSON RPC
[3] gRPC
[n] None of the above
Make your selection or type a composer package name and version (n): n

Which config center do you want to use ?
[1] Apollo
[2] Aliyun ACM
[3] ETCD
[4] Nacos
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/constants component ?
[y] yes
[n] None of the above
Make your selection (n): y</li>
 	<li>Adding package hyperf/constants (~3.1.0)</li>
 	<li>Copying app/Constants/ErrorCode.php</li>
 	<li>Copying app/Exception/BusinessException.php

Do you want to use hyperf/async-queue component ? (A simple redis queue component)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): y</li>
 	<li>Adding package hyperf/async-queue (~3.1.0)</li>
 	<li>Copying config/autoload/async_queue.php</li>
 	<li>Copying app/Process/AsyncQueueConsumer.php</li>
 	<li>Copying app/Listener/QueueHandleListener.php</li>
 	<li>Copying config/autoload/redis.php

Do you want to use hyperf/amqp component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/model-cache component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/elasticsearch component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/tracer component ? (An open tracing protocol component, adapte with Zipkin etc.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use pestphp/pest component ? (Pest is a testing framework with a focus on simplicity,
meticulously designed to bring back the joy of testing in PHP.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): y</li>
 	<li>Adding package pestphp/pest (^2.34)</li>
 	<li>Copying test/Feature/ExampleTest.php</li>
 	<li>Copying test/Unit/ExampleTest.php</li>
 	<li>Copying test/Pest.php</li>
 	<li>Copying test/TestCase.php
Remove installer
Removing composer.lock from .gitignore
Removing Expressive installer classes, configuration, tests and docs
Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.

Problem 1
<ul>
 	<li>Root composer.json requires pestphp/pest ^2.34 -&gt; satisfiable by pestphp/pest[v2.34.0, …, 2.x-dev].</li>
 	<li>brianium/paratest[v7.9.0, …, v7.11.2] require php ~8.3.0 || ~8.4.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.12.0, …, 7.x-dev] require php ~8.3.0 || ~8.4.0 || ~8.5.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.4.3, …, v7.5.7] require php ~8.2.0 || ~8.3.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.4.8, …, v7.8.4] require php ~8.2.0 || ~8.3.0 || ~8.4.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>brianium/paratest[v7.8.5, …, 7.8.x-dev] require php ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>pestphp/pest v2.34.0 conflicts with phpunit/phpunit 10.5.63.</li>
 	<li>pestphp/pest v2.34.0 conflicts with phpunit/phpunit 10.5.x-dev.</li>
 	<li>pestphp/pest v2.34.0 requires phpunit/phpunit ^10.5.10 -&gt; satisfiable by phpunit/phpunit[10.5.62, 10.5.63, 10.5.x-dev].</li>
 	<li>pestphp/pest v2.34.3 requires brianium/paratest ^7.4.3 -&gt; satisfiable by brianium/paratest[v7.4.3, …, 7.x-dev].</li>
 	<li>pestphp/pest[v2.36.1, …, 2.x-dev] require php ^8.2.0 -&gt; your php version (8.1.27) does not satisfy that requirement.</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.1 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.2 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.4 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.5 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.6 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.34.9 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.35.0 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.35.1 (conflict analysis result)</li>
 	<li>Conclusion: don't install pestphp/pest v2.36.0 (conflict analysis result)</li>
</ul>
</li>
</ul>
/data/project #


4、报错核心原因如下:
PHP 版本不匹配:容器里的 PHP 是 8.1.27,但 Pest(测试框架)的新版本要求 PHP ≥ 8.2,我的 PHP 8.1 满足不了;
依赖连锁冲突:Pest 依赖的 brianium/paratest、phpunit/phpunit 等组件,也要求更高版本的 PHP,最终导致 Composer 无法解析出一套能安装的依赖包。

5、最快解决方法(放弃安装 Pest,先跑通项目),退出当前失败的安装流程:重新执行创建项目命令(跳过 Pest),选择 n。提示: Project directory “/data/project/hyperf-skeleton” is not empty.。在容器的 /data/project 目录下,执行清空命令:rm -rf ./* ./.??* 。如图2

提示: Project directory "/data/project/hyperf-skeleton" is not empty.。在容器的 /data/project 目录下,执行清空命令:rm -rf ./* ./.??*


/data/project # composer create-project hyperf/hyperf-skeleton
Creating a "hyperf/hyperf-skeleton" project at "./hyperf-skeleton"

In CreateProjectCommand.php line 368:

Project directory "/data/project/hyperf-skeleton" is not empty.

create-project [-s|--stability STABILITY] [--prefer-source] [--prefer-dist] [--prefer-install PREFER-INSTALL] [--repository REPOSITORY] [--repository-url REPOSITORY-URL] [--add-repository] [--dev] [--no-dev] [--no-custom-installers] [--no-scripts] [--no-progress] [--no-secure-http] [--keep-vcs] [--remove-vcs] [--no-install] [--no-audit] [--audit-format AUDIT-FORMAT] [--no-security-blocking] [--ignore-platform-req IGNORE-PLATFORM-REQ] [--ignore-platform-reqs] [--ask] [--] [<package> [<directory> [<version>]]]



6、在容器内创建项目 在 遇见 pestphp/pest 时,选择 n,创建项目成功。



/data/project # composer create-project hyperf/hyperf-skeleton
Creating a "hyperf/hyperf-skeleton" project at "./hyperf-skeleton"
Installing hyperf/hyperf-skeleton (v3.1.3)
<ul>
 	<li>Installing hyperf/hyperf-skeleton (v3.1.3): Extracting archive
Created project in /data/project/hyperf-skeleton</li>
</ul>
<blockquote>@php -r "file_exists('.env') || copy('.env.example', '.env');"
Installer\Script::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencies</blockquote>
What time zone do you want to setup ?
[n] Default time zone for php.ini
Make your selection or type a time zone name, like Asia/Shanghai (n):
Asia/Shanghai

Do you want to use Database (MySQL Client) ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y
<ul>
 	<li>Adding package hyperf/database (~3.1.0)</li>
 	<li>Adding package hyperf/db-connection (~3.1.0)

Do you want to use Redis Client ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (yes): y</li>
 	<li>Adding package hyperf/redis (~3.1.0)</li>
 	<li>Copying config/autoload/redis.php

Which RPC protocol do you want to use ?
[1] JSON RPC with Service Governance
[2] JSON RPC
[3] gRPC
[n] None of the above
Make your selection or type a composer package name and version (n): n

Which config center do you want to use ?
[1] Apollo
[2] Aliyun ACM
[3] ETCD
[4] Nacos
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/constants component ?
[y] yes
[n] None of the above
Make your selection (n): y</li>
 	<li>Adding package hyperf/constants (~3.1.0)</li>
 	<li>Copying app/Constants/ErrorCode.php</li>
 	<li>Copying app/Exception/BusinessException.php

Do you want to use hyperf/async-queue component ? (A simple redis queue component)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): y</li>
 	<li>Adding package hyperf/async-queue (~3.1.0)</li>
 	<li>Copying config/autoload/async_queue.php</li>
 	<li>Copying app/Process/AsyncQueueConsumer.php</li>
 	<li>Copying app/Listener/QueueHandleListener.php</li>
 	<li>Copying config/autoload/redis.php

Do you want to use hyperf/amqp component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/model-cache component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/elasticsearch component ?
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use hyperf/tracer component ? (An open tracing protocol component, adapte with Zipkin etc.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n

Do you want to use pestphp/pest component ? (Pest is a testing framework with a focus on simplicity,
meticulously designed to bring back the joy of testing in PHP.)
[y] yes
[n] None of the above
Make your selection or type a composer package name and version (n): n
Remove installer
Removing composer.lock from .gitignore
Removing Expressive installer classes, configuration, tests and docs
Loading composer repositories with package information
Updating dependencies
Lock file operations: 135 installs, 0 updates, 0 removals</li>
 	<li>Locking carbonphp/carbon-doctrine-types (3.2.0)</li>
 	<li>Locking clue/ndjson-react (v1.3.0)</li>
 	<li>Locking composer/pcre (3.3.2)</li>
 	<li>Locking composer/semver (3.4.4)</li>
 	<li>Locking composer/xdebug-handler (3.0.5)</li>
 	<li>Locking doctrine/inflector (2.1.0)</li>
 	<li>Locking doctrine/instantiator (1.5.0)</li>
 	<li>Locking evenement/evenement (v3.0.2)</li>
 	<li>Locking fidry/cpu-core-counter (1.3.0)</li>
 	<li>Locking fig/http-message-util (1.1.5)</li>
 	<li>Locking friendsofphp/php-cs-fixer (v3.94.2)</li>
 	<li>Locking graham-campbell/result-type (v1.1.4)</li>
 	<li>Locking guzzlehttp/guzzle (7.10.0)</li>
 	<li>Locking guzzlehttp/promises (2.3.0)</li>
 	<li>Locking guzzlehttp/psr7 (2.9.0)</li>
 	<li>Locking hamcrest/hamcrest-php (v2.1.1)</li>
 	<li>Locking hyperf/async-queue (v3.1.64)</li>
 	<li>Locking hyperf/cache (v3.1.67)</li>
 	<li>Locking hyperf/code-parser (v3.1.63)</li>
 	<li>Locking hyperf/codec (v3.1.63)</li>
 	<li>Locking hyperf/collection (v3.1.64)</li>
 	<li>Locking hyperf/command (v3.1.64)</li>
 	<li>Locking hyperf/conditionable (v3.1.63)</li>
 	<li>Locking hyperf/config (v3.1.63)</li>
 	<li>Locking hyperf/constants (v3.1.63)</li>
 	<li>Locking hyperf/context (v3.1.63)</li>
 	<li>Locking hyperf/contract (v3.1.63)</li>
 	<li>Locking hyperf/coordinator (v3.1.63)</li>
 	<li>Locking hyperf/coroutine (v3.1.65)</li>
 	<li>Locking hyperf/database (v3.1.67)</li>
 	<li>Locking hyperf/db-connection (v3.1.66)</li>
 	<li>Locking hyperf/devtool (v3.1.66)</li>
 	<li>Locking hyperf/di (v3.1.67)</li>
 	<li>Locking hyperf/dispatcher (v3.1.63)</li>
 	<li>Locking hyperf/engine (v2.15.0)</li>
 	<li>Locking hyperf/engine-contract (v1.14.0)</li>
 	<li>Locking hyperf/event (v3.1.63)</li>
 	<li>Locking hyperf/exception-handler (v3.1.63)</li>
 	<li>Locking hyperf/framework (v3.1.63)</li>
 	<li>Locking hyperf/guzzle (v3.1.66)</li>
 	<li>Locking hyperf/http-message (v3.1.65)</li>
 	<li>Locking hyperf/http-server (v3.1.65)</li>
 	<li>Locking hyperf/laminas-mime (v3.0.0)</li>
 	<li>Locking hyperf/logger (v3.1.63)</li>
 	<li>Locking hyperf/macroable (v3.1.63)</li>
 	<li>Locking hyperf/memory (v3.1.63)</li>
 	<li>Locking hyperf/model-listener (v3.1.63)</li>
 	<li>Locking hyperf/pipeline (v3.1.63)</li>
 	<li>Locking hyperf/pool (v3.1.66)</li>
 	<li>Locking hyperf/process (v3.1.63)</li>
 	<li>Locking hyperf/redis (v3.1.66)</li>
 	<li>Locking hyperf/serializer (v3.1.63)</li>
 	<li>Locking hyperf/server (v3.1.63)</li>
 	<li>Locking hyperf/stdlib (v3.1.63)</li>
 	<li>Locking hyperf/stringable (v3.1.65)</li>
 	<li>Locking hyperf/support (v3.1.65)</li>
 	<li>Locking hyperf/tappable (v3.1.63)</li>
 	<li>Locking hyperf/testing (v3.1.63)</li>
 	<li>Locking laminas/laminas-stdlib (3.20.0)</li>
 	<li>Locking mockery/mockery (1.6.12)</li>
 	<li>Locking monolog/monolog (3.10.0)</li>
 	<li>Locking myclabs/deep-copy (1.13.4)</li>
 	<li>Locking nesbot/carbon (2.73.0)</li>
 	<li>Locking nikic/fast-route (v1.3.0)</li>
 	<li>Locking nikic/php-parser (v4.19.5)</li>
 	<li>Locking phar-io/manifest (2.0.4)</li>
 	<li>Locking phar-io/version (3.2.1)</li>
 	<li>Locking php-di/phpdoc-reader (2.2.1)</li>
 	<li>Locking phpoption/phpoption (1.9.5)</li>
 	<li>Locking phpstan/phpstan (1.12.33)</li>
 	<li>Locking phpunit/php-code-coverage (10.1.16)</li>
 	<li>Locking phpunit/php-file-iterator (4.1.0)</li>
 	<li>Locking phpunit/php-invoker (4.0.0)</li>
 	<li>Locking phpunit/php-text-template (3.0.1)</li>
 	<li>Locking phpunit/php-timer (6.0.0)</li>
 	<li>Locking phpunit/phpunit (10.5.63)</li>
 	<li>Locking psr/clock (1.0.0)</li>
 	<li>Locking psr/container (2.0.2)</li>
 	<li>Locking psr/event-dispatcher (1.0.0)</li>
 	<li>Locking psr/http-client (1.0.3)</li>
 	<li>Locking psr/http-factory (1.1.0)</li>
 	<li>Locking psr/http-message (2.0)</li>
 	<li>Locking psr/http-server-handler (1.0.2)</li>
 	<li>Locking psr/http-server-middleware (1.0.2)</li>
 	<li>Locking psr/log (3.0.2)</li>
 	<li>Locking psr/simple-cache (3.0.0)</li>
 	<li>Locking ralouphie/getallheaders (3.0.3)</li>
 	<li>Locking react/cache (v1.2.0)</li>
 	<li>Locking react/child-process (v0.6.7)</li>
 	<li>Locking react/dns (v1.14.0)</li>
 	<li>Locking react/event-loop (v1.6.0)</li>
 	<li>Locking react/promise (v3.3.0)</li>
 	<li>Locking react/socket (v1.17.0)</li>
 	<li>Locking react/stream (v1.4.0)</li>
 	<li>Locking sebastian/cli-parser (2.0.1)</li>
 	<li>Locking sebastian/code-unit (2.0.0)</li>
 	<li>Locking sebastian/code-unit-reverse-lookup (3.0.0)</li>
 	<li>Locking sebastian/comparator (5.0.5)</li>
 	<li>Locking sebastian/complexity (3.2.0)</li>
 	<li>Locking sebastian/diff (5.1.1)</li>
 	<li>Locking sebastian/environment (6.1.0)</li>
 	<li>Locking sebastian/exporter (5.1.4)</li>
 	<li>Locking sebastian/global-state (6.0.2)</li>
 	<li>Locking sebastian/lines-of-code (2.0.2)</li>
 	<li>Locking sebastian/object-enumerator (5.0.0)</li>
 	<li>Locking sebastian/object-reflector (3.0.0)</li>
 	<li>Locking sebastian/recursion-context (5.0.1)</li>
 	<li>Locking sebastian/type (4.0.0)</li>
 	<li>Locking sebastian/version (4.0.1)</li>
 	<li>Locking swoole/ide-helper (5.1.8)</li>
 	<li>Locking swow/psr7-plus (v1.1.2)</li>
 	<li>Locking symfony/console (v6.4.35)</li>
 	<li>Locking symfony/deprecation-contracts (v3.6.0)</li>
 	<li>Locking symfony/event-dispatcher (v6.4.32)</li>
 	<li>Locking symfony/event-dispatcher-contracts (v3.6.0)</li>
 	<li>Locking symfony/filesystem (v6.4.34)</li>
 	<li>Locking symfony/finder (v6.4.34)</li>
 	<li>Locking symfony/http-foundation (v6.4.35)</li>
 	<li>Locking symfony/options-resolver (v6.4.30)</li>
 	<li>Locking symfony/polyfill-ctype (v1.33.0)</li>
 	<li>Locking symfony/polyfill-intl-grapheme (v1.33.0)</li>
 	<li>Locking symfony/polyfill-intl-normalizer (v1.33.0)</li>
 	<li>Locking symfony/polyfill-mbstring (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php80 (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php81 (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php83 (v1.33.0)</li>
 	<li>Locking symfony/polyfill-php84 (v1.33.0)</li>
 	<li>Locking symfony/process (v6.4.33)</li>
 	<li>Locking symfony/service-contracts (v3.6.1)</li>
 	<li>Locking symfony/stopwatch (v6.4.24)</li>
 	<li>Locking symfony/string (v6.4.34)</li>
 	<li>Locking symfony/translation (v6.4.34)</li>
 	<li>Locking symfony/translation-contracts (v3.6.1)</li>
 	<li>Locking theseer/tokenizer (1.3.1)</li>
 	<li>Locking vlucas/phpdotenv (v5.6.3)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 135 installs, 0 updates, 0 removals</li>
 	<li>Downloading composer/pcre (3.3.2)</li>
 	<li>Downloading doctrine/inflector (2.1.0)</li>
 	<li>Downloading doctrine/instantiator (1.5.0)</li>
 	<li>Downloading symfony/deprecation-contracts (v3.6.0)</li>
 	<li>Downloading psr/container (2.0.2)</li>
 	<li>Downloading symfony/service-contracts (v3.6.1)</li>
 	<li>Downloading symfony/stopwatch (v6.4.24)</li>
 	<li>Downloading symfony/process (v6.4.33)</li>
 	<li>Downloading symfony/polyfill-php84 (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-php81 (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-php80 (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-mbstring (v1.33.0)</li>
 	<li>Downloading symfony/options-resolver (v6.4.30)</li>
 	<li>Downloading symfony/finder (v6.4.34)</li>
 	<li>Downloading symfony/polyfill-ctype (v1.33.0)</li>
 	<li>Downloading symfony/filesystem (v6.4.34)</li>
 	<li>Downloading psr/event-dispatcher (1.0.0)</li>
 	<li>Downloading symfony/event-dispatcher-contracts (v3.6.0)</li>
 	<li>Downloading symfony/event-dispatcher (v6.4.32)</li>
 	<li>Downloading symfony/polyfill-intl-normalizer (v1.33.0)</li>
 	<li>Downloading symfony/polyfill-intl-grapheme (v1.33.0)</li>
 	<li>Downloading symfony/string (v6.4.34)</li>
 	<li>Downloading symfony/console (v6.4.35)</li>
 	<li>Downloading sebastian/diff (5.1.1)</li>
 	<li>Downloading react/event-loop (v1.6.0)</li>
 	<li>Downloading evenement/evenement (v3.0.2)</li>
 	<li>Downloading react/stream (v1.4.0)</li>
 	<li>Downloading react/promise (v3.3.0)</li>
 	<li>Downloading react/cache (v1.2.0)</li>
 	<li>Downloading react/dns (v1.14.0)</li>
 	<li>Downloading react/socket (v1.17.0)</li>
 	<li>Downloading react/child-process (v0.6.7)</li>
 	<li>Downloading fidry/cpu-core-counter (1.3.0)</li>
 	<li>Downloading psr/log (3.0.2)</li>
 	<li>Downloading composer/xdebug-handler (3.0.5)</li>
 	<li>Downloading composer/semver (3.4.4)</li>
 	<li>Downloading clue/ndjson-react (v1.3.0)</li>
 	<li>Downloading friendsofphp/php-cs-fixer (v3.94.2)</li>
 	<li>Downloading guzzlehttp/promises (2.3.0)</li>
 	<li>Downloading ralouphie/getallheaders (3.0.3)</li>
 	<li>Downloading psr/http-message (2.0)</li>
 	<li>Downloading psr/http-factory (1.1.0)</li>
 	<li>Downloading guzzlehttp/psr7 (2.9.0)</li>
 	<li>Downloading hyperf/tappable (v3.1.63)</li>
 	<li>Downloading hyperf/macroable (v3.1.63)</li>
 	<li>Downloading hyperf/conditionable (v3.1.63)</li>
 	<li>Downloading hyperf/stringable (v3.1.65)</li>
 	<li>Downloading hyperf/contract (v3.1.63)</li>
 	<li>Downloading hyperf/collection (v3.1.64)</li>
 	<li>Downloading hyperf/engine-contract (v1.14.0)</li>
 	<li>Downloading hyperf/engine (v2.15.0)</li>
 	<li>Downloading hyperf/context (v3.1.63)</li>
 	<li>Downloading hyperf/coroutine (v3.1.65)</li>
 	<li>Downloading hyperf/support (v3.1.65)</li>
 	<li>Downloading phpoption/phpoption (1.9.5)</li>
 	<li>Downloading graham-campbell/result-type (v1.1.4)</li>
 	<li>Downloading vlucas/phpdotenv (v5.6.3)</li>
 	<li>Downloading php-di/phpdoc-reader (2.2.1)</li>
 	<li>Downloading nikic/php-parser (v4.19.5)</li>
 	<li>Downloading hyperf/stdlib (v3.1.63)</li>
 	<li>Downloading hyperf/pipeline (v3.1.63)</li>
 	<li>Downloading hyperf/code-parser (v3.1.63)</li>
 	<li>Downloading hyperf/di (v3.1.67)</li>
 	<li>Downloading hyperf/coordinator (v3.1.63)</li>
 	<li>Downloading hyperf/command (v3.1.64)</li>
 	<li>Downloading hyperf/codec (v3.1.63)</li>
 	<li>Downloading hyperf/async-queue (v3.1.64)</li>
 	<li>Downloading psr/simple-cache (3.0.0)</li>
 	<li>Downloading hyperf/cache (v3.1.67)</li>
 	<li>Downloading hyperf/config (v3.1.63)</li>
 	<li>Downloading hyperf/constants (v3.1.63)</li>
 	<li>Downloading hyperf/pool (v3.1.66)</li>
 	<li>Downloading hyperf/event (v3.1.63)</li>
 	<li>Downloading symfony/translation-contracts (v3.6.1)</li>
 	<li>Downloading symfony/translation (v6.4.34)</li>
 	<li>Downloading psr/clock (1.0.0)</li>
 	<li>Downloading carbonphp/carbon-doctrine-types (3.2.0)</li>
 	<li>Downloading nesbot/carbon (2.73.0)</li>
 	<li>Downloading hyperf/database (v3.1.67)</li>
 	<li>Downloading hyperf/model-listener (v3.1.63)</li>
 	<li>Downloading fig/http-message-util (1.1.5)</li>
 	<li>Downloading hyperf/framework (v3.1.63)</li>
 	<li>Downloading hyperf/db-connection (v3.1.66)</li>
 	<li>Downloading hyperf/devtool (v3.1.66)</li>
 	<li>Downloading psr/http-client (1.0.3)</li>
 	<li>Downloading swow/psr7-plus (v1.1.2)</li>
 	<li>Downloading laminas/laminas-stdlib (3.20.0)</li>
 	<li>Downloading hyperf/laminas-mime (v3.0.0)</li>
 	<li>Downloading hyperf/http-message (v3.1.65)</li>
 	<li>Downloading psr/http-server-handler (1.0.2)</li>
 	<li>Downloading psr/http-server-middleware (1.0.2)</li>
 	<li>Downloading hyperf/dispatcher (v3.1.63)</li>
 	<li>Downloading hyperf/exception-handler (v3.1.63)</li>
 	<li>Downloading guzzlehttp/guzzle (7.10.0)</li>
 	<li>Downloading hyperf/guzzle (v3.1.66)</li>
 	<li>Downloading monolog/monolog (3.10.0)</li>
 	<li>Downloading hyperf/logger (v3.1.63)</li>
 	<li>Downloading hyperf/memory (v3.1.63)</li>
 	<li>Downloading hyperf/process (v3.1.63)</li>
 	<li>Downloading hyperf/redis (v3.1.66)</li>
 	<li>Downloading hyperf/serializer (v3.1.63)</li>
 	<li>Downloading hyperf/server (v3.1.63)</li>
 	<li>Downloading symfony/polyfill-php83 (v1.33.0)</li>
 	<li>Downloading symfony/http-foundation (v6.4.35)</li>
 	<li>Downloading sebastian/version (4.0.1)</li>
 	<li>Downloading sebastian/type (4.0.0)</li>
 	<li>Downloading sebastian/recursion-context (5.0.1)</li>
 	<li>Downloading sebastian/object-reflector (3.0.0)</li>
 	<li>Downloading sebastian/object-enumerator (5.0.0)</li>
 	<li>Downloading sebastian/global-state (6.0.2)</li>
 	<li>Downloading sebastian/exporter (5.1.4)</li>
 	<li>Downloading sebastian/environment (6.1.0)</li>
 	<li>Downloading sebastian/comparator (5.0.5)</li>
 	<li>Downloading sebastian/code-unit (2.0.0)</li>
 	<li>Downloading sebastian/cli-parser (2.0.1)</li>
 	<li>Downloading phpunit/php-timer (6.0.0)</li>
 	<li>Downloading phpunit/php-text-template (3.0.1)</li>
 	<li>Downloading phpunit/php-invoker (4.0.0)</li>
 	<li>Downloading phpunit/php-file-iterator (4.1.0)</li>
 	<li>Downloading theseer/tokenizer (1.3.1)</li>
 	<li>Downloading sebastian/lines-of-code (2.0.2)</li>
 	<li>Downloading sebastian/complexity (3.2.0)</li>
 	<li>Downloading sebastian/code-unit-reverse-lookup (3.0.0)</li>
 	<li>Downloading phpunit/php-code-coverage (10.1.16)</li>
 	<li>Downloading phar-io/version (3.2.1)</li>
 	<li>Downloading phar-io/manifest (2.0.4)</li>
 	<li>Downloading myclabs/deep-copy (1.13.4)</li>
 	<li>Downloading phpunit/phpunit (10.5.63)</li>
 	<li>Downloading nikic/fast-route (v1.3.0)</li>
 	<li>Downloading hyperf/http-server (v3.1.65)</li>
 	<li>Downloading hyperf/testing (v3.1.63)</li>
 	<li>Downloading hamcrest/hamcrest-php (v2.1.1)</li>
 	<li>Downloading mockery/mockery (1.6.12)</li>
 	<li>Downloading phpstan/phpstan (1.12.33)</li>
 	<li>Downloading swoole/ide-helper (5.1.8)</li>
 	<li>Installing composer/pcre (3.3.2): Extracting archive</li>
 	<li>Installing doctrine/inflector (2.1.0): Extracting archive</li>
 	<li>Installing doctrine/instantiator (1.5.0): Extracting archive</li>
 	<li>Installing symfony/deprecation-contracts (v3.6.0): Extracting archive</li>
 	<li>Installing psr/container (2.0.2): Extracting archive</li>
 	<li>Installing symfony/service-contracts (v3.6.1): Extracting archive</li>
 	<li>Installing symfony/stopwatch (v6.4.24): Extracting archive</li>
 	<li>Installing symfony/process (v6.4.33): Extracting archive</li>
 	<li>Installing symfony/polyfill-php84 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-php81 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-php80 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-mbstring (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/options-resolver (v6.4.30): Extracting archive</li>
 	<li>Installing symfony/finder (v6.4.34): Extracting archive</li>
 	<li>Installing symfony/polyfill-ctype (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/filesystem (v6.4.34): Extracting archive</li>
 	<li>Installing psr/event-dispatcher (1.0.0): Extracting archive</li>
 	<li>Installing symfony/event-dispatcher-contracts (v3.6.0): Extracting archive</li>
 	<li>Installing symfony/event-dispatcher (v6.4.32): Extracting archive</li>
 	<li>Installing symfony/polyfill-intl-normalizer (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/polyfill-intl-grapheme (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/string (v6.4.34): Extracting archive</li>
 	<li>Installing symfony/console (v6.4.35): Extracting archive</li>
 	<li>Installing sebastian/diff (5.1.1): Extracting archive</li>
 	<li>Installing react/event-loop (v1.6.0): Extracting archive</li>
 	<li>Installing evenement/evenement (v3.0.2): Extracting archive</li>
 	<li>Installing react/stream (v1.4.0): Extracting archive</li>
 	<li>Installing react/promise (v3.3.0): Extracting archive</li>
 	<li>Installing react/cache (v1.2.0): Extracting archive</li>
 	<li>Installing react/dns (v1.14.0): Extracting archive</li>
 	<li>Installing react/socket (v1.17.0): Extracting archive</li>
 	<li>Installing react/child-process (v0.6.7): Extracting archive</li>
 	<li>Installing fidry/cpu-core-counter (1.3.0): Extracting archive</li>
 	<li>Installing psr/log (3.0.2): Extracting archive</li>
 	<li>Installing composer/xdebug-handler (3.0.5): Extracting archive</li>
 	<li>Installing composer/semver (3.4.4): Extracting archive</li>
 	<li>Installing clue/ndjson-react (v1.3.0): Extracting archive</li>
 	<li>Installing friendsofphp/php-cs-fixer (v3.94.2): Extracting archive</li>
 	<li>Installing guzzlehttp/promises (2.3.0): Extracting archive</li>
 	<li>Installing ralouphie/getallheaders (3.0.3): Extracting archive</li>
 	<li>Installing psr/http-message (2.0): Extracting archive</li>
 	<li>Installing psr/http-factory (1.1.0): Extracting archive</li>
 	<li>Installing guzzlehttp/psr7 (2.9.0): Extracting archive</li>
 	<li>Installing hyperf/tappable (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/macroable (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/conditionable (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/stringable (v3.1.65): Extracting archive</li>
 	<li>Installing hyperf/contract (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/collection (v3.1.64): Extracting archive</li>
 	<li>Installing hyperf/engine-contract (v1.14.0): Extracting archive</li>
 	<li>Installing hyperf/engine (v2.15.0): Extracting archive</li>
 	<li>Installing hyperf/context (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/coroutine (v3.1.65): Extracting archive</li>
 	<li>Installing hyperf/support (v3.1.65): Extracting archive</li>
 	<li>Installing phpoption/phpoption (1.9.5): Extracting archive</li>
 	<li>Installing graham-campbell/result-type (v1.1.4): Extracting archive</li>
 	<li>Installing vlucas/phpdotenv (v5.6.3): Extracting archive</li>
 	<li>Installing php-di/phpdoc-reader (2.2.1): Extracting archive</li>
 	<li>Installing nikic/php-parser (v4.19.5): Extracting archive</li>
 	<li>Installing hyperf/stdlib (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/pipeline (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/code-parser (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/di (v3.1.67): Extracting archive</li>
 	<li>Installing hyperf/coordinator (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/command (v3.1.64): Extracting archive</li>
 	<li>Installing hyperf/codec (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/async-queue (v3.1.64): Extracting archive</li>
 	<li>Installing psr/simple-cache (3.0.0): Extracting archive</li>
 	<li>Installing hyperf/cache (v3.1.67): Extracting archive</li>
 	<li>Installing hyperf/config (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/constants (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/pool (v3.1.66): Extracting archive</li>
 	<li>Installing hyperf/event (v3.1.63): Extracting archive</li>
 	<li>Installing symfony/translation-contracts (v3.6.1): Extracting archive</li>
 	<li>Installing symfony/translation (v6.4.34): Extracting archive</li>
 	<li>Installing psr/clock (1.0.0): Extracting archive</li>
 	<li>Installing carbonphp/carbon-doctrine-types (3.2.0): Extracting archive</li>
 	<li>Installing nesbot/carbon (2.73.0): Extracting archive</li>
 	<li>Installing hyperf/database (v3.1.67): Extracting archive</li>
 	<li>Installing hyperf/model-listener (v3.1.63): Extracting archive</li>
 	<li>Installing fig/http-message-util (1.1.5): Extracting archive</li>
 	<li>Installing hyperf/framework (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/db-connection (v3.1.66): Extracting archive</li>
 	<li>Installing hyperf/devtool (v3.1.66): Extracting archive</li>
 	<li>Installing psr/http-client (1.0.3): Extracting archive</li>
 	<li>Installing swow/psr7-plus (v1.1.2): Extracting archive</li>
 	<li>Installing laminas/laminas-stdlib (3.20.0): Extracting archive</li>
 	<li>Installing hyperf/laminas-mime (v3.0.0): Extracting archive</li>
 	<li>Installing hyperf/http-message (v3.1.65): Extracting archive</li>
 	<li>Installing psr/http-server-handler (1.0.2): Extracting archive</li>
 	<li>Installing psr/http-server-middleware (1.0.2): Extracting archive</li>
 	<li>Installing hyperf/dispatcher (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/exception-handler (v3.1.63): Extracting archive</li>
 	<li>Installing guzzlehttp/guzzle (7.10.0): Extracting archive</li>
 	<li>Installing hyperf/guzzle (v3.1.66): Extracting archive</li>
 	<li>Installing monolog/monolog (3.10.0): Extracting archive</li>
 	<li>Installing hyperf/logger (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/memory (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/process (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/redis (v3.1.66): Extracting archive</li>
 	<li>Installing hyperf/serializer (v3.1.63): Extracting archive</li>
 	<li>Installing hyperf/server (v3.1.63): Extracting archive</li>
 	<li>Installing symfony/polyfill-php83 (v1.33.0): Extracting archive</li>
 	<li>Installing symfony/http-foundation (v6.4.35): Extracting archive</li>
 	<li>Installing sebastian/version (4.0.1): Extracting archive</li>
 	<li>Installing sebastian/type (4.0.0): Extracting archive</li>
 	<li>Installing sebastian/recursion-context (5.0.1): Extracting archive</li>
 	<li>Installing sebastian/object-reflector (3.0.0): Extracting archive</li>
 	<li>Installing sebastian/object-enumerator (5.0.0): Extracting archive</li>
 	<li>Installing sebastian/global-state (6.0.2): Extracting archive</li>
 	<li>Installing sebastian/exporter (5.1.4): Extracting archive</li>
 	<li>Installing sebastian/environment (6.1.0): Extracting archive</li>
 	<li>Installing sebastian/comparator (5.0.5): Extracting archive</li>
 	<li>Installing sebastian/code-unit (2.0.0): Extracting archive</li>
 	<li>Installing sebastian/cli-parser (2.0.1): Extracting archive</li>
 	<li>Installing phpunit/php-timer (6.0.0): Extracting archive</li>
 	<li>Installing phpunit/php-text-template (3.0.1): Extracting archive</li>
 	<li>Installing phpunit/php-invoker (4.0.0): Extracting archive</li>
 	<li>Installing phpunit/php-file-iterator (4.1.0): Extracting archive</li>
 	<li>Installing theseer/tokenizer (1.3.1): Extracting archive</li>
 	<li>Installing sebastian/lines-of-code (2.0.2): Extracting archive</li>
 	<li>Installing sebastian/complexity (3.2.0): Extracting archive</li>
 	<li>Installing sebastian/code-unit-reverse-lookup (3.0.0): Extracting archive</li>
 	<li>Installing phpunit/php-code-coverage (10.1.16): Extracting archive</li>
 	<li>Installing phar-io/version (3.2.1): Extracting archive</li>
 	<li>Installing phar-io/manifest (2.0.4): Extracting archive</li>
 	<li>Installing myclabs/deep-copy (1.13.4): Extracting archive</li>
 	<li>Installing phpunit/phpunit (10.5.63): Extracting archive</li>
 	<li>Installing nikic/fast-route (v1.3.0): Extracting archive</li>
 	<li>Installing hyperf/http-server (v3.1.65): Extracting archive</li>
 	<li>Installing hyperf/testing (v3.1.63): Extracting archive</li>
 	<li>Installing hamcrest/hamcrest-php (v2.1.1): Extracting archive</li>
 	<li>Installing mockery/mockery (1.6.12): Extracting archive</li>
 	<li>Installing phpstan/phpstan (1.12.33): Extracting archive</li>
 	<li>Installing swoole/ide-helper (5.1.8): Extracting archive
27 package suggestions were added by new dependencies, use <code>composer suggest</code> to see details.
Generating optimized autoload files</li>
</ul>
<blockquote>rm -rf runtime/container
114 packages you are using are looking for funding.
Use the <code>composer fund</code> command to find out more!
No security vulnerability advisories found.
/data/project #


]]>
https://www.shuijingwanwq.com/2026/03/20/9355/feed/ 0
在 phpstorm 中提示:@property-read https://www.shuijingwanwq.com/2025/08/27/9286/ https://www.shuijingwanwq.com/2025/08/27/9286/#respond Wed, 27 Aug 2025 02:51:49 +0000 mailer]]> https://www.shuijingwanwq.com/?p=9286 Post Views: 67 1、在 phpstorm 中提示:@property-read 。如图1
在 phpstorm 中提示:@property-read

图1



$setting = ConventionEmailSetting::findSettingByConventionId($params['convention_id']);
if($setting && $setting->enable_custom_server == ConventionEmailSetting::ENABLE_CUSTOM_SERVER_YES){
	Yii::$app->mailer->transport = [
		'class' => 'Swift_SmtpTransport',
		'host' => $setting->host,
		'username' => $setting->username,
		'password' => $setting->password,
		'port' => $setting->port ?: '25',
		'encryption' => $setting->encryption ?: 'tls',
	];
}


2、在 common/config/main-local.php 中
<pre class="wp-block-syntaxhighlighter-code">

<?php

return [
    'components' => [
        'mailer' => [
            'class' => 'yii\swiftmailer\Mailer',
            'viewPath' => '@common/mail',
            // send all mails to a file by default. You have to set
            // 'useFileTransport' to false and configure a transport
            // for the mailer to send real emails.
            'useFileTransport' => false,
            'transport' => [
                'class' => 'Swift_SmtpTransport',
                'host' => 'smtpdm.aliyun.com',
                'username' => '',
                'password' => '',
                'port' => '25',
                'encryption' => 'tls',
            ]
        ],
    ],
];


</pre>
3、在 Yii2 中,yii\swiftmailer\Mailer 组件中对 transport 并不是通过公开属性设置的,而是通过 setTransport() 方法设置的。而 Yii::$app->mailer->transport = […] 属于 直接设置属性方式,PHPStorm 无法静态分析出你其实是通过 magic setter 间接调用了 setter 方法。这就是它提示 @property-read $transport 的原因。调整如下,不再报错。如图2
在 Yii2 中,yii\swiftmailer\Mailer  组件中对 transport 并不是通过公开属性设置的,而是通过 setTransport() 方法设置的。而 Yii::$app->mailer->transport = [...] 属于 直接设置属性方式,PHPStorm 无法静态分析出你其实是通过 magic setter 间接调用了 setter 方法。这就是它提示 @property-read $transport 的原因。调整如下,不再报错

图2



Yii::$app->mailer->setTransport([
	'class' => 'Swift_SmtpTransport',
	'host' => $setting->host,
	'username' => $setting->username,
	'password' => $setting->password,
	'port' => $setting->port ?: '25',
	'encryption' => $setting->encryption ?: 'tls',
]);


]]>
https://www.shuijingwanwq.com/2025/08/27/9286/feed/ 0
行业资讯页面缓存未及时更新的排查分析 https://www.shuijingwanwq.com/2025/08/26/9281/ https://www.shuijingwanwq.com/2025/08/26/9281/#respond Tue, 26 Aug 2025 01:46:16 +0000 https://www.shuijingwanwq.com/?p=9281 Post Views: 167 1、行业资讯页面缓存未及时更新 。当后台添加了新的文章后,前台的列表页面未及时更新,总是需要等上 5 分钟后才更新,感觉页面缓存的依赖未生效。后台的最新的两篇文章未同步在前台更新。如图1
行业资讯页面缓存未及时更新 。当后台添加了新的文章后,前台的列表页面未及时更新,总是需要等上 5 分钟后才更新,感觉页面缓存的依赖未生效。后台的最新的两篇文章未同步在前台更新。

图1

2、但是同样的程序在本地环境是正常可用的。仔细分析差异,发现生产环境的后台的文章的更新时间要较之北京时间 早 8 小时。如果 updated_at 存的是 UTC 时间(即比北京时间晚 8 小时),而你用的是北京时间做判断(比如看页面没更新),会出现: 数据实际已更新(北京时间 15:30), 但 updated_at 是 2025-06-05 07:30:25(UTC), 所以缓存判断依赖的 MAX(updated_at) 没变化,页面继续用旧缓存。


public function behaviors()
    {
        $request = Yii::$app->request;
        $alias = $request->get('alias');
        $id = $request->get('id');
        $categoryId = (int)$request->get('c', 0);
        $keyword = trim($request->get('k'));
        $tag = $request->get('tag');
        $page = (int)$request->get('page', 1);

        $detect = new MobileDetect();
        $isMobile = $detect->isMobile() ? PageElementType::DEVICE_TYPE_MOBILE : PageElementType::DEVICE_TYPE_PC;

        $behaviors = [];

        // view 页面缓存
        if (!empty($alias)) {
            $dependency = new DbDependency([
                'sql' => 'SELECT updated_at FROM articles WHERE alias = :alias LIMIT 1',
                'params' => [':alias' => $alias],
            ]);
        } elseif (!empty($id)) {
            $dependency = new DbDependency([
                'sql' => 'SELECT updated_at FROM articles WHERE id = :id LIMIT 1',
                'params' => [':id' => $id],
            ]);
        } else {
            $dependency = null;
        }

        $behaviors['pageCacheView'] = [
            'class' => PageCache::class,
            'only' => ['view'],
            'duration' => 300,
            'variations' => [$id, $alias, $isMobile],
            'dependency' => $dependency,
            'cache' => 'fileCache',
        ];

        // index 页面缓存,仅当无关键词和标签时启用
        if ($keyword === '' && empty($tag)) {

            $category = null;
            if (!empty($alias) && UrlHelper::isValidAlias($alias)) {
                $category = Yii::$app->db->cache(fn() => ArticleCategory::find()
                    ->where(['alias' => $alias, 'status' => CommonArticleCategory::STATUS_NORMAL])
                    ->limit(1)
                    ->one(), 300);
            } elseif (!empty($categoryId)) {
                $category = Yii::$app->db->cache(fn() => ArticleCategory::find()
                    ->where(['id' => $categoryId, 'status' => CommonArticleCategory::STATUS_NORMAL])
                    ->limit(1)
                    ->one(), 300);
            }

            if ($category !== null) {
                // 有分类筛选,仅依赖该分类的文章更新时间
                $indexDependency = new DbDependency([
                    'sql' => 'SELECT MAX(updated_at) FROM articles WHERE status = 1 AND category_id = :categoryId',
                    'params' => [':categoryId' => $category->id],
                ]);
            } else {
                // 无分类筛选,才依赖全表(不建议再进一步切分,否则复杂度会上升)
                $indexDependency = new DbDependency([
                    'sql' => 'SELECT MAX(updated_at) FROM articles WHERE status = 1',
                ]);
            }

            $behaviors['pageCacheIndex'] = [
                'class' => PageCache::class,
                'only' => ['index'],
                'duration' => 300,
                'variations' => [
                    $categoryId,
                    $alias,
                    $page,
                    $isMobile,
                ],
                'dependency' => $indexDependency,
                'cache' => 'fileCache',
            ];
        }

        return $behaviors;
    }


3、查看 php.ini 中的时区设置,其与 date.timezone = Asia/Shanghai 是等效的。决定修改为 date.timezone = Asia/Shanghai


date.timezone = PRC


4、设置 MySQL 连接时区,确保你的 PHP 连接数据库时设置了时区为 +08:00。在 Yii2 中可以这样做:


'components' => [
    'db' => [
        // ... 其他配置
        'on afterOpen' => function($event) {
            $event->sender->createCommand("SET time_zone = '+08:00'")->execute();
        },
    ],
],


5、编辑一篇文章后,其更新时间已经是北京时区。如图2
编辑一篇文章后,其更新时间已经是北京时区

图2

]]>
https://www.shuijingwanwq.com/2025/08/26/9281/feed/ 0