# skeleton **Repository Path**: lmverse/skeleton ## Basic Information - **Project Name**: skeleton - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: dev/1.0 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-27 - **Last Updated**: 2026-07-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Hyperf Production Service Template 基于 Hyperf 3.2、PHP 8.2+ 和 Swoole 的生产化后端服务模板。项目保留可直接复用的用户认证、账号管理、文件上传、健康检查、gRPC、API 文档、链路追踪、结构化访问日志、业务事件、异步队列和定时任务能力,不包含 Hello World、Demo Echo、Greeter 等纯演示模块。 ## Runtime - PHP >= 8.2 - Swoole >= 5.0 或 Swow >= 1.3 - MySQL 8.0 - Redis 7 - Protobuf 扩展(启用 gRPC 时) HTTP 默认监听 `9501`,gRPC 默认监听 `9502`。 ## Architecture ```text app/ ├── Common/ # 响应、异常、中间件、健康检查、仓储基类和公共基础设施 ├── User/ # 用户、认证会话、账号管理和 User gRPC 服务 └── Upload/ # 上传校验、文件元数据、存储适配器和受控下载 ``` 模块约束由测试自动检查: - `Common` 不依赖具体业务模块。 - 业务模块只依赖自身与 `Common`。 - Service 不直接使用 `Db::table()` 或 `Model::query()`,数据库访问由 Repository 负责。 - 常规查询默认过滤 `is_delete = 0`。 ## Quick Start ```bash cp .env.example .env docker compose up -d mysql redis docker compose run --rm skeleton php bin/hyperf.php migrate --seed docker compose up -d skeleton ``` 验证服务: ```bash curl http://127.0.0.1:9501/health/live curl http://127.0.0.1:9501/health/ready ``` Compose 会直接启动 Hyperf,不再使用空闲容器占位。MySQL 8 使用 `mysql8-data` 数据卷;旧版项目遗留的 `mysql-data` 卷不会被自动删除或覆盖。 开发环境默认管理员: ```text username: admin password: ChangeMe123! ``` 生产环境必须通过 `ADMIN_PASSWORD` 和 `SIMPLE_JWT_SECRET` 提供独立强凭据。`APP_ENV=prod` 时,JWT 密钥不足 32 字符或仍为开发默认值会阻止应用启动。 ## API Contract 普通 JSON 接口使用统一响应格式: ```json { "code": 0, "message": "success", "data": {}, "meta": { "trace_id": "4c8d35fda4f249329c1d58ec68e94e6a" } } ``` 参数错误、业务错误、认证错误、HTTP 404/405 和系统异常使用相同外层结构。参数错误额外包含 `errors`。HTTP 状态码与错误语义保持一致,不使用 HTTP 200 表示失败。 JSON Controller 返回 `AbstractResponse` 派生 DTO,业务数据使用对应的 Response DTO;下载、重定向和需要自定义 HTTP 状态的接口返回 `ResponseInterface`。请求参数统一映射到 Request DTO,Controller 不再重复解析原始 JSON 字段。 每个响应都携带 `X-Request-Id`。客户端提供的请求 ID 仅接受 1 到 128 位字母、数字、点、下划线、冒号和连字符;非法值会被服务端重新生成。 API 错误消息支持 `en` 和 `zh_CN`。客户端通过 `Accept-Language` 协商语言,服务端通过 `Content-Language` 返回实际采用的语言;支持权重和常见别名,例如 `zh-CN` 会映射为 `zh_CN`。未提供、无法匹配或语言被禁用时使用 `APP_LOCALE`,单个翻译缺失时使用 `APP_FALLBACK_LOCALE`。 ```bash curl -H 'Accept-Language: zh-CN, en;q=0.8' http://127.0.0.1:9501/auth/profile ``` ```dotenv APP_LOCALE=en APP_FALLBACK_LOCALE=en APP_SUPPORTED_LOCALES=en,zh_CN ``` Service 保留稳定错误码和英文回退消息,本地化统一发生在 HTTP 异常响应边界。成功响应和错误响应都携带 `Content-Language`。 ## Access Logs 每个未排除的 HTTP 请求输出一条 JSON 访问日志,包含 `method`、`path`、`status`、`duration_ms` 和 `trace_id`,并可配置是否记录客户端 IP 与截断后的 User-Agent。访问日志不记录 query、请求体、Authorization 或令牌。 ```dotenv ACCESS_LOG_ENABLED=true ACCESS_LOG_OUTPUT=php://stdout ACCESS_LOG_EXCLUDE_PATHS= ACCESS_LOG_INCLUDE_CLIENT_IP=true ACCESS_LOG_INCLUDE_USER_AGENT=true ``` 应用日志和 AccessLog 使用独立 logger channel,可以输出到不同文件或日志采集管道。 ## HTTP Endpoints ### Health ```text GET /health/live 进程存活检查,不访问外部依赖 GET /health/ready MySQL、Redis 和初始化状态检查;未就绪返回 HTTP 503 ``` ### Authentication ```text POST /auth/login POST /auth/token/refresh POST /auth/logout POST /auth/logout-all POST /auth/password GET /auth/profile PATCH /auth/profile ``` 认证使用 HS256 JWT。访问令牌还必须同时存在于 Redis 活跃索引和 `user_auth_sessions` 有效会话中。刷新令牌是独立随机令牌,数据库和 Redis 只保存 SHA-256 哈希。退出、禁用用户、删除用户、修改或重置密码都会撤销相关会话。 登录接口默认对同一用户名和 IP 执行失败次数限制,可通过 `AUTH_LOGIN_MAX_ATTEMPTS` 和 `AUTH_LOGIN_DECAY_SECONDS` 调整。 ### User Management 以下接口要求已登录且角色为 `admin`: ```text GET /users POST /users GET /users/{id} PATCH /users/{id} DELETE /users/{id} POST /users/{id}/password/reset ``` 列表支持 `page`、`per_page`、`keyword`、`status` 和 `role`。删除采用逻辑删除。系统禁止管理员禁用、降级或删除自己的账号,并保证至少保留一个启用的管理员。 ### Files ```text POST /files GET /files/{id}/content ``` 上传接口要求认证,并记录 `upload_user_id`。服务端验证大小、扩展名和服务端识别的 MIME 类型,使用雪花 ID 生成文件名并计算 SHA-256。文件写入后若元数据落库失败,会调用存储适配器删除已写入对象。 内置 `local`、`s3` 和 `oss` 三个存储盘,通过 `UPLOAD_DISK` 切换新上传文件的默认目标。文件元数据始终保存实际使用的盘,切换默认盘后,历史文件仍由原驱动读取。下载只允许文件所有者或管理员访问。 本地存储默认位于 `runtime/uploads`。`UPLOAD_PUBLIC_URL` 留空时,接口直接返回受认证的文件流;配置静态文件域名后则重定向到外部 URL。 ```dotenv UPLOAD_DISK=local UPLOAD_ROOT=/opt/www/skeleton/runtime/uploads UPLOAD_PUBLIC_URL= ``` AWS S3 使用官方 SDK。接入 AWS 时通常只需桶、区域和凭据;接入 MinIO 或其他 S3 兼容服务时,同时配置 endpoint,并按服务要求启用 path-style。`S3_PREFIX` 可将对象统一放在指定键前缀下。 ```dotenv UPLOAD_DISK=s3 S3_BUCKET=example-bucket S3_REGION=us-east-1 S3_ACCESS_KEY_ID= S3_ACCESS_KEY_SECRET= S3_SECURITY_TOKEN= S3_ENDPOINT=http://minio:9000 S3_USE_PATH_STYLE_ENDPOINT=true S3_PREFIX=uploads S3_PUBLIC_URL= S3_TEMPORARY_URL_TTL=900 ``` 阿里云 OSS 同样使用官方 SDK。`OSS_ENDPOINT` 填区域 endpoint;使用自定义域名作为 endpoint 时将 `OSS_IS_CNAME` 设为 `true`。STS 场景可通过 `OSS_SECURITY_TOKEN` 提供临时令牌。 ```dotenv UPLOAD_DISK=oss OSS_BUCKET=example-bucket OSS_ACCESS_KEY_ID= OSS_ACCESS_KEY_SECRET= OSS_SECURITY_TOKEN= OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com OSS_IS_CNAME=false OSS_USE_SSL=true OSS_PREFIX=uploads OSS_PUBLIC_URL= OSS_TEMPORARY_URL_TTL=900 ``` 对象存储的 `*_PUBLIC_URL` 配置为公开读域名或 CDN 前缀时,下载接口重定向到该地址;留空时生成有效期受 `*_TEMPORARY_URL_TTL` 控制的短期签名 URL。数据库只保存相对路径,不保存签名 URL,也不会因域名或前缀调整而改写历史记录。 ## Access Control And CORS HTTP 鉴权默认采用 `whitelist` 模式:除健康检查、登录、刷新令牌和非生产 Swagger 外,其余新路由默认要求登录。生产环境不应切回默认公开的 blacklist 策略。 CORS 在所有环境使用显式来源列表,不会任意回显 `Origin`。通过以下变量配置: ```text CORS_ENABLED CORS_ALLOWED_ORIGINS CORS_ALLOWED_METHODS CORS_ALLOWED_HEADERS CORS_EXPOSED_HEADERS CORS_ALLOW_CREDENTIALS CORS_MAX_AGE ``` ## gRPC `proto/user/user.proto` 定义内部用户服务: ```text GetUser VerifyUser ``` 非生产环境的 Swagger 页面会在 `用户服务-gRPC` 分组下展示这两个 gRPC 方法的契约,包括实际 Hyperf gRPC 路径、protobuf 请求/响应消息字段和示例。当前实际调用路径为: ```text /grpc.User/GetUser /grpc.User/VerifyUser ``` gRPC 接口以 `#[RpcService]` 标注的服务类作为 Hyperf 原生路由入口,类似 HTTP Controller。类级 `#[GrpcContract]` 统一声明 proto 文件、package、service 和 Swagger 分组;服务方法使用 `#[GrpcOperation]` 声明摘要、说明、入参 DTO、出参 DTO 和请求示例。旧的 `#[GrpcRequest]` + `#[ApiResponse]` 写法仍被文档生成器兼容,但新接口优先使用组合注解。`app/User/DTO/Grpc` 下的文档 DTO 维护入参、出参、字段号、protobuf 类型、业务必填语义、默认值和字段说明;`app/User/Grpc/Message` 仍然是实际 gRPC 编解码使用的 protobuf message 类。 Swagger/OpenAPI 只用于展示 gRPC 契约,不能直接发起 protobuf-framed HTTP/2 调用;调试和业务调用请使用 `grpcurl`、生成的 protobuf 客户端、低层 `UserClient` 或 `UserServiceInterface` RPC 代理。 服务端由 `GRPC_SERVER_ENABLE` 和 `GRPC_RPC_SERVER_ENABLE` 控制。需要静态客户端节点时配置 `USER_GRPC_CLIENT_HOST`、`USER_GRPC_CLIENT_PORT`、权重和负载均衡策略。应用既可以使用低层 `UserClient`,也可以依赖 `UserServiceInterface` 生成 RPC 代理。 gRPC 默认视为内部网络能力,生产部署需要在网关、服务网格或拦截器层增加服务间认证。 ## Crontab heartbeat 示例默认关闭: ```dotenv CRONTAB_ENABLE=false ``` 设置为 `true` 会启用调度进程和 heartbeat 调度器。Crontab callback 只投递 `SystemHeartbeatJob`,实际逻辑由异步队列消费者执行;任务同时启用 `singleton`、`on_one_server`、Redis mutex 和 Job 幂等键,避免多实例重复执行。 ## Async Queue And Events Redis 异步队列默认启用一个消费者进程,使用 5、30、60 秒分级重试,单 Job 最长处理 30 秒;达到最大尝试次数后进入 failed queue。主要配置: ```dotenv ASYNC_QUEUE_ENABLE=true ASYNC_QUEUE_CHANNEL={skeleton}:async_queue ASYNC_QUEUE_PROCESSES=1 ASYNC_QUEUE_CONCURRENT_LIMIT=10 ASYNC_QUEUE_HANDLE_TIMEOUT=30 ASYNC_QUEUE_MAX_MESSAGES=1000 ``` 查看积压、重放失败消息和清理失败队列: ```bash php bin/hyperf.php queue:info default php bin/hyperf.php queue:reload default php bin/hyperf.php queue:flush default ``` 当前业务事件为 `UserLoggedIn`、`UserCreated` 和 `FileUploaded`。事件只包含业务 ID、标量快照、发生时间、版本和 trace ID,不携带 ORM Model、密码、令牌或文件内容;事件在核心持久化完成后派发,Listener 失败不会把已经提交的业务状态误报为请求失败。需要不可丢失的通知或审计时,应在后续模块中使用 Transactional Outbox。 ## Database Conventions - 逻辑删除字段:`is_delete` - 创建和更新时间:`create_time`、`update_time` - 业务时间字段使用明确的 `*_time` - 操作者字段使用领域名称,例如 `upload_user_id` - 表、字段和索引使用明确名称,迁移包含中文注释 已有数据库升级时执行: ```bash docker compose run --rm skeleton php bin/hyperf.php migrate ``` ## Quality Checks ```bash docker compose run --rm skeleton composer test:unit docker compose run --rm skeleton composer test:integration docker compose run --rm skeleton composer check ``` `test:unit` 使用 Mockery、Stub 和 Fake 隔离 Repository、Redis、Storage 与事件派发器,不访问 MySQL/Redis;`test:integration` 验证 HTTP、数据库和本地存储契约。`check` 依次执行代码风格检查、PHPStan 和完整 PHPUnit。集成测试依赖已迁移并完成种子初始化的 MySQL 与可用 Redis。 ## Production Checklist - 设置 `APP_ENV=prod` 和高强度 `SIMPLE_JWT_SECRET`。 - 设置独立的 `ADMIN_USERNAME`、`ADMIN_NAME` 和 `ADMIN_PASSWORD`。 - 配置实际数据库、Redis 凭据和网络访问控制。 - 确认异步队列消费者已启动,并监控 waiting、failed、timeout 队列。 - 将 `CORS_ALLOWED_ORIGINS` 限制为真实前端域名。 - 决定本地文件由应用下载、反向代理发送,还是切换到对象存储。 - 在反向代理配置 TLS、请求体限制、超时和可信代理头。 - 为 gRPC 增加内部身份认证,并限制 `9502` 的网络暴露。 - 将日志输出接入集中式采集,并根据业务补充指标、告警和审计策略。