红包场景实践
场景说明
用户在单聊/群聊会话中向指定好友发送红包(实际货币、虚拟货币、积分等),发送人和接收人可以明确感知红包是否被领取的状态(未领取、已领取)。
效果示例

红包消息流程
1. 准备工作
在开始之前,请确保已创建应用并完成客户端 SDK 集成。
2. 定义红包消息
2.1 定义红包消息类型
您可通过 IMLib SDK 的自定义消息,创建红包类消息类型(如 RedPacketMessage
),消息内容结构需根据业务需求自行定义,确保多端(Android / iOS / Web)一致。
Android / iOS 端自定义消息类参考:
- Android 端 CustomRedPacketMessage.java
- iOS 端 CustomRedPacketMessage.h
- iOS 端 CustomRedPacketMessage.m
- Server SDK RedPacketMessage.java
Web 端示例代码:
// 1. RongIMLib.registerMessageType 必须在 connect 之前调用,否则可能造成收取消息的行为异常。
// 2. 请尽量把应用中所有涉及到的自定义消息统一一次注册,且同类型消息仅 调用一次注册,便于管理。
const messageType = 'app:red_packet' // 消息类型
const isPersited = true // 是否存储
const isCounted = true // 是否计数
const searchProps = [] // 搜索字段,Web 端无需设置,搜索字段值设置为数字时取值范围为 (-Math.pow(2, 64), Math.pow(2, 64)) 且为整数
const isStatusMessage = false // 是否是状态消息。状态消息不存储、不计数,接收方在线时才能收到。
const PersonMessage = RongIMLib.registerMessageType(messageType, isPersited, isCounted, searchProps, isStatusMessage)
2.2 定义红包消息展示模板
如果您使用的是 IMKit SDK,必须创建对应的消息展示模板,否则 SDK 无法正常展示该类型消息。
Android / iOS 端自定义红包消息展示模板参考类:
- Android 端 CustomRedPacketMessageItemProvider.java
- iOS 端 CustomRedPacketMessageCell.h
- iOS 端 CustomRedPacketMessageCell.m
Web IMKit 设置自定义消息样式示例代码
// 构造 IMKit 初始化参数
const customMessage = {
// 普通消息展示
userMessage: {
// key 为自定义消息的 messageType,return 的元素中暂不支持设置 class,如需设置样式可设置为行内样式。
'app:red_packet': (message) => {
const content = message.content;
return `<div style='padding: 0.5em 0.8333em;'>来自 ${content.name} 的红包</div>`;
}
},
// 通知类消息展示
notifyMessage: {
// key 为自定义消息的 messageType,return 的元素中暂不支持设置 class,如需设置样式可设置为行内样式。
'app:red_packet': (message) => {
const content = message.content
const string = `<div>来自 ${content.name} 的红包</div>`
return string;
}
},
// 会话最后一条消息展示
lastMessage:{
// key 为自定义消息的 messageType,return 的元素中暂不支持设置 class,如需设置样式可设置为行内样式。
'app:red_packet': (message) => {
const content = message.content;
return `[红包信息]`;
},
}
};
// 特别注意:此处 init 仅为展示自定义消息设置,应用内不需要多次进行初始化
imkit.init({
customMessage:customMessage
});
3. 注册并接收红包消息
- Android
- iOS
- Web
// 注册自定义消息类型,初始化 SDK 后注册
ArrayList<Class<? extends MessageContent>> myMessages = new ArrayList<>();
myMessages.add(CustomRedPacketMessage.class);
RongCoreClient.registerMessageType(myMessages);
// 接收消息示例:设置接收消息监听器,接收消息时会自动回调,如果您使用的 IMLib SDK,请调用 RongCoreClient 的 addOnReceiveMessageListener 方法
IMCenter.addOnReceiveMessageListener(
new io.rong.imlib.listener.OnReceiveMessageWrapperListener() {
@Override
public boolean onReceivedMessage(Message message, ReceivedProfile profile) {
int left = profile.getLeft();
boolean isOffline = profile.isOffline();
boolean hasPackage = profile.hasPackage();
}
});
// 返回的 `Message` 实体参考下面信息,可以使用 `objectName` 或者 `content` 来区分消息类型:
{
"conversationType": "PRIVATE",
"targetId": "userid3453",
"messageId": 70,
"channelId": "",
"messageDirection": "RECEIVE",
"senderUserId": "userid3453",
"receivedStatus": "io.rong.imlib.model.Message$ReceivedStatus @560f848",
"sentStatus": "SENT",
"receivedTime": 1739428279001,
"sentTime": 1739428279158,
"objectName": "app:red_packet",
"content": {
"content": "红包消息"
},
"extra": "",
"readReceiptInfo": "io.rong.imlib.model.ReadReceiptInfo @b8d3c06",
"messageConfig": {
"disablePushTitle": false,
"pushTitle": "",
"pushContent": "",
"pushData": "null",
"templateId": "",
"forceShowDetailContent": false,
"iOSConfig": null,
"androidConfig": null,
"harmonyConfig": null
},
"canIncludeExpansion": false,
"expansionDic": null,
"expansionDicEx": null,
"mayHasMoreMessagesBefore": false,
"UId": "CKVO-0J6T-GM26-D3E6",
"disableUpdateLastMessage": "false",
"directedUsers": "0"
}
// 在初始化 SDK 后,连接 IM 之前注册自定义消息
[[RCCoreClient sharedCoreClient] registerMessageType:[CustomRedPacketMessage class]];
// 注册接收消息监听,IMLib 用户设置 [RCCoreClient sharedCoreClient] 的代理
[RCIM sharedRCIM].receiveMessageDelegate = self;
// IMKit 的代理回调:
- (void)onRCIMReceiveMessage:(RCMessage *)message left:(int)left;
// IMLib 的代理回调
- (void)onReceived:(RCMessage *)message left:(int)nLeft object:(nullable id)object;
// 获取到的自定义消息的相关内容
_isOffline = false
_canIncludeExpansion = false
_hasChanged = false
_disableUpdateLastMessage = false
_conversationType = 1
_targetId = @"123"
_channelId = @""
_messageId = 17
_messageDirection = 2
_senderUserId = @"123"
_receivedStatusInfo RCReceivedStatusInfo * 0x3025eb5c0
_sentStatus = 30
_receivedTime = 1740566955000
_sentTime = 1740566955056
_objectName = @"app:red_packet"
_content CustomRedPacketMessage * 0x300691b80
_RCMessageContent RCMessageContent
_senderUserInfo
_mentionedInfo
_auditInfo
_destructDuration = 0
_extra
_rawJSONData
_contentBaseKeys
_content = @"红包"
_extra = @""
_messageUId = @"CL87-G08C-6L85-E222"
_readReceiptInfo
_groupReadReceiptInfoV2
_messageConfig RCMessageConfig * 0x30279cee0
_messagePushConfig RCMessagePushConfig * 0x300693110
_directedUserIds
_destructTime = 0
_expansionDicEx
// 设置消息监听
const Events = RongIMLib.Events
RongIMLib.addEventListener(Events.MESSAGES, (evt) => {
console.log(evt.messages)
})
// 注册自定义消息
const PersonMessage = RongIMLib.registerMessageType('app:red_packet', true, true, [], false)
// 发送自定义消息
// 构建要发送的自定义消息
const message = new PersonMessage({ name: 'someone', age: 18 })
// 接收该自定义消息示例
// 调用 addEventListener 设置消息接收监听器。所有接收到的消息都会在此接口方法中回调。
// 返回 IAReceivedMessage 类型的消息数组,类型请参考:https://doc.rongcloud.cn/apidoc/im-web/latest/zh_CN/interfaces/IMessagesEvent.html
const Events = RongIMLib.Events
const listener = (evt) => {
console.log(evt.messages)
};
RongIMLib.addEventListener(Events.MESSAGES, listener)
// 返回的 `Message` 实体参考下面信息,可以使用 `messageType` 来区分消息类型:
{
// 可根据 messageType 筛选业务层要处理的礼物消息
"messageType": "app:red_packet",
"channelId": "",
"content": {
"content": "红包消息"
},
"senderUserId": "user01",
"targetId": "chart01",
"conversationType": 4,
"sentTime": 1743414376795,
"receivedTime": 0,
"messageUId": "消息唯一值",
"messageDirection": 1,
"isPersited": true,
"isCounted": true,
"isMentioned": false,
"disableNotification": false,
"isStatusMessage": false,
"canIncludeExpansion": false,
"expansion": null,
"receivedStatus": 0,
"receivedStatusInfo": {
"isRead": false,
"isListened": false,
"isDownload": false,
"isRetrieved": false
},
"messageId": 8488190336485840000,
"sentStatus": 30,
"isOffLineMessage": false
}
4. 扩展区域展示(发送红包入口)
4.1 设置红包扩展面板插件
如果您使用的是 IMLib SDK,您需要自行实现发送红包入口。 如果您使用的是 IMKit SDK,可以在 IMKit 的扩展面板中添加自定义插件。
Android 端实现流程
自定义插件需要实现 IPluginModule
接口类,可以参考 IMKit 源码中的 IPluginModule.java
,以及具体的实现类。
此处以实现自定义插件 RedPacketPlugin
示例类为例。
iOS 端实现流程
在聊天页面的 viewDidLoad
方法中插入对应的红包扩展插件图标
[self.chatSessionInputBarControl.pluginBoardView insertItem:[UIImage imageNamed:@"redPacket"] highlightedImage:[UIImage imageNamed:@"redPacket"] title:@"发红包" tag:20080];
Web IMKit 没有扩展面板概念
4.2 配置扩展面板插件
示例代码
- Android
- iOS
- Web
// 1. 继承 `DefaultExtensionConfig`,创建自定义的扩展面板配置类 `MyExtensionConfig`,重写 `getPluginModules()` 方法。
public class MyExtensionConfig extends DefaultExtensionConfig {
@Override
public List<IPluginModule> getPluginModules(Conversation.ConversationType conversationType, String targetId) {
List<IPluginModule> pluginModules = super.getPluginModules(conversationType,targetId);
// 增加红包扩展项
pluginModules.add(new RedPacketPlugin());
return pluginModules;
}
}
// 2. SDK 初始化之后,调用 `setExtensionConfig` 方法设置自定义的输入配置,SDK 会根据此配置展示扩展面板。
RongExtensionManager.getInstance().setExtensionConfig(new MyExtensionConfig());
// 在聊天页面重写插件的点击方法,实现红包发送相关业务逻辑:
// 处理扩展项点击事件
- (void)pluginBoardView:(RCPluginBoardView *)pluginBoardView clickedItemWithTag:(NSInteger)tag {
// 红包扩展插件的 tag
if (tag == 20080) {
// 业务层实现红包发送相关逻辑
}else {
[super pluginBoardView:pluginBoardView clickedItemWithTag:tag];
}
}
// Web IMKit 不支持
5. 付款成功发送红包消息
在用户付款成功后,需要调用融云的发送消息方法,发送红包消息并设置消息可扩展。根据业务侧逻辑选择从哪个端发送此类消息。
- Android
- iOS
- Web
// 构建红包消息
CustomRedPacketMessage redPacketMessage = CustomRedPacketMessage.obtain("0.01");
io.rong.imlib.model.Message message =
io.rong.imlib.model.Message.obtain(targetId, conversationType, redPacketMessage);
// 设置消息可扩展
message.setCanIncludeExpansion(true);
HashMap<String, String> redInfo = new HashMap<>();
redInfo.put("open","false");
redInfo.put("count","1");
redInfo.put("amount","0.01");
message.setExpansion(redInfo);
// 发送消息,如果您使用的 IMLib SDK,请使用 RongCoreClient 的 sendMessage 方法
IMCenter.getInstance().sendMessage(message, null, null, null);
// 构建红包消息
CustomRedPacketMessage *message = [CustomRedPacketMessage messageWithContent:@"0,01"];
RCMessage *msg = [[RCMessage alloc] initWithType:self.conversationType targetId:self.targetId direction:MessageDirection_SEND content:message];
// 设置消息可扩展
msg.canIncludeExpansion = YES;
msg.expansionDic = @{@"open":@"false",@"count":@"1",@"amount":@"0.01"};
// 发送消息,如果您使用的 IMLib SDK,请使用 RCCoreClient 的 sendMessage 方法
[[RCIM sharedRCIM] sendMessage:msg pushContent:@"红包来了" pushData:nil successBlock:^(RCMessage *successMessage) {
} errorBlock:^(RCErrorCode nErrorCode, RCMessage *errorMessage) {
}];
// 构建要发送的自定义红包消息
const message = new PersonMessage({ key1: 'value1', key2: 'value2' })
// 发送消息
RongIMLib.sendMessage({
conversationType: RongIMLib.ConversationType.PRIVATE,
targetId: '<targetId>'
}, message).then(res => {
if (res.code === 0) {
console.log(res.code, res.data)
} else {
console.log(res.code)
}
})
服务端示例代码
- Server SDK in Java
- Server SDK in PHP
- Server SDK in GO
/**
* Send group gray bar message - targeted users (up to 1000 users per request)
*/
String[] targetIds = {"groupId"};
RedPacketMessage redPacketMessage =new RedPacketMessage("红包消息");
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("open", "false");
hashMap.put("count", "1");
hashMap.put("amount", "2");
GroupMessage groupMessage = new GroupMessage()
.setSenderId("fromId")
.setIsIncludeSender(1)
.setTargetId(targetIds) // Group ID
.setContent(redPacketMessage)
.setObjectName(txtMessage.getType())
.setExpansion(true)
.setExtraContent(hashMap);
ResponseResult redPackeReslut = group.send(groupMessage);
System.out.println("group info Notify message result: " + redPackeReslut.toString());
require "./../../RongCloud.php";
define("APPKEY", '');
define('APPSECRET','');
use RongCloud\RongCloud;
use RongCloud\Lib\Utils;
/**
* Group message sending
*/
function send()
{
$RongSDK = new RongCloud(APPKEY,APPSECRET);
$message = [
'senderId'=> 'Vu-oC0_LQ6kgPqltm_zYtI',// Sender ID
'targetId'=> ['php group1'],// Group ID
"objectName"=>'app:red_packet',// Message type Text
'content'=>json_encode(['content'=>'红包消息'])// Message Body
'expansion'=>true,
'extraContent' => ['open' => 'false', 'count' => '2', 'amount' => '3'],// Custom message extension content, JSON structure, set in Key, Value format
];
$Result = $RongSDK->getMessage()->Group()->send($message);
Utils::dump("Group message sending",$Result);
}
send();
func TestRongCloud_GroupSend(t *testing.T) {
rc := NewRongCloud(
os.Getenv("APP_KEY"),
os.Getenv("APP_SECRET"),
REGION_BJ,
)
msg := TXTMsg{
Content: "红包消息",
Extra: "helloExtra",
}
// 创建一个包含多个键值对的 map
dataMap := map[string]string{
"open": "false",
"count": "1",
"amount": "2",
}
// 将 map 转换为 JSON 格式
data, err := json.Marshal(dataMap)
err := rc.GroupSend(
"7Szq13MKRVortoknTAk7W8",
[]string{"CFtiYbXNQNYtSr7rzUfHco"},
"app:red_packet",
&msg,
"",
"",
1,
0,
WithMsgExpansion(true),
WithMsgExtraContent(data),
)
t.Log(err)
}
6. 开启红包更新红包扩展
用户点击开启红包后,红包消息状态需要变更为「已开启」。此时,您可通过设置消息扩展监听 updateMessageExpansion
更新此条消息的扩展信息,扩展设置开启用户 id 以及标识为已开启状态,同时更改本地显示的消息样式。
- Android
- iOS
- Web
// messageUid 为原红包消息唯一 Id。
RongIMClient.getInstance()
.updateMessageExpansion(
redInfo,
messageUid,
new RongIMClient.OperationCallback() {
@Override
public void onSuccess() {
// 更新发起者在这里处理更新扩展后的 UI 数据刷新
IMCenter.getInstance().refreshMessage(currentMessage);
}
@Override
public void onError(RongIMClient.ErrorCode errorCode) {
Toast.makeText(
getApplicationContext(),
"设置失败,ErrorCode : " + errorCode.getValue(),
Toast.LENGTH_LONG)
.show();
}
});
// 会话页面数据源遍历数据
for (RCMessageModel *model in self.conversationDataRepository) {
if (model.messageUId == 红包的 messageUId) {
// 更新红包扩展
[[RCCoreClient sharedCoreClient] updateMessageExpansion:dict messageUId:model.messageUId success:^{
NSUInteger row = [self.conversationDataRepository indexOfObject:model];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:row inSection:0];
dispatch_async(dispatch_get_main_queue(), ^{
// 更新对应 UI
[self.conversationMessageCollectionView reloadItemsAtIndexPaths:@[indexPath]];
});
} error:^(RCErrorCode status) {
}];
}
}
// 更新红包消息的扩展字段
RongIMLib.updateMessageExpansion({ key1: 'val1', key2: 'val2' }, message).then(res => {
if (res.code === 0) {
console.log(res.code, '更新成功')
} else {
console.log(res.code, res.msg)
}
})
服务端示例代码
- Server SDK in Java
- Server SDK in PHP
- Server SDK in GO
/**
*
* Set message extension
*
*/
ExpansionModel msg = new ExpansionModel();
msg.setMsgUID("BS45-NPH4-HV87-10LM");
msg.setUserId("WNYZbMqpH");
msg.setTargetId("tjw3zbMrU");
msg.setConversationType(1);
HashMap<String, String> kv = new HashMap<String, String>();
kv.put("type1", "1");
kv.put("type2", "2");
kv.put("type3", "3");
kv.put("type4", "4");
msg.setExtraKeyVal(kv);
msg.setIsSyncSender(1);
ResponseResult result = expansion.set(msg);
System.out.println("set expansion: " + result.toString());
/**
* Message module two-person message instance
*/
require "./../../RongCloud.php";
define("APPKEY", '');
define('APPSECRET', '');
use RongCloud\RongCloud;
use RongCloud\Lib\Utils;
/**
* Two-person message sending
*/
function set()
{
// Connect to the Singapore Data Center
// RongCloud::$apiUrl = ['http://api.sg-light-api.com/'];
$RongSDK = new RongCloud(APPKEY, APPSECRET);
$message = [
'msgUID' => 'BS45-NPH4-HV87-10LM', // Message unique identifier, the server can obtain it through the full message routing function.
'userId' => 'WNYZbMqpH', // Need to set the extended message delivery user Id.
'targetId' => 'tjw3zbMrU', // Target ID, depending on the conversationType, could be a user ID or a group ID.
'conversationType' => '1', // Conversation type, one-on-one chat is 1, group chat is 3, only supports single chat and group chat types.
'extraKeyVal' => ['type1' => '1', 'type2' => '2', 'type3' => '3', 'type4' => '4',],// Custom message extension content, JSON structure, set in Key, Value format
'isSyncSender' => 0 // Whether the sender accepts the terminal user's online status, 0 indicates not accepting, 1 indicates accepting, default is 0 not accepting
];
$res = $RongSDK->getMessage()->Expansion()->set($message);
Utils::dump("Two-person message sending", $res);
}
set();
func TestRongCloud_MessageExpansionSet(t *testing.T) {
data, err := json.Marshal(map[string]string{"type": "3"})
if err != nil {
t.Log("marshal err", err)
return
}
rc := NewRongCloud(
os.Getenv("APP_KEY"),
os.Getenv("APP_SECRET"),
REGION_BJ,
)
if err := rc.MessageExpansionSet("C16R-VBGG-1IE5-SD0C",
"u01",
"3",
"testExp0309",
string(data),
1,
); err != nil {
t.Error(err)
return
}
t.Log("do UGMessageGet suc")
}
7. 发送方更新红包消息扩展
红包消息发送方可全局设置消息扩展,收到消息扩展更新回调时做对应的处理。建议调用客户端服务 API 获取最新的红包领取信息。
- Android
- iOS
- Web
// 接收方 Android 端示例代码
RongIMClient.getInstance().setMessageExpansionListener(new RongIMClient.MessageExpansionListener() {
@Override
public void onMessageExpansionUpdate(Map<String, String> expansion, Message message) {
if (message.getContent() instanceof CustomRedPacketMessage){
IMCenter.getInstance().refreshMessage(message); // 刷新原消息
// 其他自定义处理
}
}
});
// 接收方 iOS 端示例代码:
[RCCoreClient sharedCoreClient].messageExpansionDelegate = self;
// 消息扩展信息更改的回调
- (void)messageExpansionDidUpdate:(NSDictionary<NSString *,NSString *> *)expansionDic message:(RCMessage *)message {
for (int i = 0; i < self.conversationDataRepository.count; i++) {
RCMessageModel *model = self.conversationDataRepository[i];
if ([model.messageUId isEqualToString:message.messageUId]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.conversationMessageCollectionView reloadItemsAtIndexPaths:@[indexPath]];
});
break;
}
}
}
// Web 接收端收到消息扩展通知:
RongIMLib.addEventListener(RongIMLib.Events.EXPANSION, ({ updatedExpansion, deletedExpansion }) => {
console.log('拓展信息更新:', updatedExpansion);
console.log('拓展信息删除:', deletedExpansion);
})
8. 红包领取后操作
开启红包后,如需展示" XX 领取了红包"。可由服务端发送一条群定向消息或者红包发送者和领取者,或者监听消息扩展信息,展示对应的用户信息及领取状态。红包发送者可以监听消息扩展信息,收到监听后,主动到业务客户端获取消息领取的最新状态。
- Android
- iOS
- Web
// 插入小灰条消息
InformationNotificationMessage informationNotificationMessage = InformationNotificationMessage.obtain("你领取了xxx 发送的红包");
ConversationType conversationType = ConversationType.PRIVATE;
String targetId = "user1";
String senderUserId = "模拟发送方的 ID";
ReceivedStatus receivedStatus = new ReceivedStatus(0x1);
String sentTime = System.currentTimeMillis();
IMCenter.getInstance().insertIncomingMessage(conversationType, targetId, senderUserId, receivedStatus, informationNotificationMessage, sentTime, new RongIMClient.ResultCallback<Message>() {
/**
* 成功回调
* @param message 插入的消息
*/
@Override
public void onSuccess(Message message) {
}
/**
* 失败回调
* @param errorCode 错误码
*/
@Override
public void onError(RongIMClient.ErrorCode errorCode) {
}
});
RCInformationNotificationMessage *warningMsg =
[RCInformationNotificationMessage
notificationWithMessage:@"你领取了xxx 发送的红包" extra:nil];
RCMessage *savedMsg = [[RCCoreClient sharedCoreClient]
insertOutgoingMessage:self.conversationType targetId:self.targetId
sentStatus:SentStatus_SENT content:warningMsg];
// 使用 IMKit 的话,在聊天页面调用这个方法在 UI 上插入消息
[self appendAndDisplayMessage:savedMsg];
// Web 消息监听收到消息,按需 UI 展示即可
const Events = RongIMLib.Events
const listener = (evt) => {
console.log(evt.messages)
};
RongIMLib.addEventListener(Events.MESSAGES, listener)