
【基于 SwooleHyperf 的微服务实战】第九周·周二:为电商核心系统填充第一个业务服务——用户服务今天我们进入第九周周二开始为电商核心系统填充第一个业务服务——用户服务。昨天我们已经设计好了微服务架构、Proto 契约和数据库 Schema今天将亲手实现用户注册、登录和 JWT 签发让用户服务真正运行起来注册到 Consul并通过网关对外提供服务。这是整个电商系统的认证基石后续所有服务的用户身份都将依赖于此。今日目标创建user-service项目骨架配置数据库、Redis、Consul 注册。根据 Proto 契约实现UserServiceRPC 接口Register、Login、GetUserById。集成hyperf/jwt在用户服务端签发 Token登录时返回 JWT。编写数据库迁移和模型保证密码加密存储Bcrypt。启动用户服务验证 Consul 注册成功并通过网关和 RPC 直接测试接口。在网关层为/auth/*路由配置反向代理到用户服务初步实现统一入口。一、项目创建与配置约 45 分钟我们将在services/user目录下创建独立的 Hyperf 项目并将其加入到 Docker Compose 编排中。1. 创建项目进入容器或直接在宿主机利用 Composerdocker-composeexecswoolebashcd/var/www/servicescomposercreate-project hyperf/hyperf-skeleton usercduser2. 安装必要组件composerrequire hyperf/database hyperf/redis hyperf/json-rpc hyperf/jwt hyperf/service-governance-consul3. 配置数据库编辑.envDB_DRIVERmysql DB_HOSTmysql DB_PORT3306 DB_DATABASEuser_db DB_USERNAMEroot DB_PASSWORDroot配置config/autoload/databases.php指向上述环境变量。4. 配置 Redis、Consul 注册config/autoload/redis.php使用默认的redis容器。config/autoload/services.php配置 Consul 驱动参考第四周配置drivers[consul[driverHyperf\ServiceGovernance\Driver\ConsulDriver::class,urihttp://consul:8500,check[ttl10,deregister_critical_service_after30s,],],],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{// 验证简化实际应使用 Validatorif(empty($data[username])||empty($data[password])){thrownew\InvalidArgumentException(Username and password required);}// 检查唯一性if(User::where(username,$data[username])-exists()){thrownew\InvalidArgumentException(Username already exists);}$userUser::create([username$data[username],passwordpassword_hash($data[password],PASSWORD_BCRYPT),email$data[email]??,]);return$this-formatUser($user);}publicfunctionlogin(string$username,string$password):array{$userUser::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{$userUser::find($id);return$user?$this-formatUser($user):[];}publicfunctionbatchGetUsers(array$ids):array{$usersUser::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$tableusers;protected$fillable[username,password,email,avatar];protected$hidden[password];}步骤 4配置 JSON-RPC 服务端编辑config/autoload/server.php添加jsonrpc服务器servers[[namejsonrpc,typeServer::SERVER_HTTP,host0.0.0.0,port9502,sock_typeSWOOLE_SOCK_TCP,callbacks[Event::ON_REQUEST[\Hyperf\JsonRpc\HttpServer::class,onRequest],],],],步骤 5配置依赖注入config/autoload/dependencies.php中绑定接口到实现App\JsonRpc\UserServiceInterface::classApp\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\-HContent-Type: application/json\-d{jsonrpc:2.0,method:user/register,params:[{username:alice,password:123456,email:aliceexample.com}],id:1}预期返回用户信息。# 登录curl-XPOST http://localhost:9502\-HContent-Type: application/json\-d{jsonrpc:2.0,method:user/login,params:[alice,123456],id:2}预期返回 Token 和用户信息。# 获取用户curl-XPOST http://localhost:9502\-HContent-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配置确认没有命名冲突。六、今日作业与学习产出提交代码将services/user项目代码提交到 Git。完善接口添加参数验证使用hyperf/validation增强健壮性。实现用户信息更新接口修改密码、邮箱等。学习笔记绘制用户服务启动和注册到 Consul 的流程图。对比直接在服务内使用 JWT 与在网关统一鉴权的利弊。挑战任务编写一个用户服务消费者可在网关中实现通过 Consul 发现调用用户服务实现统一认证入口。为用户服务添加链路追踪 Span集成hyperf/tracer上报到 Jaeger。通过今天的学习电商系统拥有了认证基石——用户服务。明天我们将实现商品服务并打通与网关、Consul 的完整链路让商品数据能够被查询和管理。