ssh/common-util

PHP 常用工具类集合,包含阿里云IoT/OSS/SMS、国密SM2/SM3/SM4加解密、AES加解密、商米云打印等工具

Maintainers

Package info

github.com/yn-ssh/common-util-php

pkg:composer/ssh/common-util

Transparency log

Statistics

Installs: 19

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.5.0 2026-08-07 16:12 UTC

This package is auto-updated.

Last update: 2026-08-07 16:14:59 UTC


README

基于 Webman 框架的 PHP 公共组件库,为微服务提供统一的响应输出、错误码体系、结构化日志、HTTP 中间件、鉴权加密、多租户数据库路由、审计日志,以及阿里云 IoT/OSS/SMS、国密 SM2/SM3/SM4、AES 加解密、商米云打印等工具。

安装

composer require ssh/common-util

环境要求

  • PHP >= 8.1
  • workerman/webman-framework ^2.0
  • monolog/monolog ^3.10
  • ext-curl、ext-openssl(1.1.1+,SM4 需要)、ext-gmp
  • ext-gd(商米云打印图片处理需要,可选)
  • ext-redis + webman/redis + webman/database(IdGenerator 需要,可选)

功能一览

响应与错误码

类名 说明
ResponseUtil 统一响应输出(含业务错误码、异常转响应)
ErrorCode 统一错误码常量与 HTTP 状态码映射
Exception\BusinessException 业务异常(携带错误码,支持字符串快捷构造)

结构化日志

类名 说明
Log\JsonFormatter JSON 结构化日志格式化器(生产)
Log\DevLineFormatter 人类可读行格式化器(开发)
Log\ServiceProcessor 注入 service / server_ip / server_port 字段
Log\RequestIdProcessor 注入 request_id / client_ip / client_ua / app_id 字段
Log\UserIdProcessor 注入 user_id / org_id 字段
Log\SlsHandler 阿里云 SLS 日志投递(环境变量控制)
Bootstrap\LaravelLog SQL 执行日志监听器

HTTP 中间件

类名 说明
Middleware\RequestIdMiddleware 请求追踪(X-Request-Id 透传 + 访问日志)
Middleware\InternalMiddleware 内部调用鉴权(Token / 内网 IP 白名单)
Middleware\StaticFile 静态文件安全拦截

鉴权与安全

类名 说明
Auth\JwtService JWT(HS256)签发与校验
Security\CryptoService AES-256-GCM 加解密(敏感字段存储)

审计日志

类名 说明
Audit\AuditLogService 审计日志服务
Audit\AuditLog 审计日志模型(sys_audit_log)

Trait

类名 说明
Traits\HasTimestampColumns 统一时间戳列名(gmt_create/gmt_modified/delete_time)

业务枚举与常量(AppTypeRoleConstant)已迁移至零依赖契约包 ssh/spms-contracts

通用工具

类名 说明
IdGenerator 唯一号生成器(Redis 原子自增 + MySQL 兜底)
AliIotUtil 阿里云 IoT 消息推送、设备管理
AliIotAmqpUtil 阿里云 IoT AMQP 认证凭证生成
AliOssUtil 阿里云 OSS 对象存储操作
AliSmsUtil 阿里云短信发送
SmCryptoUtil 国密 SM2/SM3/SM4 加解密
SymmetricEncoder AES 加解密(兼容 Java SHA1PRNG 密钥派生)
SunmiCloudPrinter 商米云打印机 ESC/POS 指令

ResponseUtil - 统一响应输出

基于 Webman 的 support\Response,构建统一格式的响应对象,可直接作为控制器返回值。

use Ssh\CommonUtil\ResponseUtil;

// 基础格式: ['code' => 200, 'msg' => 'success', 'data' => [...]]
$result = ResponseUtil::toArray(200, 'success', ['id' => 1]);

// 返回 webman Response 对象(直接在控制器中 return)
return ResponseUtil::success('操作成功', ['id' => 1]);   // 200
return ResponseUtil::fail('参数错误');                     // 400
return ResponseUtil::error();                              // 500
return ResponseUtil::notFound();                           // 404
return ResponseUtil::unauthenticated();                    // 401
return ResponseUtil::notBind();                            // 402
return ResponseUtil::forbidden();                          // 403
return ResponseUtil::notFoundController();                 // 404
return ResponseUtil::notImplemented();                     // 501
return ResponseUtil::serviceUnavailable();                 // 503
return ResponseUtil::tooManyRequests();                    // 429

