各位大大好,我这边使用 uni-push2 + uniCloud URL 化云函数时,遇到一个可稳定复现的问题,拜谢!
我在云函数中调用:
uniCloud.getPushManager({ appId: "UNI**" })
随后无论调用:
- getClientDetailByCid("...")
- sendMessage({...})
都会返回:
appId 不能为空
但我已经在云函数返回里加了调试字段,确认云函数内部实际拿到的 appId 是正确且非空的,例如:
- appId = "UNI**"
- operation = "getClientDetailByCid" / "sendMessage"
- pushClientId = "..."
我已经排查过:
- 不是旧 AppID 污染:原应用和全新新建应用都复现
- 不是旧包名污染:两个不同包名都复现
- 不是请求体没传到云函数:调试字段能看到正确 appId
- 不是白名单没更新:白名单不匹配时返回的是 appId_not_allowed,更新部署后变成 appId 不能为空
- 不是 sendMessage 特有问题:getClientDetailByCid 也同样报错
- 不是初始化时机问题:请求内初始化、缓存初始化、模块顶层初始化都试过,结果一致
我还做了全新应用隔离实验:
- 原应用 AppID:UNI14****
- 新应用 AppID:UNI07****
- 原包名:com.*****.workbench
- 新包名:com.*****.testpush2
两套配置都能成功注册 push,状态都是 registered,但调用 getPushManager 后续 API 时都一样报:
appId 不能为空
所以现在怀疑不是项目配置问题,而是 uniCloud.getPushManager({ appId }) / uni-push 运行环境侧异常。
想请教:
- 这个问题是否有已知平台异常?
- 为什么云函数里已经拿到正确 appId,后续仍报“appId 不能为空”?
- 是否与 uniCloud 服务空间、uni-push2 关联状态或平台内部映射有关?
补充:
“安卓真机已成功注册,push 状态为 registered;CID 有效;uni-push2 到真机的基础链路正常(消息推送到手机成功)。当前异常不是‘发不到手机’,而是 uniCloud.getPushManager({ appId }) 调用后,getClientDetailByCid / sendMessage 都返回‘appId 不能为空’。”
继续补充 1:脱敏后的云函数代码
```js
'use strict';
const crypto = require('crypto');
const gatewayConfig = require('./config.json');
const DEFAULT_TTL_MS = 60 * 60 * 1000;
const DEFAULT_TIME_DIFF_TOLERANCE_MS = 60 * 1000;
const DEFAULT_SIGN_METHOD = 'hmac-sha256';
const STATIC_ALLOWED_APP_IDS = Array.isArray(gatewayConfig.allowedAppIds)
? gatewayConfig.allowedAppIds.map((item) => String(item || '').trim()).filter(Boolean)
: [];
const STATIC_UNI_PUSH_MANAGERS = STATIC_ALLOWED_APP_IDS.reduce((acc, appId) => {
acc.set(appId, uniCloud.getPushManager({ appId }));
return acc;
}, new Map());
function normalizePositiveInteger(value, fallbackValue) {
const normalized = Number(value);
if (!Number.isFinite(normalized) || normalized <= 0) return fallbackValue;
return Math.floor(normalized);
}
function sortDeep(value) {
if (Array.isArray(value)) return value.map(sortDeep);
if (!value || typeof value !== 'object') return value;
return Object.keys(value).sort().reduce((acc, key) => {
const current = value[key];
if (current === undefined) return acc;
acc[key] = sortDeep(current);
return acc;
}, {});
}
function stableStringify(value) {
return JSON.stringify(sortDeep(value));
}
function hmacSha256Text(text, key) {
return crypto.createHmac('sha256', String(key || '')).update(String(text || '')).digest('hex');
}
function getHeader(headers, key) {
if (!headers || typeof headers !== 'object') return '';
const foundKey = Object.keys(headers).find((item) => String(item || '').toLowerCase() === String(key || '').toLowerCase());
return foundKey ? String(headers[foundKey] || '').trim() : '';
}
function getRuntimeConfig() {
return {
signMethod: String(gatewayConfig.signMethod || DEFAULT_SIGN_METHOD).trim().toLowerCase() || DEFAULT_SIGN_METHOD,
signKey: String(gatewayConfig.signKey || '').trim(),
timeDiffToleranceMs: normalizePositiveInteger(gatewayConfig.timeDiffToleranceMs, DEFAULT_TIME_DIFF_TOLERANCE_MS),
allowedAppIds: Array.isArray(gatewayConfig.allowedAppIds)
? gatewayConfig.allowedAppIds.map((item) => String(item || '').trim()).filter(Boolean)
: [],
};
}
function getUniPushManager(appId) {
const normalizedAppId = String(appId || '').trim();
if (!normalizedAppId) {
throw new Error('appId_required');
}
if (STATIC_UNI_PUSH_MANAGERS.has(normalizedAppId)) {
return STATIC_UNI_PUSH_MANAGERS.get(normalizedAppId);
}
return uniCloud.getPushManager({ appId: normalizedAppId });
}
function parseRequestBody(event) {
if (event && typeof event === 'object' && event.body && typeof event.body === 'string') {
return JSON.parse(event.body || '{}');
}
if (event && typeof event === 'object' && event.body && typeof event.body === 'object') {
return event.body;
}
return event && typeof event === 'object' ? event : {};
}
function verifyHttpSignature(event, body, config) {
const timestamp = getHeader(event.headers, 'Unicloud-S2s-Timestamp');
const signatureHeader = getHeader(event.headers, 'Unicloud-S2s-Signature');
if (!timestamp || !signatureHeader) {
throw new Error('gateway_signature_missing');
}
const [signMethod, signatureValue] = signatureHeader.split(/\s+/, 2);
if (String(signMethod || '').trim().toLowerCase() !== config.signMethod || !signatureValue) {
throw new Error('gateway_signature_invalid');
}
const normalizedTimestamp = Number(timestamp);
if (!Number.isFinite(normalizedTimestamp) || normalizedTimestamp <= 0) {
throw new Error('gateway_timestamp_invalid');
}
if (Math.abs(Date.now() - normalizedTimestamp) > config.timeDiffToleranceMs) {
throw new Error('gateway_timestamp_expired');
}
const expectedSignature = hmacSha256Text(`${timestamp}\n${stableStringify(body)}`, config.signKey);
if (expectedSignature !== String(signatureValue || '').trim()) {
throw new Error('gateway_signature_mismatch');
}
}
function normalizePayload(body, config) {
const operation = String(body.operation || body.action || 'sendMessage').trim();
const appId = String(body.appId || '').trim();
const pushClientId = String(body.pushClientId || '').trim();
if (!appId) throw new Error('appId_required');
if (!pushClientId) throw new Error('pushClientId_required');
if (config.allowedAppIds.length > 0 && !config.allowedAppIds.includes(appId)) {
throw new Error('appId_not_allowed');
}
return {
operation,
appId,
pushClientId,
title: String(body.title || '').trim(),
content: String(body.content || '').trim(),
payload: body.payload && typeof body.payload === 'object' ? body.payload : {},
ttl: normalizePositiveInteger(body?.settings?.ttl, DEFAULT_TTL_MS),
requestId: String(body.requestId || Date.now()),
};
}
async function executeUniPushOperation(uniPush, normalized) {
if (normalized.operation === 'getClientDetailByCid') {
return uniPush.getClientDetailByCid(normalized.pushClientId);
}
return uniPush.sendMessage({
request_id: normalized.requestId,
push_clientid: normalized.pushClientId,
title: normalized.title,
content: normalized.content,
payload: normalized.payload,
settings: {
ttl: normalized.ttl,
},
});
}
exports.main = async (event, context) => {
let normalized = null;
try {
const config = getRuntimeConfig();
const body = parseRequestBody(event);
if (context?.source === 'http') {
verifyHttpSignature(event, body, config);
}
normalized = normalizePayload(body, config);
const uniPush = getUniPushManager(normalized.appId);
const result = await executeUniPushOperation(uniPush, normalized);
return {
ok: true,
status: 'success',
data: result,
};
} catch (error) {
return {
ok: false,
status: 'failed',
message: error instanceof Error ? error.message : 'push_gateway_failed',
debug: {
appId: normalized?.appId || null,
operation: normalized?.operation || null,
pushClientId: normalized?.pushClientId || null,
},
};
}
};
继续补充 2:脱敏后的 config.json
{
"signMethod": "hmac-sha256",
"signKey": "************************",
"timeDiffToleranceMs": 60000,
"allowedAppIds": ["__UNI__******"]
}
继续补充 3:脱敏后的请求体
getClientDetailByCid 请求体:
{
"operation": "getClientDetailByCid",
"appId": "__UNI__******",
"pushClientId": "7c74fc******dfef",
"requestId": "cidcheck********"
}
sendMessage 请求体:
{
"appId": "__UNI__******",
"pushClientId": "7c74fc******dfef",
"title": "testpush2 隔离实验",
"content": "验证新的 AppID 路径",
"payload": {
"source": "testpush2-isolation",
"ts": 17**********,
"route": "/pages/notifications/index"
},
"settings": {
"ttl": 3600000
},
"requestId": "offtest********"
}
继续补充 4:脱敏后的请求头结构
Content-Type: application/json;charset=utf-8
Unicloud-S2s-Timestamp: 17**********
Unicloud-S2s-Signature: hmac-sha256 ************************
继续补充 5:脱敏后的报错结果
原应用复现时返回:
{
"ok": false,
"status": "failed",
"message": "appId 不能为空",
"debug": {
"appId": "__UNI__14****",
"operation": "getClientDetailByCid",
"pushClientId": "84bf05******8a9"
}
}
{
"ok": false,
"status": "failed",
"message": "appId 不能为空",
"debug": {
"appId": "__UNI__14****",
"operation": "sendMessage",
"pushClientId": "84bf05******8a9"
}
}
新应用隔离实验返回:
{
"ok": false,
"status": "failed",
"message": "appId 不能为空",
"debug": {
"appId": "__UNI__07****",
"operation": "getClientDetailByCid",
"pushClientId": "7c74fc******dfef"
}
}
{
"ok": false,
"status": "failed",
"message": "appId 不能为空",
"debug": {
"appId": "__UNI__07****",
"operation": "sendMessage",
"pushClientId": "7c74fc******dfef"
}
}
继续补充 6:本地服务端环境变量(脱敏)
UNIPUSH2_MODE=real
UNIPUSH2_APP_ID=__UNI__******
UNIPUSH2_CLOUD_ENDPOINT=https://*.next.bspapp.com/*
UNIPUSH2_S2S_SIGN_KEY=************************
UNIPUSH2_S2S_SIGN_METHOD=hmac-sha256
UNIPUSH2_TIMEOUT_MS=5000
UNIPUSH2_TTL_MS=3600000
1 个回复
Ask小助手 - Ask社区AI助手
我在知识库中找到了与您描述高度相似的问题报告:【报Bug】IM 发送消息 参数有appid 但是结果还是提示appid不能为空。该案例中用户同样遇到了请求参数中包含 appId 但服务端仍返回"参数:appId的值不能为空"的情况,这表明您遇到的问题可能存在已知的平台侧异常。
关于您询问的具体原因(为何云函数内已获取到正确 appId 但后续调用仍报错、是否与 uniCloud 服务空间或 uni-push2 关联状态有关),知识库中暂未找到明确的技术解释或官方修复说明。
建议您通过以下渠道联系官方技术支持进行报障和咨询:
(报障处理参考优先知识库中 uni-config-center 配置未生效问题的官方处理流程)
要回复问题请先登录或注册
公告
更多>相关问题