基于“东莞梦幻网络科技”体育赛事直播系统中的聊一聊功能模块的群聊和发红包关键代码实现方案,所用技术栈(后端:PHP-ThinkPHP、安卓-Java、iOS-OC、PC/H5-Vue.js)。
数据库表设计(MySQL)
-- 群组表
CREATE TABLE `groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`creator_id` int(11) NOT NULL,
`avatar` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
PRIMARY KEY (`id`)
);
-- 群成员表
CREATE TABLE `group_members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`role` tinyint(1) DEFAULT '0' COMMENT '0-普通成员 1-管理员 2-群主',
`joined_at` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `group_user` (`group_id`,`user_id`)
);
-- 消息表
CREATE TABLE `messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`sender_id` int(11) NOT NULL,
`content` text NOT NULL,
`type` tinyint(1) DEFAULT '0' COMMENT '0-文本 1-图片 2-红包 3-系统通知',
`created_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `group_id` (`group_id`)
);
-- 红包表
CREATE TABLE `red_packets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`sender_id` int(11) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`count` int(11) NOT NULL,
`remain_amount` decimal(10,2) NOT NULL,
`remain_count` int(11) NOT NULL,
`message` varchar(255) DEFAULT NULL,
`created_at` datetime NOT NULL,
`expire_at` datetime NOT NULL,
`status` tinyint(1) DEFAULT '0' COMMENT '0-未领完 1-已领完 2-已过期',
PRIMARY KEY (`id`)
);
-- 红包领取记录
CREATE TABLE `red_packet_records` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`red_packet_id` int(11) NOT NULL,
`receiver_id` int(11) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`received_at` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `red_packet_id` (`red_packet_id`)
);
后端实现 (ThinkPHP)
群聊API控制器
<?php
namespace app\api\controller;
use think\Controller;
use think\Request;
class Group extends Controller
{
// 创建群组
public function create()
{
$user_id = Request::instance()->param('user_id');
$name = Request::instance()->param('name');
$group_id = Db::name('groups')->insertGetId([
'name' => $name,
'creator_id' => $user_id,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
// 添加创建者为群主
Db::name('group_members')->insert([
'group_id' => $group_id,
'user_id' => $user_id,
'role' => 2, // 群主
'joined_at' => date('Y-m-d H:i:s')
]);
return json(['code' => 0, 'data' => ['group_id' => $group_id]]);
}
// 发送群消息
public function sendMessage()
{
$group_id = Request::instance()->param('group_id');
$user_id = Request::instance()->param('user_id');
$content = Request::instance()->param('content');
// 检查是否是群成员
$is_member = Db::name('group_members')
->where('group_id', $group_id)
->where('user_id', $user_id)
->count();
if(!$is_member) {
return json(['code' => 1, 'msg' => '您不是该群成员']);
}
// 保存消息到数据库
$message_id = Db::name('messages')->insertGetId([
'group_id' => $group_id,
'sender_id' => $user_id,
'content' => $content,
'type' => 0,
'created_at' => date('Y-m-d H:i:s')
]);
// 通过WebSocket推送消息
// 实际实现中应该调用WebSocket服务推送
return json(['code' => 0, 'data' => ['message_id' => $message_id]]);
}
// 发送红包
public function sendRedPacket()
{
$group_id = Request::instance()->param('group_id');
$user_id = Request::instance()->param('user_id');
$amount = Request::instance()->param('amount');
$count = Request::instance()->param('count');
$message = Request::instance()->param('message', '');
// 检查余额等逻辑
// 创建红包
$red_packet_id = Db::name('red_packets')->insertGetId([
'group_id' => $group_id,
'sender_id' => $user_id,
'amount' => $amount,
'count' => $count,
'remain_amount' => $amount,
'remain_count' => $count,
'message' => $message,
'created_at' => date('Y-m-d H:i:s'),
'expire_at' => date('Y-m-d H:i:s', time() + 86400), // 24小时后过期
'status' => 0
]);
// 保存消息
$message_id = Db::name('messages')->insertGetId([
'group_id' => $group_id,
'sender_id' => $user_id,
'content' => json_encode(['red_packet_id' => $red_packet_id]),
'type' => 2, // 红包消息
'created_at' => date('Y-m-d H:i:s')
]);
return json(['code' => 0, 'data' => ['red_packet_id' => $red_packet_id]]);
}
// 领取红包
public function receiveRedPacket()
{
$red_packet_id = Request::instance()->param('red_packet_id');
$user_id = Request::instance()->param('user_id');
// 检查红包状态
$red_packet = Db::name('red_packets')->find($red_packet_id);
if(!$red_packet || $red_packet['status'] != 0) {
return json(['code' => 1, 'msg' => '红包已领取完或已过期']);
}
// 检查是否已领取
$has_received = Db::name('red_packet_records')
->where('red_packet_id', $red_packet_id)
->where('receiver_id', $user_id)
->count();
if($has_received) {
return json(['code' => 2, 'msg' => '您已领取过该红包']);
}
// 计算领取金额
$amount = $this->calculateRedPacketAmount($red_packet);
// 更新红包状态
Db::name('red_packets')
->where('id', $red_packet_id)
->update([
'remain_amount' => Db::raw('remain_amount-' . $amount),
'remain_count' => Db::raw('remain_count-1'),
'status' => $red_packet['remain_count'] == 1 ? 1 : 0
]);
// 添加领取记录
Db::name('red_packet_records')->insert([
'red_packet_id' => $red_packet_id,
'receiver_id' => $user_id,
'amount' => $amount,
'received_at' => date('Y-m-d H:i:s')
]);
// 更新用户余额等逻辑
return json(['code' => 0, 'data' => ['amount' => $amount]]);
}
private function calculateRedPacketAmount($red_packet)
{
// 简单实现:平均分配
if($red_packet['remain_count'] == 1) {
return $red_packet['remain_amount'];
}
$avg = $red_packet['remain_amount'] / $red_packet['remain_count'];
$min = 0.01;
$max = $avg * 2;
$amount = mt_rand($min * 100, $max * 100) / 100;
$amount = min($amount, $red_packet['remain_amount'] - ($red_packet['remain_count'] - 1) * 0.01);
return round($amount, 2);
}
}
前端实现 (Vue.js)
群聊主界面组件
<template>
<div class="group-chat-container">
<!-- 群聊头部 -->
<div class="chat-header">
<h2>{{ groupName }}</h2>
<div class="header-actions">
<button @click="showGroupInfo">群信息</button>
<button @click="showRedPacketDialog">发红包</button>
</div>
</div>
<!-- 消息列表 -->
<div class="message-list" ref="messageList">
<div v-for="(message, index) in messages" :key="index"
:class="['message-item', message.sender_id === currentUserId ? 'self' : 'other']">
<div v-if="message.type === 0" class="text-message">
<div class="sender-name" v-if="message.sender_id !== currentUserId">
{{ getMemberName(message.sender_id) }}
</div>
<div class="message-content">{{ message.content }}</div>
<div class="message-time">{{ formatTime(message.created_at) }}</div>
</div>
<div v-else-if="message.type === 2" class="red-packet-message">
<div class="sender-name">{{ getMemberName(message.sender_id) }}的红包</div>
<div class="red-packet-content" @click="openRedPacket(message)">
<div class="red-packet-icon">🧧</div>
<div class="red-packet-message-text">{{ getRedPacketMessage(message) }}</div>
</div>
<div class="message-time">{{ formatTime(message.created_at) }}</div>
</div>
</div>
</div>
<!-- 输入框 -->
<div class="message-input">
<input v-model="inputMessage" @keyup.enter="sendMessage" placeholder="输入消息...">
<button @click="sendMessage">发送</button>
</div>
<!-- 红包对话框 -->
<div v-if="showRedPacket" class="red-packet-dialog">
<div class="red-packet-box">
<h3>发红包</h3>
<div class="red-packet-form">
<div class="form-item">
<label>金额</label>
<input type="number" v-model="redPacketAmount" placeholder="输入金额">
</div>
<div class="form-item">
<label>个数</label>
<input type="number" v-model="redPacketCount" placeholder="输入红包个数">
</div>
<div class="form-item">
<label>留言</label>
<input v-model="redPacketMessage" placeholder="恭喜发财">
</div>
<div class="form-actions">
<button @click="sendRedPacket">塞钱进红包</button>
<button @click="showRedPacket = false">取消</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
groupId: null,
groupName: '群聊名称',
currentUserId: 1, // 当前用户ID
messages: [],
inputMessage: '',
showRedPacket: false,
redPacketAmount: '',
redPacketCount: 1,
redPacketMessage: '恭喜发财',
members: [],
ws: null
}
},
mounted() {
this.groupId = this.$route.params.groupId;
this.loadGroupInfo();
this.loadMessages();
this.connectWebSocket();
},
methods: {
async loadGroupInfo() {
try {
const response = await this.$http.get(`/api/group/info/${this.groupId}`);
this.groupName = response.data.name;
this.members = response.data.members;
} catch (error) {
console.error('获取群信息失败:', error);
}
},
async loadMessages() {
try {
const response = await this.$http.get(`/api/group/messages/${this.groupId}`);
this.messages = response.data;
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error('获取消息失败:', error);
}
},
connectWebSocket() {
this.ws = new WebSocket(`ws://your-websocket-server:2346?user_id=${this.currentUserId}`);
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if(message.group_id === this.groupId) {
this.messages.push(message);
this.$nextTick(() => {
this.scrollToBottom();
});
}
};
this.ws.onclose = () => {
console.log('WebSocket连接关闭');
};
},
sendMessage() {
if(!this.inputMessage.trim()) return;
const message = {
type: 'group_message',
group_id: this.groupId,
sender_id: this.currentUserId,
content: this.inputMessage
};
this.ws.send(JSON.stringify(message));
// 添加到本地消息列表
this.messages.push({
group_id: this.groupId,
sender_id: this.currentUserId,
content: this.inputMessage,
type: 0,
created_at: new Date().toISOString()
});
this.inputMessage = '';
this.$nextTick(() => {
this.scrollToBottom();
});
},
scrollToBottom() {
const container = this.$refs.messageList;
container.scrollTop = container.scrollHeight;
},
showRedPacketDialog() {
this.showRedPacket = true;
},
async sendRedPacket() {
try {
const response = await this.$http.post('/api/group/sendRedPacket', {
group_id: this.groupId,
user_id: this.currentUserId,
amount: this.redPacketAmount,
count: this.redPacketCount,
message: this.redPacketMessage
});
// 添加到消息列表
this.messages.push({
group_id: this.groupId,
sender_id: this.currentUserId,
content: JSON.stringify({red_packet_id: response.data.red_packet_id}),
type: 2,
created_at: new Date().toISOString()
});
this.showRedPacket = false;
this.redPacketAmount = '';
this.redPacketCount = 1;
this.redPacketMessage = '恭喜发财';
this.$nextTick(() => {
this.scrollToBottom();
});
} catch (error) {
console.error('发送红包失败:', error);
}
},
async openRedPacket(message) {
try {
const content = JSON.parse(message.content);
const response = await this.$http.post('/api/group/receiveRedPacket', {
red_packet_id: content.red_packet_id,
user_id: this.currentUserId
});
alert(`恭喜您领取了${response.data.amount}元红包`);
} catch (error) {
console.error('领取红包失败:', error);
alert(error.response?.data?.msg || '领取红包失败');
}
},
getMemberName(userId) {
const member = this.members.find(m => m.user_id === userId);
return member ? member.name : '未知用户';
},
getRedPacketMessage(message) {
try {
const content = JSON.parse(message.content);
return content.message || '恭喜发财';
} catch {
return '恭喜发财';
}
},
formatTime(time) {
return new Date(time).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
},
showGroupInfo() {
// 显示群信息
}
},
beforeDestroy() {
if(this.ws) {
this.ws.close();
}
}
}
</script>
<style scoped>
.group-chat-container {
display: flex;
flex-direction: column;
height: 100vh;
}
.chat-header {
padding: 10px;
background: #f5f5f5;
display: flex;
justify-content: space-between;
align-items: center;
}
.message-list {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.message-item {
margin-bottom: 15px;
}
.message-item.self {
text-align: right;
}
.message-item.other {
text-align: left;
}
.sender-name {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.message-content {
display: inline-block;
padding: 8px 12px;
background: #e6f7ff;
border-radius: 4px;
max-width: 70%;
word-break: break-word;
}
.message-item.self .message-content {
background: #69c0ff;
color: white;
}
.message-time {
font-size: 10px;
color: #999;
margin-top: 3px;
}
.message-input {
padding: 10px;
display: flex;
border-top: 1px solid #eee;
}
.message-input input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
margin-right: 10px;
}
.red-packet-message {
cursor: pointer;
}
.red-packet-content {
display: inline-flex;
align-items: center;
padding: 8px 12px;
background: #ff4d4f;
color: white;
border-radius: 4px;
}
.red-packet-icon {
font-size: 20px;
margin-right: 8px;
}
.red-packet-dialog {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.red-packet-box {
background: white;
padding: 20px;
border-radius: 8px;
width: 300px;
}
.red-packet-form .form-item {
margin-bottom: 15px;
}
.red-packet-form label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.red-packet-form input {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-actions {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.form-actions button {
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.form-actions button:first-child {
background: #ff4d4f;
color: white;
}
.form-actions button:last-child {
background: #f5f5f5;
}
</style>
Android实现 (Java)
public class GroupChatActivity extends AppCompatActivity implements ChatWebSocketClient.ChatMessageListener {
private static final String WS_URL = "ws://your-websocket-server:2346";
private RecyclerView messageRecyclerView;
private EditText messageInput;
private Button sendButton;
private ChatWebSocketClient webSocketClient;
private MessageAdapter messageAdapter;
private List<ChatMessage> messages = new ArrayList<>();
private int currentUserId = 1; // 当前用户ID
private int groupId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_group_chat);
groupId = getIntent().getIntExtra("group_id", 0);
messageRecyclerView = findViewById(R.id.message_recycler_view);
messageInput = findViewById(R.id.message_input);
sendButton = findViewById(R.id.send_button);
// 设置RecyclerView
messageAdapter = new MessageAdapter(messages, currentUserId);
messageRecyclerView.setLayoutManager(new LinearLayoutManager(this));
messageRecyclerView.setAdapter(messageAdapter);
// 连接WebSocket
try {
webSocketClient = new ChatWebSocketClient(new URI(WS_URL + "?user_id=" + currentUserId), this);
webSocketClient.connect();
} catch (URISyntaxException e) {
e.printStackTrace();
}
// 发送消息
sendButton.setOnClickListener(v -> sendMessage());
// 加载历史消息
loadMessages();
}
private void sendMessage() {
String content = messageInput.getText().toString().trim();
if (content.isEmpty()) return;
try {
JSONObject message = new JSONObject();
message.put("type", "group_message");
message.put("group_id", groupId);
message.put("sender_id", currentUserId);
message.put("content", content);
if (webSocketClient != null && webSocketClient.isOpen()) {
webSocketClient.send(message.toString());
// 添加到本地列表
ChatMessage chatMessage = new ChatMessage();
chatMessage.setGroupId(groupId);
chatMessage.setSenderId(currentUserId);
chatMessage.setContent(content);
chatMessage.setType(0); // 文本消息
chatMessage.setTime(new SimpleDateFormat("HH:mm", Locale.getDefault()).format(new Date()));
messages.add(chatMessage);
messageAdapter.notifyItemInserted(messages.size() - 1);
messageRecyclerView.scrollToPosition(messages.size() - 1);
messageInput.setText("");
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void loadMessages() {
// 从API加载历史消息
// 实现网络请求获取消息列表
}
@Override
public void onGroupMessageReceived(int groupId, int senderId, String content, String time) {
if (this.groupId == groupId) {
runOnUiThread(() -> {
ChatMessage message = new ChatMessage();
message.setGroupId(groupId);
message.setSenderId(senderId);
message.setContent(content);
message.setType(0); // 文本消息
message.setTime(time);
messages.add(message);
messageAdapter.notifyItemInserted(messages.size() - 1);
messageRecyclerView.scrollToPosition(messages.size() - 1);
});
}
}
@Override
public void onRedPacketReceived(int groupId, int senderId, int redPacketId, String message) {
// 处理红包消息
}
@Override
protected void onDestroy() {
super.onDestroy();
if (webSocketClient != null) {
webSocketClient.close();
}
}
}
class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MessageViewHolder> {
private List<ChatMessage> messages;
private int currentUserId;
public MessageAdapter(List<ChatMessage> messages, int currentUserId) {
this.messages = messages;
this.currentUserId = currentUserId;
}
@Override
public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_message, parent, false);
return new MessageViewHolder(view);
}
@Override
public void onBindViewHolder(MessageViewHolder holder, int position) {
ChatMessage message = messages.get(position);
if (message.getSenderId() == currentUserId) {
// 自己的消息
holder.messageLayout.setGravity(Gravity.END);
holder.messageContent.setBackgroundResource(R.drawable.bg_message_self);
} else {
// 别人的消息
holder.messageLayout.setGravity(Gravity.START);
holder.messageContent.setBackgroundResource(R.drawable.bg_message_other);
holder.senderName.setText("用户" + message.getSenderId());
holder.senderName.setVisibility(View.VISIBLE);
}
holder.messageContent.setText(message.getContent());
holder.messageTime.setText(message.getTime());
}
@Override
public int getItemCount() {
return messages.size();
}
class MessageViewHolder extends RecyclerView.ViewHolder {
LinearLayout messageLayout;
TextView senderName;
TextView messageContent;
TextView messageTime;
public MessageViewHolder(View itemView) {
super(itemView);
messageLayout = itemView.findViewById(R.id.message_layout);
senderName = itemView.findViewById(R.id.sender_name);
messageContent = itemView.findViewById(R.id.message_content);
messageTime = itemView.findViewById(R.id.message_time);
}
}
}
iOS实现 (Objective-C)
#import "ChatWebSocketClient.h"
#import "SRWebSocket.h"
@interface ChatWebSocketClient () <SRWebSocketDelegate>
@property (nonatomic, strong) SRWebSocket *webSocket;
@property (nonatomic, weak) id<ChatMessageDelegate> delegate;
@end
@implementation ChatWebSocketClient
- (instancetype)initWithURL:(NSURL *)url delegate:(id<ChatMessageDelegate>)delegate {
self = [super init];
if (self) {
_delegate = delegate;
_webSocket = [[SRWebSocket alloc] initWithURL:url];
_webSocket.delegate = self;
[_webSocket open];
}
return self;
}
- (void)sendMessage:(NSDictionary *)message {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:message options:0 error:&error];
if (!error) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[_webSocket send:jsonString];
}
}
- (void)close {
[_webSocket close];
}
#pragma mark - SRWebSocketDelegate
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
NSLog(@"WebSocket连接已打开");
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSLog(@"收到消息: %@", message);
if ([message isKindOfClass:[NSString class]]) {
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
if (!error) {
NSString *type = json[@"type"];
if ([type isEqualToString:@"group_message"]) {
NSInteger groupId = [json[@"group_id"] integerValue];
NSInteger senderId = [json[@"sender_id"] integerValue];
NSString *content = json[@"content"];
NSString *time = json[@"time"];
if ([self.delegate respondsToSelector:@selector(didReceiveGroupMessage:groupId:senderId:content:time:)]) {
[self.delegate didReceiveGroupMessage:self groupId:groupId senderId:senderId content:content time:time];
}
} else if ([type isEqualToString:@"red_packet"]) {
// 处理红包消息
}
}
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
NSLog(@"WebSocket错误: %@", error);
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
NSLog(@"WebSocket连接已关闭: %@", reason);
}
@end
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。