// 自定义业务码(HTTP 200,body 携带业务 code,如 4002 需图形验证码)
return ResponseUtil::code(4002, '需要图形验证码', ['captcha' => true]);

// 业务错误码响应(HTTP 状态码由 ErrorCode 映射表决定)
return ResponseUtil::failWithCode(ErrorCode::TOKEN_EXPIRED);

error()notFound()unauthenticated() 等方法内部使用 trans() 读取国际化翻译,需配合 symfony/translation 使用。

异常转响应

use Ssh\CommonUtil\ResponseUtil;
use Ssh\CommonUtil\Exception\BusinessException;

// 从 BusinessException 创建响应
return ResponseUtil::fromException(new BusinessException(40401, '用户不存在'));

// 统一异常处理:BusinessException 返回对应业务码,其他异常返回 500
return ResponseUtil::handleException($e);

ErrorCode - 统一错误码

错误码规则:400xx 参数错误、401xx 认证失败、403xx 授权失败、404xx 资源不存在、409xx 业务冲突、500xx 服务端错误、502xx 下游调用失败。

常量 HTTP 说明
SUCCESS 200 200 成功
PARAM_ERROR 40000 400 参数错误(类目基码)
PARAM_INVALID 40001 400 参数校验失败
PARAM_MISSING 40002 400 缺少必填参数
UNAUTHENTICATED 40100 401 未登录/未认证
TOKEN_EXPIRED 40101 401 Token 已过期
TOKEN_INVALID 40102 401 Token 无效
FORBIDDEN 40300 403 无权限
ACCOUNT_DISABLED 40301 403 账号已禁用
ACCOUNT_LOCKED 40302 403 账号已锁定
NOT_FOUND 40400 404 资源不存在
USER_NOT_FOUND 40401 404 用户不存在
THIRD_PARTY_NOT_FOUND 40402 404 第三方账号记录不存在
DATA_EXISTS 40900 409 数据已存在
VERSION_CONFLICT 40901 409 数据版本冲突(乐观锁)
STATUS_CONFLICT 40902 409 状态冲突
SERVER_ERROR 50000 500 系统内部错误
SERVICE_CALL_FAILED 50200 502 下游服务调用超时或异常
SERVICE_UNAVAILABLE 50300 503 服务不可用
use Ssh\CommonUtil\ErrorCode;

$httpStatus = ErrorCode::toHttpStatus(ErrorCode::TOKEN_EXPIRED); // 401

BusinessException - 业务异常

use Ssh\CommonUtil\Exception\BusinessException;
use Ssh\CommonUtil\ErrorCode;

// 两种构造方式
throw new BusinessException(ErrorCode::USER_NOT_FOUND, '用户不存在');  // 显式错误码
throw new BusinessException('机构不存在');                              // 字符串 → 默认业务码 400

// 快捷工厂方法
throw BusinessException::notFound('订单不存在');     // 40400
throw BusinessException::userNotFound();             // 40401
throw BusinessException::dataExists('手机号已注册'); // 40900
throw BusinessException::versionConflict();          // 40901

// 获取错误码
$e->getErrorCode();

结构化日志

提供双格式输出,共用同一套 Processor,按环境切换:

  • 生产环境JsonFormatter 输出单行 JSON,供 ELK/Loki/SLS 等采集系统解析
  • 开发环境DevLineFormatter 输出人类可读的行格式,extra 关键字段内联为标签

生产格式示例:

{"time":"2026-07-01 12:00:00","level_name":"INFO","service":"spms-auth","server_ip":"172.18.0.3","server_port":"8781","request_id":"a1b2c3d4","user_id":"1001","org_id":"10","client_ip":"203.0.113.5","app_id":"wx123456789","client_ua":"Mozilla/5.0 ...","message":"登录成功","context":{}}

开发格式示例(无值的标签自动省略):

