Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion server/src/controllers/misound.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,21 @@ class MisoundController {
}
}

/**
* 从请求体中提取小爱音箱扩展播放配置
* 空值保持为空,由渠道 validate 与 send 侧做默认处理
*/
static _extractPlaybackConfig(body = {}) {
return {
startVolume: body.startVolume,
endVolume: body.endVolume,
playCount: body.playCount,
playInterval: body.playInterval,
audioUrl: body.audioUrl,
endVolumeDelay: body.endVolumeDelay,
};
}

/**
* 确认绑定并创建渠道
*
Expand All @@ -96,6 +111,7 @@ class MisoundController {
static async confirmBind(req, res) {
try {
const { userId, passToken, did, name, ttsMode } = req.body;
const playbackConfig = MisoundController._extractPlaybackConfig(req.body);

if (!userId || !passToken) {
return ResponseUtil.badRequest(res, '缺少登录凭证');
Expand All @@ -104,7 +120,7 @@ class MisoundController {
return ResponseUtil.badRequest(res, '请输入设备名称');
}

// 创建渠道,配置中存储扫码获取的凭证
// 创建渠道,配置中存储扫码获取的凭证与播放增强参数
const channel = await ChannelService.createChannel(req.user.userId, {
channelType: 'misound',
name: name || '小爱音箱',
Expand All @@ -113,6 +129,7 @@ class MisoundController {
passToken,
did,
ttsMode: ttsMode || 'auto',
...playbackConfig,
},
});

Expand Down Expand Up @@ -140,6 +157,7 @@ class MisoundController {
try {
const channelId = parseInt(req.params.channelId);
const { userId, passToken, did, ttsMode } = req.body;
const playbackConfig = MisoundController._extractPlaybackConfig(req.body);

if (!userId || !passToken) {
return ResponseUtil.badRequest(res, '缺少登录凭证');
Expand All @@ -151,6 +169,7 @@ class MisoundController {
passToken,
did: did || '',
ttsMode: ttsMode || 'auto',
...playbackConfig,
},
});

Expand Down
36 changes: 30 additions & 6 deletions server/src/controllers/push.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ const getRealIP = require('../utils/ip');
* 推送控制器
*/
class PushController {
/**
* 从请求源中提取小爱音箱可选覆盖字段(仅 misound 渠道识别,其他渠道忽略)
*/
static _extractMisoundOverrides(source = {}) {
return {
volume: source.volume,
audioUrl: source.audioUrl,
playCount: source.playCount,
playInterval: source.playInterval,
};
}

/**
* 通过接口令牌推送
*/
Expand All @@ -27,10 +39,18 @@ class PushController {

// 支持 POST body 或 GET query 参数
const source = req.method === 'GET' ? req.query : req.body;
const { title, content, type = 'text', extraData } = source;
const { title, type = 'text', extraData } = source;
// content 可空:小爱音箱可仅传 audioUrl
const content = source.content == null ? '' : source.content;
const url = source.url || '';
const misoundOverrides = PushController._extractMisoundOverrides(source);

const result = await PushService.pushByToken(token, { title, content, type, url, extraData }, getRealIP(req), req.requestId);
const result = await PushService.pushByToken(
token,
{ title, content, type, url, extraData, ...misoundOverrides },
getRealIP(req),
req.requestId
);

if (result.success) {
return ResponseUtil.success(res, result, '推送成功');
Expand All @@ -49,13 +69,15 @@ class PushController {
static async pushByEndpoint(req, res) {
try {
const endpointId = parseInt(req.params.endpointId);
const { title, content, type = 'text', extraData } = req.body;
const { title, type = 'text', extraData } = req.body;
const content = req.body.content == null ? '' : req.body.content;
const url = req.body.url || '';
const misoundOverrides = PushController._extractMisoundOverrides(req.body);

const result = await PushService.pushByEndpoint(
endpointId,
req.user.userId,
{ title, content, type, url, extraData },
{ title, content, type, url, extraData, ...misoundOverrides },
getRealIP(req),
req.requestId
);
Expand All @@ -80,13 +102,15 @@ class PushController {
static async pushByChannel(req, res) {
try {
const channelId = parseInt(req.params.channelId);
const { title, content, type = 'text', extraData } = req.body;
const { title, type = 'text', extraData } = req.body;
const content = req.body.content == null ? '' : req.body.content;
const url = req.body.url || '';
const misoundOverrides = PushController._extractMisoundOverrides(req.body);

const result = await PushService.pushByChannel(
channelId,
req.user.userId,
{ title, content, type, url, extraData },
{ title, content, type, url, extraData, ...misoundOverrides },
getRealIP(req),
req.requestId
);
Expand Down
38 changes: 32 additions & 6 deletions server/src/middleware/validator.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,21 @@ const pushMessageValidation = [
.isLength({ max: 200 })
.withMessage('标题不能超过200个字符'),

// content 默认可空:小爱音箱可仅用 audioUrl 推送;其他渠道仍建议传 content
body('content')
.optional()
.trim()
.notEmpty()
.withMessage('消息内容不能为空')
.isLength({ max: 5000 })
.withMessage('消息内容不能超过5000个字符'),
.optional({ nullable: true })
.custom((value, { req }) => {
const content = value == null ? '' : String(value);
const source = req.method === 'GET' ? req.query : req.body;
const audioUrl = source && source.audioUrl;
if (!content.trim() && !audioUrl) {
throw new Error('消息内容不能为空(小爱音箱可仅传 audioUrl)');
}
if (content.length > 5000) {
throw new Error('消息内容不能超过5000个字符');
}
return true;
}),

body('type')
.optional()
Expand All @@ -185,6 +193,24 @@ const pushMessageValidation = [
.isObject()
.withMessage('extraData 必须是对象'),

// 小爱音箱可选覆盖字段
body('volume')
.optional()
.isInt({ min: 0, max: 100 })
.withMessage('volume 必须是 0-100 的整数'),
body('playCount')
.optional()
.isInt({ min: 1, max: 10 })
.withMessage('playCount 必须是 1-10 的整数'),
body('playInterval')
.optional()
.isFloat({ min: 0, max: 300 })
.withMessage('playInterval 必须是 0-300 的数字'),
body('audioUrl')
.optional()
.isString()
.isLength({ max: 2000 })
.withMessage('audioUrl 长度不能超过2000'),
handleValidationErrors,
];

Expand Down
Loading
Loading