☰
【基于 Swoole+Hyperf 的微服务实战】第九周·周二:为电商核心系统填充第一个业务服务——用户服务
2026/10/6 2:46:27 网站建设 项目流程

【基于 Swoole+Hyperf 的微服务实战】第九周·周二:为电商核心系统填充第一个业务服务——用户服务


今天我们进入第九周周二,开始为电商核心系统填充第一个业务服务——用户服务。昨天我们已经设计好了微服务架构、Proto 契约和数据库 Schema,今天将亲手实现用户注册、登录和 JWT 签发,让用户服务真正运行起来,注册到 Consul,并通过网关对外提供服务。这是整个电商系统的认证基石,后续所有服务的用户身份都将依赖于此。


今日目标

  1. 创建user-service项目骨架,配置数据库、Redis、Consul 注册。
  2. 根据 Proto 契约实现UserServiceRPC 接口:Register、Login、GetUserById。
  3. 集成hyperf/jwt在用户服务端签发 Token,登录时返回 JWT。
  4. 编写数据库迁移和模型,保证密码加密存储(Bcrypt)。
  5. 启动用户服务,验证 Consul 注册成功,并通过网关和 RPC 直接测试接口。
  6. 在网关层为/auth/*路由配置反向代理到用户服务,初步实现统一入口。

一、项目创建与配置(约 45 分钟)

我们将在services/user目录下创建独立的 Hyperf 项目,并将其加入到 Docker Compose 编排中。

1. 创建项目

进入容器(或直接在宿主机利用 Composer):

docker-composeexecswoolebashcd/var/www/servicescomposercreate-project hyperf/hyperf-skeleton usercduser
2. 安装必要组件
composerrequire hyperf/database hyperf/redis hyperf/json-rpc hyperf/jwt hyperf/service-governance-consul
3. 配置数据库

编辑.env:

DB_DRIVER=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=user_db DB_USERNAME=root DB_PASSWORD=root

配置config/autoload/databases.php指向上述环境变量。

4. 配置 Redis、Consul 注册

config/autoload/redis.php使用默认的redis容器。
config/autoload/services.php配置 Consul 驱动(参考第四周配置):

'drivers'=>['consul'=>['driver'=>Hyperf\ServiceGovernance\Driver\ConsulDriver::class,'uri'=>'http://consul:8500','check'=>['ttl'=>10,'deregister_critical_service_after'=>'30s',],],],
5. 配置 JWT

发布配置:php bin/hyperf.php vendor:publish hyperf/jwt
编辑config/autoload/jwt.php,设置密钥和有效期。

6. 创建数据库表

在user_db中执行昨天编写的users表,或通过迁移文件创建。


二、知识核心:密码加密、JWT 与 RPC 实现(约 1 小时)

1. 密码存储

绝不能存储明文密码。使用password_hash(Bcrypt) 加密,password_verify验证。Laravel 的Hashfacade 在 Hyperf 中可通过hyperf/hashing组件获得,或直接使用 PHP 原生函数。我们将采用原生方式,简单可靠。

2. JWT 签发

登录成功后,使用hyperf/jwt生成包含用户 ID、用户名等信息的 Token,返回给客户端。该 Token 可在网关层被验证,实现无状态认证。

3. RPC 服务发布

通过#[RpcService]注解将用户服务注册为 JSON-RPC 服务端,端口 9502。消费者(网关或其他服务)通过 Consul 发现并调用。


三、实战:编码用户服务接口(约 2.5 小时)

步骤 1:定义服务契约接口

根据 Proto 定义,创建app/JsonRpc/UserServiceInterface.php:

<?phpnamespaceApp\JsonRpc;useHyperf\RpcServer\Annotation\RpcService;#[RpcService(name:"UserService",protocol:"jsonrpc-http",server:"jsonrpc")]interfaceUserServiceInterface{publicfunctionregister(array$data):array;publicfunctionlogin(string$username,string$password):array;publicfunctiongetUserById(int$id):array;publicfunctionbatchGetUsers(array$ids):array;}
步骤 2:实现服务

创建app/JsonRpc/UserService.php:

<?phpnamespaceApp\JsonRpc;useApp\Model\User;useHyperf\Jwt\JwtFactory;useHyperf\Di\Annotation\Inject;#[RpcService(name:"UserService",protocol:"jsonrpc-http",server:"jsonrpc")]classUserServiceimplementsUserServiceInterface{#[Inject]privateJwtFactory$jwtFactory;publicfunctionregister(array$data):array{// 验证(简化,实际应使用 Validator)if(empty($data['username'])||empty($data['password'])){thrownew\InvalidArgumentException('Username and password required');}// 检查唯一性if(User::where('username',$data['username'])->exists()){thrownew\InvalidArgumentException('Username already exists');}$user=User::create(['username'=>$data['username'],'password'=>password_hash($data['password'],PASSWORD_BCRYPT),'email'=>$data['email']??'',]);return$this->formatUser($user);}publicfunctionlogin(string$username,string$password):array{$user=User::where('username',$username)->first();if(!$user||!password_verify($password,$user->password)){thrownew\InvalidArgumentException('Invalid credentials');}$token=(string)$this->jwtFactory->make(['id'=>$user->id,'username'=>$user->username,]);return['token'=>$token,'user'=>$this->formatUser($user),];}publicfunctiongetUserById(int$id):array{$user=User::find($id);return$user?$this->formatUser($user):[];}publicfunctionbatchGetUsers(array$ids):array{$users=User::findMany($ids);returnarray_map([$this,'formatUser'],$users);}privatefunctionformatUser($user):array{return['id'=>$user->id,'username'=>$user->username,'email'=>$user->email,'avatar'=>$user->avatar,'created_at'=>$user->created_at,];}}
步骤 3:创建 User 模型

app/Model/User.php:

<?phpnamespaceApp\Model;useHyperf\DbConnection\Model\Model;classUserextendsModel{protected$table='users';protected$fillable=['username','password','email','avatar'];protected$hidden=['password'];}
步骤 4:配置 JSON-RPC 服务端

编辑config/autoload/server.php,添加jsonrpc服务器:

'servers'=>[['name'=>'jsonrpc','type'=>Server::SERVER_HTTP,'host'=>'0.0.0.0','port'=>9502,'sock_type'=>SWOOLE_SOCK_TCP,'callbacks'=>[Event::ON_REQUEST=>[\Hyperf\JsonRpc\HttpServer::class,'onRequest'],],],],
步骤 5:配置依赖注入

config/autoload/dependencies.php中绑定接口到实现:

App\JsonRpc\UserServiceInterface::class=>App\JsonRpc\UserService::class,
步骤 6:编写数据库迁移(可选)

创建迁移文件或直接使用 SQL 初始化。由于我们昨天已通过docker-entrypoint-initdb.d自动创建了user_db和users表,此处无需额外操作。


四、启动与测试(约 1 小时)

1. 启动用户服务
php bin/hyperf.php start

确认控制台输出Service registered successfully或类似信息,并在 Consul UI 中看到UserService服务。

2. 直接测试 RPC 接口

使用curl调用 JSON-RPC 端点:

# 注册curl-XPOST http://localhost:9502\-H"Content-Type: application/json"\-d'{"jsonrpc":"2.0","method":"user/register","params":[{"username":"alice","password":"123456","email":"alice@example.com"}],"id":1}'

预期返回用户信息。

# 登录curl-XPOST http://localhost:9502\-H"Content-Type: application/json"\-d'{"jsonrpc":"2.0","method":"user/login","params":["alice","123456"],"id":2}'

预期返回 Token 和用户信息。

# 获取用户curl-XPOST http://localhost:9502\-H"Content-Type: application/json"\-d'{"jsonrpc":"2.0","method":"user/getUserById","params":[1],"id":3}'
3. 通过网关访问(后续步骤)

修改网关hyperf-gateway的 Nacosgateway-routes配置,增加:

{"prefix":"/auth","target":"http://user-service:9502"}

重启网关后,客户端可以通过http://localhost:9500/auth/register等路由访问用户服务(注意路径转换,网关目前是透明转发,JSON-RPC 路径需匹配)。为使 RESTful 友好,我们可以在网关编写一个/auth/register的聚合控制器,但今天先保证 RPC 调用正常。

4. 验证服务注册与发现

在 Consul UI 中确认UserService健康。其他服务(如将来的订单服务)可通过配置services.php消费者从 Consul 发现用户服务,实现 RPC 调用。


五、成果测试与记录(约 30 分钟)

测试清单
检验项方法通过标准
用户注册RPC 调用 register用户数据入库,密码加密
重复用户名再次注册相同用户名抛出异常或返回错误
登录成功正确凭证调用 login返回有效 JWT
登录失败错误密码调用 login返回错误
JWT 验证使用返回的 Token 在网关测试(若已对接)网关解析成功
GetUserById传入有效 ID返回用户信息
Consul 注册Consul UI 查看UserService 出现,健康检查通过
密码加密查看数据库 users 表password 字段非明文
常见问题
  • JWT 签名失败:检查jwt.php密钥长度,确保 HS256 至少 32 字节。
  • 数据库连接失败:确认databases.php主机名为mysql,并且user_db已创建。
  • RPC 服务未注册:检查@RpcService注解和server.php配置,确认没有命名冲突。

六、今日作业与学习产出

  1. 提交代码:将services/user项目代码提交到 Git。
  2. 完善接口:
    • 添加参数验证(使用hyperf/validation),增强健壮性。
    • 实现用户信息更新接口(修改密码、邮箱等)。
  3. 学习笔记:
    • 绘制用户服务启动和注册到 Consul 的流程图。
    • 对比直接在服务内使用 JWT 与在网关统一鉴权的利弊。
  4. 挑战任务:
    • 编写一个用户服务消费者(可在网关中实现),通过 Consul 发现调用用户服务,实现统一认证入口。
    • 为用户服务添加链路追踪 Span(集成hyperf/tracer),上报到 Jaeger。

通过今天的学习,电商系统拥有了认证基石——用户服务。明天我们将实现商品服务,并打通与网关、Consul 的完整链路,让商品数据能够被查询和管理。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询