[2026-07-01 12:00:00] webman.INFO [svc=spms-auth req=a1b2c3d4 uid=1001 org=10 ip=203.0.113.5]: 登录成功 {"uid":1001}

Webman 配置示例(双环境自动切换)

config/log.php

use Monolog\Level;
use Ssh\CommonUtil\Log\JsonFormatter;
use Ssh\CommonUtil\Log\DevLineFormatter;
use Ssh\CommonUtil\Log\ServiceProcessor;
use Ssh\CommonUtil\Log\RequestIdProcessor;
use Ssh\CommonUtil\Log\UserIdProcessor;

// 生产环境输出 JSON 供采集系统解析,其他环境输出可读行格式
$isProd = getenv('APP_ENV') === 'production';

return [
    'default' => [
        'handlers' => [
            [
                'class' => Monolog\Handler\StreamHandler::class,
                'constructor' => [
                    runtime_path() . '/logs/app.log',
                    Level::Debug,
                ],
                'formatter' => [
                    'class' => $isProd ? JsonFormatter::class : DevLineFormatter::class,
                    'constructor' => [],
                ],
            ],
        ],
        'processors' => [
            new ServiceProcessor('spms-auth', getenv('SERVER_IP') ?: '-', getenv('SERVER_PORT') ?: '-'),
            new RequestIdProcessor(),
            new UserIdProcessor(),
        ],
    ],
];

在中间件/鉴权中注入上下文

use Ssh\CommonUtil\Log\RequestIdProcessor;
use Ssh\CommonUtil\Log\UserIdProcessor;

// 请求开始
RequestIdProcessor::setRequestId($requestId);
RequestIdProcessor::setClientIp($request->getRealIp());
RequestIdProcessor::setClientUa($request->header('user-agent', ''));
RequestIdProcessor::setAppId($appId);

// 鉴权通过后
UserIdProcessor::setUserId((string) $userId);
UserIdProcessor::setOrgId((string) $orgId);

// 请求结束(RequestIdMiddleware 已自动处理)
RequestIdProcessor::reset();
UserIdProcessor::reset();

SlsHandler - 阿里云 SLS 日志投递

将日志推送到阿里云日志服务(SLS),通过环境变量控制开关与配置,未配置时零影响

环境变量 必填 说明
SLS_ENABLED 总开关,=1 启用
SLS_ENDPOINT 服务接入点,如 cn-shenzhen.log.aliyuncs.com
SLS_ACCESS_KEY_ID / SLS_ACCESS_KEY_SECRET AccessKey
SLS_PROJECT Project 名称
SLS_LOGSTORE Logstore 名称
SLS_TOPIC 日志主题,默认空
SLS_SOURCE 日志来源,默认主机名
SLS_BATCH_SIZE 批量条数阈值,默认 50
SLS_FLUSH_INTERVAL 最长刷出间隔(秒),默认 3
SLS_LOG_LEVEL 最低上报级别,默认 WARNING

特性:

  • 批量发送:缓冲达到条数阈值或时间阈值才调用 putLogs,避免逐条远程写入阻塞请求
  • 级别分流:默认只上报 WARNING+,DEBUG/INFO 留本地文件,控制 SLS 成本
  • 失败降级:发送异常吞掉并写入 PHP 错误日志,不影响业务
  • 字段一致:与 JsonFormatter 同构,SLS 侧可直接按字段建索引

需安装 SDK:composer require alibabacloud/aliyun-log-php-sdk

config/log.php 条件挂载:

use Ssh\CommonUtil\Log\SlsHandler;

$handlers = [
    [
        'class' => Monolog\Handler\StreamHandler::class,
        'constructor' => [runtime_path() . '/logs/app.log', Monolog\Level::Debug],
        'formatter' => [
            'class' => $isProd ? JsonFormatter::class : DevLineFormatter::class,
            'constructor' => [],
        ],
    ],
];

// SLS_ENABLED=1 且配置齐全时自动追加 SLS Handler
if (SlsHandler::isConfigured()) {
    $handlers[] = [
        'class' => SlsHandler::class,
        'constructor' => [
            getenv('SLS_ENDPOINT'),
            getenv('SLS_ACCESS_KEY_ID'),
            getenv('SLS_ACCESS_KEY_SECRET'),
            getenv('SLS_PROJECT'),
            getenv('SLS_LOGSTORE'),
            getenv('SLS_TOPIC') ?: '',
            getenv('SLS_SOURCE') ?: '',
            (int) (getenv('SLS_BATCH_SIZE') ?: 50),
            (float) (getenv('SLS_FLUSH_INTERVAL') ?: 3),
            Monolog\Level::fromName(strtoupper(getenv('SLS_LOG_LEVEL') ?: 'WARNING')),
        ],
    ];
}

return [
    'default' => [
        'handlers' => $handlers,
        'processors' => [
            new ServiceProcessor('spms-auth', getenv('SERVER_IP') ?: '-', getenv('SERVER_PORT') ?: '-'),
            new RequestIdProcessor(),
            new UserIdProcessor(),
        ],
    ],
];

也可直接用环境变量工厂创建:SlsHandler::fromEnv()(配置缺失时抛异常)。

LaravelLog - SQL 执行日志

实现 Webman\Bootstrap,通过 Db::listen 捕获所有 SQL 写入日志通道(自动替换绑定参数、过滤 select 1 心跳)。

日志通道可配置,优先级:setChannel() 运行时覆盖 > 环境变量 SQL_LOG_CHANNEL > 默认 sql

// 方式1:环境变量
SQL_LOG_CHANNEL=sql_debug

// 方式2:代码中指定(需在服务启动前调用,如 config/bootstrap.php 顶部)
use Ssh\CommonUtil\Bootstrap\LaravelLog;
LaravelLog::setChannel('sql_debug');

注意:所配置的通道需在 config/log.php 中定义对应的 handler。

config/bootstrap.php 中注册:

return [
    // ...
    Ssh\CommonUtil\Bootstrap\LaravelLog::class,
];

HTTP 中间件

config/middleware.php 中注册:

use Ssh\CommonUtil\Middleware\RequestIdMiddleware;
use Ssh\CommonUtil\Middleware\InternalMiddleware;

return [
    '' => [
        RequestIdMiddleware::class,
    ],
    // 内部接口路由单独挂载
    'internal' => [
        InternalMiddleware::class,
    ],
];

RequestIdMiddleware - 请求追踪

  1. X-Request-Id 请求头读取(支持网关透传),无则生成 32 位十六进制 ID
  2. 注入 client_ip / client_uaRequestIdProcessor
  3. 请求结束记录 HTTP 访问日志(方法、URI、状态码、耗时)并重置请求级静态变量
  4. 响应头回写 X-Request-Id,便于全链路追踪

InternalMiddleware - 内部调用鉴权

校验请求来源是否为内部服务调用,满足任一条件即放行:

  • 请求头 X-Internal-Token 与环境变量 INTERNAL_TOKEN_SECRET 匹配(推荐)
  • 客户端 IP 属于内网段(127/8、10/8、172.16/12、192.168/16,备用)

StaticFile - 静态文件拦截

拒绝访问路径中带 /. 的隐藏文件请求(Webman 默认实现)。

JwtService - JWT 签发与校验

HS256 轻量实现,密钥取环境变量 JWT_SECRET。双 token 约定:机构 token(scope=org,claims:uid/org_id/role)、平台 token(scope=platform,claims:uid/role_id),校验时必须检查 scope 防止 token 混用。

use Ssh\CommonUtil\Auth\JwtService;

// 签发(默认有效期 7200 秒)
$token = JwtService::sign([
    'scope'  => 'org',
    'uid'    => 1001,
    'org_id' => 10,
    'role'   => 'admin',
], ttl: 3600);

// 校验(失败或过期返回 null)
$payload = JwtService::verify($token);
if ($payload && $payload['scope'] === 'org') {
    // 鉴权通过
}

CryptoService - AES-256-GCM 加解密

用于机构库密码、设备 Secret 等敏感字段的存储加密。密钥取 env('APP_KEY') 的 sha256 前 32 字节,存储格式 base64(iv12 + tag16 + ciphertext)

注意:与 SymmetricEncoder(AES-128-ECB,Java 兼容)是两套算法,勿混用。

use Ssh\CommonUtil\Security\CryptoService;

$encrypted = CryptoService::encrypt('db-password');
$plain     = CryptoService::decrypt($encrypted);

审计日志

记录关键业务操作的 who/what/when/where/result,支持两种存储驱动,通过环境变量 AUDIT_DRIVER 切换:

驱动 环境变量 说明
db 默认 写入 sys_audit_log 表,多微服务共享同一数据库,通过 service 字段区分来源
log AUDIT_DRIVER=log 写入 audit 日志通道(本地文件 + SLS),无需建表

db 驱动:表结构

CREATE TABLE `sys_audit_log` (
    `id`         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `service`    VARCHAR(50)  NOT NULL DEFAULT '' COMMENT '服务名',
    `module`     VARCHAR(50)  NOT NULL COMMENT '功能模块',
    `action`     VARCHAR(50)  NOT NULL COMMENT '操作类型',
    `method`     VARCHAR(10)  NOT NULL DEFAULT '' COMMENT 'HTTP 方法',
    `uri`        VARCHAR(255) NOT NULL DEFAULT '' COMMENT '请求路径',
    `params`     JSON         NULL COMMENT '请求参数(已脱敏)',
    `user_id`    BIGINT       NULL COMMENT '操作人ID',
    `org_id`     BIGINT       NULL COMMENT '机构ID',
    `client_ip`  VARCHAR(45)  NOT NULL DEFAULT '-',
    `client_ua`  VARCHAR(512) NOT NULL DEFAULT '-',
    `app_id`     VARCHAR(64)  NOT NULL DEFAULT '',
    `request_id` VARCHAR(64)  NOT NULL DEFAULT '-',
    `status`     TINYINT      NOT NULL DEFAULT 1 COMMENT '0=失败 1=成功',
    `error_msg`  VARCHAR(500) NULL,
    `created_at` TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `idx_user` (`user_id`),
    KEY `idx_module_action` (`module`, `action`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作审计日志';

log 驱动:audit 通道配置

config/log.php 增加 audit 通道,本地文件兜底(防丢)+ SLS 集中化(SLS 可开 WORM 合规保留,防篡改):

'audit' => [
    'handlers' => [
        // 本地文件兜底,JSON 格式便于采集器重传
        [
            'class' => Monolog\Handler\StreamHandler::class,
            'constructor' => [runtime_path() . '/logs/audit.log', Monolog\Level::Debug],
            'formatter' => ['class' => JsonFormatter::class, 'constructor' => []],
        ],
        // SLS 集中化:注意 level 必须为 Debug,审计含 INFO 级别,
        // 不能复用 SLS_LOG_LEVEL 默认的 WARNING,否则会被过滤
        [
            'class' => SlsHandler::class,
            'constructor' => [
                getenv('SLS_ENDPOINT'),
                getenv('SLS_ACCESS_KEY_ID'),
                getenv('SLS_ACCESS_KEY_SECRET'),
                getenv('SLS_PROJECT'),
                getenv('SLS_LOGSTORE_AUDIT') ?: getenv('SLS_LOGSTORE'),
                'audit',
                getenv('SLS_SOURCE') ?: '',
                50,
                3.0,
                Monolog\Level::Debug,
            ],
        ],
    ],
    'processors' => [
        new ServiceProcessor('spms-auth', getenv('SERVER_IP') ?: '-', getenv('SERVER_PORT') ?: '-'),
        new RequestIdProcessor(),
        new UserIdProcessor(),
    ],
],

审计记录成功为 INFO、失败为 WARNING,业务字段(module/action/method/uri/params/status/error_msg)在 context,上下文字段由 Processor 自动注入 extra,SLS 侧可直接按字段索引查询。

用法

use Ssh\CommonUtil\Audit\AuditLogService;

// 成功操作
AuditLogService::record('用户管理', '创建', $request->all());

// 失败操作
AuditLogService::record('用户管理', '创建', $request->all(), 0, $e->getMessage());
  • user_id / org_id 不传时自动从 UserIdProcessor 上下文获取
  • 请求上下文(client_ip / client_ua / app_id / request_id)自动注入
  • 敏感字段(password / token / app_secret 等)自动脱敏为 ****
  • fire-and-forget:写入失败仅记 warning 日志,不影响业务

Trait

HasTimestampColumns - 时间戳列名

use Ssh\CommonUtil\Traits\HasTimestampColumns;

class User extends Model
{
    use HasTimestampColumns;
    // CREATED_AT = 'gmt_create',UPDATED_AT = 'gmt_modified',DELETED_AT = 'delete_time'
}

Enum\AppTypeEnum\RoleConstant 已迁移至零依赖契约包 ssh/spms-contracts,请改为 use Ssh\SpmsContracts\Enum\AppType / use Ssh\SpmsContracts\Enum\RoleConstant

IdGenerator - 唯一号生成器

基于 Redis 原子自增 + MySQL 兜底的分布式唯一号生成器,支持 Swoole 协程异步回写。

生成格式: YYYYMMDD + 类型码(2位) + 当日序号(补零) 示例: 202607140100000001(18位,类型 01=用户,序号 1)

MySQL 表结构

CREATE TABLE `sys_id_sequence` (
    `type_code`   VARCHAR(10)  NOT NULL COMMENT '类型码',
    `biz_date`    VARCHAR(8)   NOT NULL COMMENT '业务日期 YYYYMMDD',
    `current_seq` BIGINT       NOT NULL DEFAULT 0 COMMENT '当前序号',
    `updated_at`  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`type_code`, `biz_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='唯一号序列表';

基本用法

use Ssh\CommonUtil\IdGenerator;

// 生成单个唯一号(类型名或类型码方式)
$id = IdGenerator::next('user');
$id = IdGenerator::next('01');
// => "202607140100000001"

// 指定总长度(默认 18 位)
$id = IdGenerator::next('org', totalLength: 20);

// 指定日期
$id = IdGenerator::next('user', date: '2026-07-15');

// 批量生成(1-1000)
$ids = IdGenerator::batch('user', count: 10);

内置类型码

类型名 类型码 常量
user 01 IdGenerator::TYPE_USER
org 02 IdGenerator::TYPE_ORG
app 03 IdGenerator::TYPE_APP
device 04 IdGenerator::TYPE_DEVICE

也支持直接传入 2 位数字类型码(如 '99')来自定义类型。

解析唯一号

$info = IdGenerator::parse('202607140100000001');
// => ['date' => '2026-07-14', 'type_code' => '01', 'sequence' => 1, 'type_name' => 'user']

工作原理

  1. Redis 优先:通过 INCR 原子自增获取序号,高性能无锁
  2. Redis 初始化校准:首次创建 key 时从 MySQL 读取最大序号,防止 Redis 重启后序号冲突
  3. 异步回写 MySQL:Redis 生成成功后异步回写 sys_id_sequence 表保持同步(Swoole 协程优先,无协程时同步写入)
  4. MySQL 兜底:Redis 不可用时自动回退到 MySQL INSERT ... ON DUPLICATE KEY UPDATE 原子自增

AliIotUtil - 阿里云 IoT 消息推送与设备管理

需要配置环境变量:

ALIBABA_CLOUD_ACCESS_KEY_ID=your-key-id
ALIBABA_CLOUD_ACCESS_KEY_SECRET=your-key-secret
use Ssh\CommonUtil\AliIotUtil;

// 发送消息到单个设备
$result = AliIotUtil::publish(
    productKey: 'your-product-key',
    deviceName: 'your-device-name',
    payload: ['cmd' => 'restart'],  // 数组或字符串
    iotInstanceId: 'your-instance-id',
    topic: 'get',   // 自定义 Topic 后缀,默认 'get'
    qos: 0          // QoS0 或 QoS1
);

// 批量发送消息(deviceName 必须为数组)
$result = AliIotUtil::batchPublish(
    productKey: 'your-product-key',
    deviceName: ['device1', 'device2'],
    payload: ['cmd' => 'update'],
    iotInstanceId: 'your-instance-id'
);

// 广播消息
$result = AliIotUtil::pubBroadcast('your-product-key', ['msg' => 'hello all']);

// RPC 同步调用 / 异步 RPC 调用
$result = AliIotUtil::rRpc('product-key', 'device-name', ['cmd' => 'status'], timeout: 8000);
$result = AliIotUtil::asyncRRpc('product-key', 'device-name', ['cmd' => 'notify']);

// 设备管理
$result = AliIotUtil::registerDevice('product-key', 'device-name', 'nickname');
$result = AliIotUtil::queryDeviceInfo('product-key', 'device-name');
$result = AliIotUtil::queryDeviceDetail('product-key', 'device-name');
$result = AliIotUtil::getDeviceStatus('product-key', 'device-name');
$result = AliIotUtil::deleteDevice('product-key', 'device-name');

// ClientId 管理
$result = AliIotUtil::queryClientIds('iot-id');
$result = AliIotUtil::transformClientId('iot-id', 'client-id');
$result = AliIotUtil::deleteClientIds('iot-id');

所有方法返回统一格式数组:['code' => 200/400/500, 'msg' => '...', 'data' => [...]]

AliIotAmqpUtil - 阿里云 IoT AMQP 认证

用于生成阿里云 IoT 平台 AMQP 客户端连接所需的用户名和密码。

use Ssh\CommonUtil\AliIotAmqpUtil;

$amqp = AliIotAmqpUtil::getInstance(
    accessKey: 'your-access-key',
    accessSecret: 'your-access-secret',
    consumerGroupId: 'your-consumer-group-id',
    iotInstanceId: 'your-iot-instance-id'
);

$amqp->getIotLoginPasscode();
$userName = $amqp->getUserName();
$passWord = $amqp->getPassWord();
// 将 $userName 和 $passWord 传入 AMQP 客户端连接配置中

AliOssUtil - 阿里云 OSS 对象存储

需要配置环境变量:

ALIYUN.OSS.ACCESS_KEY_ID=your-key-id
ALIYUN.OSS.ACCESS_KEY_SECRET=your-key-secret
ALIYUN.OSS.REGION_ID=cn-shenzhen
use Ssh\CommonUtil\AliOssUtil;

// 列出 Bucket 下所有对象
$result = AliOssUtil::listObjects('my-bucket', prefix: 'images/');

// 判断对象是否存在
$result = AliOssUtil::isObjectExist('my-bucket', 'images/a.jpg');

// 上传文件 / 获取上传预签名 URL(前端直传用)
$result = AliOssUtil::putObject('my-bucket', 'images/a.jpg', '/local/path/a.jpg');
$result = AliOssUtil::putObjectSignUrl('my-bucket', 'images/b.jpg');

// 下载对象(返回 Base64 编码内容)/ 获取下载预签名 URL
$result = AliOssUtil::getObject('my-bucket', 'images/a.jpg');
$result = AliOssUtil::getObjectSignUrl('my-bucket', 'images/a.jpg', expire: 3600);

// 删除对象
$result = AliOssUtil::deleteObject('my-bucket', 'images/a.jpg');

AliSmsUtil - 阿里云短信

需要配置环境变量:

ALIYUN.SMS.ACCESS_KEY_ID=your-key-id
ALIYUN.SMS.ACCESS_KEY_SECRET=your-key-secret
use Ssh\CommonUtil\AliSmsUtil;

$result = AliSmsUtil::sendSms(
    phone: '13800138000',
    signName: '你的签名',
    templateCode: 'SMS_123456789',
    templateParam: ['code' => '123456']
);

if ($result['code'] == 200) {
    echo '发送成功';
}

SmCryptoUtil - 国密 SM2/SM3/SM4 加解密

完整实现了与 Java 端互通的国密加解密流程,支持请求发送和接收两种场景。

依赖: lpilp/guomi、PHP GMP 扩展、OpenSSL 1.1.1+

use Ssh\CommonUtil\SmCryptoUtil;

// ===== 发送加密请求 =====
$result = SmCryptoUtil::sendRequestMessage(
    data: ['orderId' => '123', 'amount' => 100],
    publicKey: '02a1b2c3d4...',     // 对方公钥(压缩格式或非压缩格式均可)
    url: 'https://api.example.com/endpoint',
    appId: 'your-app-id',
    channelCode: 'your-channel',
    appChannelCode: 'your-app-channel'
);
// $result => ['code' => 200, 'msg' => '验签成功', 'data' => [...]]

// ===== 接收并解密请求 =====
$decrypted = SmCryptoUtil::receiveRequestMessage(
    json: $requestBodyJson,          // 收到的请求 JSON 字符串
    privateKey: 'your-private-key'   // 自己的私钥
);

单独使用 SM4 加解密:

// SM4-CBC 加密 / 解密
$encrypted = SmCryptoUtil::sm4EncryptCbc('hello world', $keyHex, $ivHex);
$decrypted = SmCryptoUtil::sm4DecryptCbc($encryptedHex, $keyHex, $ivHex);

SM2 公钥处理与工具方法:

$uncompressed = SmCryptoUtil::decompressSm2PublicKey('02a1b2c3...'); // 压缩 → 非压缩公钥
$cleanKey     = SmCryptoUtil::stripPrivateKeyPrefix('00abcdef...'); // 去除 Java 私钥前导 "00"

$random    = SmCryptoUtil::getRandom(16);              // 随机字符串
$hex       = SmCryptoUtil::string2HexString('Ab0');     // "416230"
$sorted    = SmCryptoUtil::getSortJson($array);          // 递归按键名排序

SymmetricEncoder - AES 加解密

兼容 Java SHA1PRNG 密钥派生 + AES/ECB/PKCS5Padding + 双重 Base64 编码的加解密逻辑(适配 Java 21)。

use Ssh\CommonUtil\SymmetricEncoder;

$seed = 'your-secret-seed';

// 加密(返回双重 Base64 字符串)
$encrypted = SymmetricEncoder::aesEncrypt($seed, 'hello world');

// 解密
$decrypted = SymmetricEncoder::aesDecrypt($seed, $encrypted);

// 调试:查看派生密钥的十六进制值(32 字符 hex)
$keyHex = SymmetricEncoder::getDerivedKeyHex($seed);

SunmiCloudPrinter - 商米云打印机

支持 ESC/POS 指令构建打印内容,并通过商米云 API 推送到打印机。构造时可传入 PSR-3 Logger 实现日志记录。

use Ssh\CommonUtil\SunmiCloudPrinter;

// 不传 logger 则不记录日志
$printer = new SunmiCloudPrinter(dots_per_line: 384, logger: $psr3Logger);

// ===== 构建打印内容 =====
$printer->restoreDefaultSettings();
$printer->setUtf8Mode(1);

// 标题居中、放大
$printer->setAlignment(SunmiCloudPrinter::ALIGN_CENTER);
$printer->setCharacterSize(2, 2);
$printer->appendText("收银小票");
$printer->lineFeed();

// 正文左对齐、正常大小
$printer->restoreDefaultSettings();
$printer->setUtf8Mode(1);
$printer->setAlignment(SunmiCloudPrinter::ALIGN_LEFT);
$printer->appendText("商品: 拿铁咖啡  x1  ¥28.00");
$printer->lineFeed();

// 分列打印
$printer->setupColumns(
    [192, SunmiCloudPrinter::ALIGN_LEFT, 0],
    [192, SunmiCloudPrinter::ALIGN_RIGHT, 0]
);
$printer->printInColumns("合计:", "¥28.00");
$printer->lineFeed(2);

// 二维码
$printer->setAlignment(SunmiCloudPrinter::ALIGN_CENTER);
$printer->appendQRcode(4, 1, "https://example.com/order/123");
$printer->lineFeed(3);

$printer->cutPaper(true);

// ===== 推送到打印机 =====
$printer->pushContent(
    sn: '打印机SN号',
    trade_no: 'ORDER_001',
    order_type: 1,
    count: 1
);

// ===== 其他设备管理接口 =====
$printer->onlineStatus('打印机SN号');        // 查询在线状态
$printer->clearPrintJob('打印机SN号');       // 清除打印队列
$printer->printStatus('ORDER_001');          // 查询打印状态
$printer->bindShop('打印机SN号', 'shop1');   // 绑定店铺
$printer->unbindShop('打印机SN号', 'shop1'); // 解绑店铺

许可证

MIT License