Merge remote master: 合并封禁系统、粉丝团消息等功能

This commit is contained in:
xiao12feng8 2026-01-05 17:27:05 +08:00
commit 1995a37f2f
53 changed files with 4139 additions and 272 deletions

View File

@ -13,6 +13,14 @@ export function fanGroupListApi(params) {
}) })
} }
// 粉丝团详情
export function fanGroupDetailApi(id) {
return request({
url: `/admin/fan/group/detail/${id}`,
method: 'get'
})
}
// 删除粉丝团 // 删除粉丝团
export function fanGroupDeleteApi(id) { export function fanGroupDeleteApi(id) {
return request({ return request({
@ -30,6 +38,24 @@ export function fanGroupBatchDeleteApi(ids) {
}) })
} }
// 修改粉丝团状态
export function fanGroupStatusApi(id, status) {
return request({
url: `/admin/fan/group/status/${id}`,
method: 'post',
data: { status }
})
}
// 修改粉丝团信息
export function fanGroupUpdateApi(id, data) {
return request({
url: `/admin/fan/group/update/${id}`,
method: 'post',
data
})
}
// 粉丝团成员列表 // 粉丝团成员列表
export function fanGroupMemberListApi(params) { export function fanGroupMemberListApi(params) {
return request({ return request({
@ -47,6 +73,15 @@ export function fanGroupMemberDeleteApi(id) {
}) })
} }
// 修改成员等级
export function fanGroupMemberUpdateLevelApi(id, level) {
return request({
url: `/admin/fan/group/member/update-level/${id}`,
method: 'post',
data: { level }
})
}
// 粉丝团聊天记录列表 // 粉丝团聊天记录列表
export function fanGroupMessageListApi(params) { export function fanGroupMessageListApi(params) {
return request({ return request({
@ -63,3 +98,11 @@ export function fanGroupMessageDeleteApi(id) {
method: 'post' method: 'post'
}) })
} }
// 粉丝团统计
export function fanGroupStatisticsApi() {
return request({
url: '/admin/fan/group/statistics',
method: 'get'
})
}

View File

@ -60,7 +60,7 @@ service.interceptors.response.use(
type: 'error', type: 'error',
duration: 5 * 1000, duration: 5 * 1000,
}); });
return Promise.reject(); return Promise.reject(res || { message: 'Error' });
} else { } else {
return res.data; return res.data;
} }

View File

@ -55,11 +55,14 @@
<span>{{ scope.row.message_count || 0 }} </span> <span>{{ scope.row.message_count || 0 }} </span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="250" align="center" fixed="right"> <el-table-column label="操作" width="320" align="center" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<div style="display: flex; gap: 8px; justify-content: center;"> <div style="display: flex; gap: 6px; justify-content: center; flex-wrap: wrap;">
<el-button type="primary" size="mini" @click="handleViewMembers(scope.row)">查看成员</el-button> <el-button type="primary" size="mini" @click="handleViewMembers(scope.row)">成员</el-button>
<el-button type="success" size="mini" @click="handleViewMessages(scope.row)">聊天记录</el-button> <el-button type="success" size="mini" @click="handleViewMessages(scope.row)">聊天</el-button>
<el-button :type="scope.row.status === 1 ? 'warning' : 'info'" size="mini" @click="handleToggleStatus(scope.row)">
{{ scope.row.status === 1 ? '解散' : '恢复' }}
</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</div> </div>
</template> </template>
@ -82,75 +85,117 @@
</el-card> </el-card>
<!-- 成员列表弹窗 --> <!-- 成员列表弹窗 -->
<el-dialog :title="'粉丝团成员 - ' + currentGroupName" :visible.sync="memberDialogVisible" width="800px"> <el-dialog :title="'粉丝团成员 - ' + currentGroupName" :visible.sync="memberDialogVisible" width="1100px" top="5vh">
<el-table :data="memberList" v-loading="memberLoading" border size="small" max-height="400"> <div class="dialog-stats mb15">
<el-table-column prop="id" label="ID" width="80" align="center" /> <el-tag type="info"> {{ memberTotal }} 位成员</el-tag>
<el-table-column prop="uid" label="用户ID" width="100" align="center" /> </div>
<el-table-column prop="nickname" label="用户昵称" width="120" align="center" /> <el-table :data="memberList" v-loading="memberLoading" border size="small" max-height="500" stripe>
<el-table-column prop="level" label="粉丝等级" width="100" align="center"> <el-table-column prop="id" label="ID" width="60" align="center" />
<el-table-column label="头像" width="60" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag type="warning" size="small">Lv.{{ scope.row.level }}</el-tag> <el-avatar :size="36" :src="scope.row.avatar" icon="el-icon-user"></el-avatar>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="intimacy" label="亲密度" width="100" align="center" /> <el-table-column prop="uid" label="用户ID" width="80" align="center" />
<el-table-column label="状态" width="100" align="center"> <el-table-column prop="nickname" label="用户昵称" min-width="120" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.nickname || scope.row.user_nickname || '未知用户' }}
</template>
</el-table-column>
<el-table-column prop="level" label="粉丝等级" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="getLevelTagType(scope.row.level)" size="small" effect="dark">
Lv.{{ scope.row.level }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="intimacy" label="亲密度" width="100" align="center">
<template slot-scope="scope">
<span style="color: #E6A23C; font-weight: bold;">{{ scope.row.intimacy || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="small"> <el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="small">
{{ scope.row.status === 1 ? '正常' : '已退出' }} {{ scope.row.status === 1 ? '正常' : '已退出' }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="join_time" label="加入时间" width="160" align="center" /> <el-table-column prop="join_time" label="加入时间" width="160" align="center">
<el-table-column label="操作" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="danger" size="mini" @click="handleDeleteMember(scope.row)">移除</el-button> {{ formatTime(scope.row.join_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right">
<template slot-scope="scope">
<div class="btn-group">
<el-button type="warning" size="mini" icon="el-icon-edit" @click="handleUpdateMemberLevel(scope.row)">等级</el-button>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="handleDeleteMember(scope.row)">移除</el-button>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="mt20" style="text-align: right;"> <div class="mt20" style="text-align: right;">
<el-pagination <el-pagination
small background
@current-change="handleMemberPageChange" @current-change="handleMemberPageChange"
:current-page="memberPage" :current-page="memberPage"
:page-size="10" :page-size="10"
layout="prev, pager, next" layout="total, prev, pager, next"
:total="memberTotal" :total="memberTotal"
></el-pagination> ></el-pagination>
</div> </div>
</el-dialog> </el-dialog>
<!-- 聊天记录弹窗 --> <!-- 聊天记录弹窗 -->
<el-dialog :title="'聊天记录 - ' + currentGroupName" :visible.sync="messageDialogVisible" width="900px"> <el-dialog :title="'聊天记录 - ' + currentGroupName" :visible.sync="messageDialogVisible" width="1100px" top="5vh">
<el-table :data="messageList" v-loading="messageLoading" border size="small" max-height="500"> <div class="dialog-stats mb15">
<el-table-column prop="id" label="ID" width="80" align="center" /> <el-tag type="info"> {{ messageTotal }} 条消息</el-tag>
<el-table-column prop="sender_id" label="发送者ID" width="100" align="center" /> <el-button type="text" icon="el-icon-refresh" @click="getMessages" style="margin-left: 10px;">刷新</el-button>
<el-table-column prop="senderName" label="发送者昵称" width="120" align="center" /> </div>
<el-table-column prop="content" label="消息内容" min-width="200" align="left"> <el-table :data="messageList" v-loading="messageLoading" border size="small" max-height="520" stripe>
<el-table-column prop="id" label="ID" width="60" align="center" />
<el-table-column label="发送者" width="200" align="left">
<template slot-scope="scope"> <template slot-scope="scope">
<div style="max-height: 60px; overflow: hidden; text-overflow: ellipsis;">{{ scope.row.content }}</div> <div style="display: flex; align-items: center; gap: 10px;">
<el-avatar :size="32" :src="scope.row.senderAvatar" icon="el-icon-user"></el-avatar>
<div>
<div style="font-weight: 500;">{{ scope.row.senderName || '未知用户' }}</div>
<div style="font-size: 12px; color: #909399;">ID: {{ scope.row.sender_id }}</div>
</div>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="message_type" label="消息类型" width="100" align="center"> <el-table-column prop="content" label="消息内容" min-width="300" align="left">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag size="small" :type="scope.row.message_type === 'text' ? '' : 'warning'"> <div class="message-content">{{ scope.row.content }}</div>
{{ scope.row.message_type === 'text' ? '文本' : scope.row.message_type }} </template>
</el-table-column>
<el-table-column prop="message_type" label="类型" width="80" align="center">
<template slot-scope="scope">
<el-tag size="small" :type="getMessageTypeTag(scope.row.message_type)">
{{ getMessageTypeText(scope.row.message_type) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="create_time" label="发送时间" width="170" align="center" /> <el-table-column prop="create_time" label="发送时间" width="160" align="center">
<el-table-column label="操作" width="80" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="danger" size="mini" @click="handleDeleteMessage(scope.row)">删除</el-button> {{ formatTime(scope.row.create_time) }}
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="danger" size="mini" icon="el-icon-delete" @click="handleDeleteMessage(scope.row)" circle></el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="mt20" style="text-align: right;"> <div class="mt20" style="text-align: right;">
<el-pagination <el-pagination
small background
@current-change="handleMessagePageChange" @current-change="handleMessagePageChange"
:current-page="messagePage" :current-page="messagePage"
:page-size="20" :page-size="20"
layout="prev, pager, next" layout="total, prev, pager, next"
:total="messageTotal" :total="messageTotal"
></el-pagination> ></el-pagination>
</div> </div>
@ -159,7 +204,7 @@
</template> </template>
<script> <script>
import { fanGroupListApi, fanGroupDeleteApi, fanGroupBatchDeleteApi, fanGroupMemberListApi, fanGroupMemberDeleteApi, fanGroupMessageListApi, fanGroupMessageDeleteApi } from '@/api/fanGroup'; import { fanGroupListApi, fanGroupDeleteApi, fanGroupBatchDeleteApi, fanGroupMemberListApi, fanGroupMemberDeleteApi, fanGroupMessageListApi, fanGroupMessageDeleteApi, fanGroupStatusApi, fanGroupMemberUpdateLevelApi } from '@/api/fanGroup';
export default { export default {
name: 'FanGroupList', name: 'FanGroupList',
@ -290,7 +335,7 @@ export default {
}, },
// //
async handleViewMessages(row) { async handleViewMessages(row) {
this.currentGroupId = row.group_id || row.id; // 使ID this.currentGroupId = row.id; // 使ID
this.currentGroupName = row.name; this.currentGroupName = row.name;
this.messagePage = 1; this.messagePage = 1;
this.messageDialogVisible = true; this.messageDialogVisible = true;
@ -322,6 +367,60 @@ export default {
this.$message.error(error.message || '删除失败'); this.$message.error(error.message || '删除失败');
} }
}).catch(() => {}); }).catch(() => {});
},
// /
async handleToggleStatus(row) {
const newStatus = row.status === 1 ? 0 : 1;
const action = newStatus === 0 ? '解散' : '恢复';
this.$confirm(`确定要${action}该粉丝团吗?`, '提示', { type: 'warning' }).then(async () => {
try {
await fanGroupStatusApi(row.id, newStatus);
this.$message.success(`${action}成功`);
this.getList();
} catch (error) {
this.$message.error(error.message || `${action}失败`);
}
}).catch(() => {});
},
//
async handleUpdateMemberLevel(row) {
this.$prompt('请输入新等级1-10', '修改等级', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^([1-9]|10)$/,
inputErrorMessage: '等级必须是1-10之间的数字',
inputValue: String(row.level)
}).then(async ({ value }) => {
try {
await fanGroupMemberUpdateLevelApi(row.id, parseInt(value));
this.$message.success('修改成功');
this.getMembers();
} catch (error) {
this.$message.error(error.message || '修改失败');
}
}).catch(() => {});
},
//
getLevelTagType(level) {
if (level >= 8) return 'danger';
if (level >= 5) return 'warning';
if (level >= 3) return 'success';
return 'info';
},
//
getMessageTypeTag(type) {
const map = { text: '', image: 'success', gift: 'warning', system: 'info' };
return map[type] || '';
},
//
getMessageTypeText(type) {
const map = { text: '文本', image: '图片', gift: '礼物', system: '系统' };
return map[type] || type;
},
//
formatTime(time) {
if (!time) return '-';
return time.replace('T', ' ').substring(0, 19);
} }
} }
}; };
@ -330,4 +429,42 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.mb20 { margin-bottom: 20px; } .mb20 { margin-bottom: 20px; }
.mt20 { margin-top: 20px; } .mt20 { margin-top: 20px; }
.mb15 { margin-bottom: 15px; }
.dialog-stats {
display: flex;
align-items: center;
padding: 10px 15px;
background: #f5f7fa;
border-radius: 4px;
}
.message-content {
max-height: 80px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
line-height: 1.5;
word-break: break-all;
}
.btn-group {
display: flex;
justify-content: center;
gap: 6px;
}
::v-deep .el-dialog__body {
padding: 15px 20px 20px;
}
::v-deep .el-table .cell {
padding: 8px 10px;
}
::v-deep .el-avatar {
border: 1px solid #eee;
}
</style> </style>

View File

@ -93,12 +93,23 @@ export default {
this.loading = true; this.loading = true;
customerServiceGroupListApi(this.searchForm) customerServiceGroupListApi(this.searchForm)
.then((res) => { .then((res) => {
this.tableData = res.data.list || []; //
this.total = res.data.total || 0; if (res && res.list) {
this.tableData = res.list || [];
this.total = res.total || 0;
} else if (Array.isArray(res)) {
this.tableData = res;
this.total = res.length;
} else {
this.tableData = [];
this.total = 0;
}
this.loading = false; this.loading = false;
}) })
.catch((err) => { .catch((err) => {
console.error('获取客服联系方式列表失败:', err); console.error('获取客服联系方式列表失败:', err);
this.tableData = [];
this.total = 0;
this.loading = false; this.loading = false;
this.$message.warning('获取数据失败,请检查后端接口'); this.$message.warning('获取数据失败,请检查后端接口');
}); });

View File

@ -48,21 +48,26 @@
<el-table-column label="发布者" width="150"> <el-table-column label="发布者" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<div class="user-info"> <div class="user-info">
<el-avatar :src="scope.row.user_avatar" :size="32" /> <el-avatar :src="scope.row.user_avatar || scope.row.avatar" :size="32">
<span class="user-name">{{ scope.row.user_name }}</span> <span>{{ scope.row.nickname ? scope.row.nickname.substr(0, 1) : '用' }}</span>
</el-avatar>
<span class="user-name">{{ scope.row.nickname }}</span>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="content" label="动态内容" min-width="200" show-overflow-tooltip /> <el-table-column prop="content" label="动态内容" min-width="200" show-overflow-tooltip />
<el-table-column label="图片/视频" width="120" align="center"> <el-table-column label="图片/视频" width="120" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-image v-if="scope.row.images && scope.row.images.length" :src="scope.row.images[0]" :preview-src-list="scope.row.images" style="width: 50px; height: 50px;" fit="cover" /> <template v-if="scope.row.images">
<el-image v-if="parseImages(scope.row.images).length" :src="parseImages(scope.row.images)[0]" :preview-src-list="parseImages(scope.row.images)" style="width: 50px; height: 50px;" fit="cover" />
<span v-else>-</span>
</template>
<span v-else>-</span> <span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="分区" width="80" align="center"> <el-table-column label="分区" width="80" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag size="mini">{{ getZoneName(scope.row.zone) }}</el-tag> <el-tag size="mini">{{ scope.row.category_id ? getCategoryName(scope.row.category_id) : '默认' }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="like_count" label="点赞" width="70" align="center"> <el-table-column prop="like_count" label="点赞" width="70" align="center">
@ -83,14 +88,14 @@
<el-table-column prop="create_time" label="发布时间" width="160" align="center" /> <el-table-column prop="create_time" label="发布时间" width="160" align="center" />
<el-table-column label="审核状态" width="100" align="center"> <el-table-column label="审核状态" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag size="mini" :type="getAuditStatusType(scope.row.audit_status)">{{ getAuditStatusName(scope.row.audit_status) }}</el-tag> <el-tag size="mini" :type="getStatusType(scope.row.status)">{{ getStatusName(scope.row.status) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="220" align="center" fixed="right"> <el-table-column label="操作" width="220" align="center" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleDetail(scope.row)">详情</el-button> <el-button type="primary" size="mini" @click="handleDetail(scope.row)">详情</el-button>
<el-button v-if="scope.row.audit_status === 0" type="success" size="mini" @click="handleAudit(scope.row, 1)">通过</el-button> <el-button v-if="scope.row.status === 0" type="success" size="mini" @click="handleAudit(scope.row, 1)">通过</el-button>
<el-button v-if="scope.row.audit_status === 0" type="warning" size="mini" @click="handleAudit(scope.row, 2)">拒绝</el-button> <el-button v-if="scope.row.status === 0" type="warning" size="mini" @click="handleAudit(scope.row, 2)">拒绝</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button> <el-button type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -109,18 +114,20 @@
<el-descriptions-item label="动态ID">{{ detailData.id }}</el-descriptions-item> <el-descriptions-item label="动态ID">{{ detailData.id }}</el-descriptions-item>
<el-descriptions-item label="发布者"> <el-descriptions-item label="发布者">
<div class="user-info"> <div class="user-info">
<el-avatar :src="detailData.user_avatar" :size="24" /> <el-avatar :src="detailData.user_avatar || detailData.avatar" :size="24">
<span>{{ detailData.user_name }}</span> <span>{{ detailData.nickname ? detailData.nickname.substr(0, 1) : '用' }}</span>
</el-avatar>
<span>{{ detailData.nickname }}</span>
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="分区">{{ getZoneName(detailData.zone) }}</el-descriptions-item> <el-descriptions-item label="位置">{{ detailData.location || '-' }}</el-descriptions-item>
<el-descriptions-item label="审核状态"> <el-descriptions-item label="状态">
<el-tag size="small" :type="getAuditStatusType(detailData.audit_status)">{{ getAuditStatusName(detailData.audit_status) }}</el-tag> <el-tag size="small" :type="getStatusType(detailData.status)">{{ getStatusName(detailData.status) }}</el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="动态内容" :span="2">{{ detailData.content }}</el-descriptions-item> <el-descriptions-item label="动态内容" :span="2">{{ detailData.content }}</el-descriptions-item>
<el-descriptions-item label="图片/视频" :span="2" v-if="detailData.images && detailData.images.length"> <el-descriptions-item label="图片/视频" :span="2" v-if="detailData.images">
<div class="image-list"> <div class="image-list">
<el-image v-for="(img, idx) in detailData.images" :key="idx" :src="img" :preview-src-list="detailData.images" style="width: 100px; height: 100px; margin-right: 10px;" fit="cover" /> <el-image v-for="(img, idx) in parseImages(detailData.images)" :key="idx" :src="img" :preview-src-list="parseImages(detailData.images)" style="width: 100px; height: 100px; margin-right: 10px;" fit="cover" />
</div> </div>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="点赞数">{{ detailData.like_count || 0 }}</el-descriptions-item> <el-descriptions-item label="点赞数">{{ detailData.like_count || 0 }}</el-descriptions-item>
@ -131,8 +138,8 @@
<el-descriptions-item label="更新时间">{{ detailData.update_time || '-' }}</el-descriptions-item> <el-descriptions-item label="更新时间">{{ detailData.update_time || '-' }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div slot="footer"> <div slot="footer">
<el-button v-if="detailData && detailData.audit_status === 0" type="success" @click="handleAudit(detailData, 1)">通过</el-button> <el-button v-if="detailData && detailData.status === 0" type="success" @click="handleAudit(detailData, 1)">通过</el-button>
<el-button v-if="detailData && detailData.audit_status === 0" type="warning" @click="handleAudit(detailData, 2)">拒绝</el-button> <el-button v-if="detailData && detailData.status === 0" type="warning" @click="handleAudit(detailData, 2)">拒绝</el-button>
<el-button type="danger" @click="handleDelete(detailData)">删除</el-button> <el-button type="danger" @click="handleDelete(detailData)">删除</el-button>
<el-button @click="detailDialogVisible = false">关闭</el-button> <el-button @click="detailDialogVisible = false">关闭</el-button>
</div> </div>
@ -230,6 +237,18 @@ export default {
const map = { recommend: '推荐', hot: '热门', new: '最新', follow: '关注' }; const map = { recommend: '推荐', hot: '热门', new: '最新', follow: '关注' };
return map[zone] || zone; return map[zone] || zone;
}, },
getCategoryName(categoryId) {
const map = { 1: '推荐', 2: '热门', 3: '最新', 4: '关注' };
return map[categoryId] || '默认';
},
getStatusName(status) {
const map = { 0: '待审核', 1: '已通过', 2: '已拒绝' };
return map[status] !== undefined ? map[status] : '已通过';
},
getStatusType(status) {
const map = { 0: 'warning', 1: 'success', 2: 'danger' };
return map[status] || 'success';
},
getAuditStatusName(status) { getAuditStatusName(status) {
const map = { 0: '待审核', 1: '已通过', 2: '已拒绝' }; const map = { 0: '待审核', 1: '已通过', 2: '已拒绝' };
return map[status] || status; return map[status] || status;
@ -238,6 +257,15 @@ export default {
const map = { 0: 'warning', 1: 'success', 2: 'danger' }; const map = { 0: 'warning', 1: 'success', 2: 'danger' };
return map[status] || 'info'; return map[status] || 'info';
}, },
parseImages(images) {
if (!images) return [];
if (Array.isArray(images)) return images;
try {
return JSON.parse(images);
} catch (e) {
return images ? [images] : [];
}
},
async handleDetail(row) { async handleDetail(row) {
try { try {
const res = await socialDynamicDetailApi(row.id); const res = await socialDynamicDetailApi(row.id);

View File

@ -11,8 +11,8 @@
<!-- 数据表格 --> <!-- 数据表格 -->
<el-table :data="tableData" v-loading="loading" border> <el-table :data="tableData" v-loading="loading" border>
<el-table-column prop="id" label="ID" width="80" align="center" /> <el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column prop="task_name" label="任务名字" min-width="200" align="center" /> <el-table-column prop="name" label="任务名字" min-width="200" align="center" />
<el-table-column prop="diamond" label="赠送的钻石数量" min-width="200" align="center" /> <el-table-column prop="reward_value" label="赠送的钻石数量" min-width="200" align="center" />
<el-table-column prop="create_time" label="创建时间" width="180" align="center" /> <el-table-column prop="create_time" label="创建时间" width="180" align="center" />
<el-table-column label="操作" width="120" align="center" fixed="right"> <el-table-column label="操作" width="120" align="center" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
@ -39,11 +39,11 @@
<!-- 添加/编辑弹窗 --> <!-- 添加/编辑弹窗 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px"> <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="500px">
<el-form :model="form" ref="form" label-width="100px" :rules="formRules"> <el-form :model="form" ref="form" label-width="100px" :rules="formRules">
<el-form-item label="任务名称" prop="task_name"> <el-form-item label="任务名称" prop="name">
<el-input v-model="form.task_name" placeholder="请输入任务名称" /> <el-input v-model="form.name" placeholder="请输入任务名称" />
</el-form-item> </el-form-item>
<el-form-item label="钻石数量" prop="diamond"> <el-form-item label="钻石数量" prop="reward_value">
<el-input-number v-model="form.diamond" :min="0" style="width: 100%;" placeholder="请输入钻石数量" /> <el-input-number v-model="form.reward_value" :min="0" style="width: 100%;" placeholder="请输入钻石数量" />
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
@ -70,12 +70,12 @@ export default {
isEdit: false, isEdit: false,
form: { form: {
id: null, id: null,
task_name: '', name: '',
diamond: 0 reward_value: 0
}, },
formRules: { formRules: {
task_name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }], name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
diamond: [{ required: true, message: '请输入钻石数量', trigger: 'blur' }] reward_value: [{ required: true, message: '请输入钻石数量', trigger: 'blur' }]
} }
}; };
}, },
@ -90,7 +90,7 @@ export default {
this.tableData = res.list || []; this.tableData = res.list || [];
this.total = res.total || 0; this.total = res.total || 0;
} catch (error) { } catch (error) {
this.$message.error(error.message || '获取列表失败'); this.$message.error((error && error.message) || '获取列表失败');
} finally { } finally {
this.loading = false; this.loading = false;
} }
@ -102,7 +102,7 @@ export default {
handleAdd() { handleAdd() {
this.dialogTitle = '添加任务'; this.dialogTitle = '添加任务';
this.isEdit = false; this.isEdit = false;
this.form = { id: null, task_name: '', diamond: 0 }; this.form = { id: null, name: '', reward_value: 0 };
this.dialogVisible = true; this.dialogVisible = true;
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.form && this.$refs.form.clearValidate(); this.$refs.form && this.$refs.form.clearValidate();
@ -113,8 +113,8 @@ export default {
this.isEdit = true; this.isEdit = true;
this.form = { this.form = {
id: row.id, id: row.id,
task_name: row.task_name, name: row.name,
diamond: row.diamond reward_value: row.reward_value
}; };
this.dialogVisible = true; this.dialogVisible = true;
}, },
@ -132,7 +132,7 @@ export default {
this.dialogVisible = false; this.dialogVisible = false;
this.getList(); this.getList();
} catch (error) { } catch (error) {
this.$message.error(error.message || '操作失败'); this.$message.error((error && error.message) || '操作失败');
} }
}); });
}, },

View File

@ -19,11 +19,18 @@
<el-table-column prop="user_nickname" label="用户昵称" min-width="120" align="center" /> <el-table-column prop="user_nickname" label="用户昵称" min-width="120" align="center" />
<el-table-column label="用户头像" width="100" align="center"> <el-table-column label="用户头像" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-image :src="scope.row.user_avatar" style="width: 50px; height: 50px" :preview-src-list="[scope.row.user_avatar]" /> <el-avatar :size="50" :src="scope.row.user_avatar || ''">
<span v-if="!scope.row.user_avatar">{{ scope.row.user_nickname ? scope.row.user_nickname.substr(0, 1) : '' }}</span>
</el-avatar>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="diamond" label="赠送的钻石数量" min-width="150" align="center" /> <el-table-column prop="reward_value" label="赠送的钻石数量" min-width="150" align="center" />
<el-table-column prop="create_time" label="创建时间" width="180" align="center" /> <el-table-column prop="continuous_days" label="连续签到天数" width="120" align="center">
<template slot-scope="scope">
<el-tag type="success"> {{ scope.row.continuous_days || 1 }} </el-tag>
</template>
</el-table-column>
<el-table-column prop="create_time" label="签到时间" width="180" align="center" />
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
@ -59,7 +66,7 @@ export default {
this.tableData = res.list || []; this.tableData = res.list || [];
this.total = res.total || 0; this.total = res.total || 0;
} catch (error) { } catch (error) {
this.$message.error(error.message || '获取列表失败'); this.$message.error((error && error.message) || '获取列表失败');
} finally { } finally {
this.loading = false; this.loading = false;
} }

View File

@ -19,14 +19,18 @@
<el-table-column prop="user_nickname" label="用户昵称" min-width="120" align="center" /> <el-table-column prop="user_nickname" label="用户昵称" min-width="120" align="center" />
<el-table-column label="用户头像" width="100" align="center"> <el-table-column label="用户头像" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-image :src="scope.row.user_avatar" style="width: 50px; height: 50px" :preview-src-list="[scope.row.user_avatar]" /> <el-avatar :size="50" :src="scope.row.user_avatar || ''">
<span v-if="!scope.row.user_avatar">{{ scope.row.user_nickname ? scope.row.user_nickname.substr(0, 1) : '' }}</span>
</el-avatar>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="task_name" label="任务名字" min-width="150" align="center" /> <el-table-column prop="task_name" label="任务名字" min-width="150" align="center" />
<el-table-column prop="diamond" label="赠送的钻石数量" min-width="120" align="center" /> <el-table-column prop="reward_value" label="赠送的钻石数量" min-width="120" align="center" />
<el-table-column label="领取状态" width="100" align="center"> <el-table-column label="领取状态" width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.receive_status === '已领取' ? 'success' : 'danger'">{{ scope.row.receive_status }}</el-tag> <el-tag :type="scope.row.status === 2 ? 'success' : (scope.row.status === 1 ? 'warning' : 'info')">
{{ scope.row.status === 2 ? '已领取' : (scope.row.status === 1 ? '已完成' : '进行中') }}
</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="create_time" label="创建时间" width="180" align="center" /> <el-table-column prop="create_time" label="创建时间" width="180" align="center" />
@ -65,7 +69,7 @@ export default {
this.tableData = res.list || []; this.tableData = res.list || [];
this.total = res.total || 0; this.total = res.total || 0;
} catch (error) { } catch (error) {
this.$message.error(error.message || '获取列表失败'); this.$message.error((error && error.message) || '获取列表失败');
} finally { } finally {
this.loading = false; this.loading = false;
} }

View File

@ -46,27 +46,43 @@
<!-- 数据表格 --> <!-- 数据表格 -->
<el-table v-loading="loading" :data="tableData" border> <el-table v-loading="loading" :data="tableData" border>
<el-table-column prop="reporter_id" label="举报人" width="80" /> <el-table-column label="举报人" width="80" align="center">
<el-table-column prop="reporter_phone" label="举报人电话" width="120" /> <template slot-scope="scope">{{ scope.row.reporter_id || scope.row.uid || '-' }}</template>
<el-table-column prop="reporter_name" label="举报人名称" width="100" /> </el-table-column>
<el-table-column prop="report_type" label="举报类型" width="80" /> <el-table-column label="举报人电话" width="120" align="center">
<el-table-column prop="report_reason" label="举报原因" min-width="100" show-overflow-tooltip /> <template slot-scope="scope">{{ scope.row.reporter_phone || '-' }}</template>
<el-table-column prop="target_id" label="动态id/房间id/用户id/家族id" width="180" /> </el-table-column>
<el-table-column prop="reported_name" label="被举报人" width="100" /> <el-table-column label="举报人名称" width="100" align="center">
<el-table-column prop="reported_phone" label="被举报人电话" width="120" /> <template slot-scope="scope">{{ scope.row.reporter_name || scope.row.nickname || '-' }}</template>
<el-table-column prop="create_time" label="举报时间" width="160" /> </el-table-column>
<el-table-column label="处理状态" width="80" align="center"> <el-table-column label="举报类型" width="80" align="center">
<template slot-scope="scope">{{ scope.row.report_type || getTargetTypeText(scope.row.target_type) }}</template>
</el-table-column>
<el-table-column label="举报原因" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.report_reason || scope.row.reason || '-' }}</template>
</el-table-column>
<el-table-column prop="target_id" label="目标ID" width="80" align="center" />
<el-table-column label="被举报人" width="100" align="center">
<template slot-scope="scope">{{ scope.row.reported_name || scope.row.target_name || '-' }}</template>
</el-table-column>
<el-table-column label="被举报人电话" width="120" align="center">
<template slot-scope="scope">{{ scope.row.reported_phone || '-' }}</template>
</el-table-column>
<el-table-column prop="create_time" label="举报时间" width="160" align="center" />
<el-table-column label="状态" width="80" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'warning'" size="small"> <el-tag :type="getStatusType(scope.row.status)" size="small">
{{ scope.row.status === 1 ? '已处理' : '待处理' }} {{ getStatusText(scope.row.status) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="160" align="center" fixed="right"> <el-table-column label="操作" width="180" align="center" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleDetail(scope.row)">查看详情</el-button> <div style="display: flex; justify-content: center; align-items: center; gap: 5px;">
<el-button v-if="scope.row.status === 0" type="warning" size="mini" @click="handleProcess(scope.row)">已处理</el-button> <el-button type="primary" size="mini" @click="handleDetail(scope.row)">详情</el-button>
<el-tag v-else type="info" size="small">已处理</el-tag> <el-button v-if="scope.row.status === 0 || scope.row.status === 1" type="warning" size="mini" @click="handleProcess(scope.row)">处理</el-button>
<el-tag v-else type="success" size="mini">已处理</el-tag>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -203,6 +219,21 @@ export default {
this.getList() this.getList()
}, },
methods: { methods: {
//
getTargetTypeText(type) {
const typeMap = { 1: '用户', 2: '房间', 3: '动态', 4: '评论' }
return typeMap[type] || '其他'
},
//
getStatusText(status) {
const statusMap = { 0: '待处理', 1: '处理中', 2: '已处理', 3: '已忽略' }
return statusMap[status] || '待处理'
},
//
getStatusType(status) {
const typeMap = { 0: 'warning', 1: 'info', 2: 'success', 3: 'danger' }
return typeMap[status] || 'warning'
},
getList() { getList() {
this.loading = true this.loading = true
const params = { const params = {
@ -217,10 +248,22 @@ export default {
limit: this.limit limit: this.limit
} }
reportListApi(params).then(res => { reportListApi(params).then(res => {
//
if (res && res.list) {
this.tableData = res.list || []
this.total = res.total || 0
} else if (res && res.data) {
this.tableData = res.data.list || [] this.tableData = res.data.list || []
this.total = res.data.total || 0 this.total = res.data.total || 0
} else {
this.tableData = []
this.total = 0
}
this.loading = false this.loading = false
}).catch(() => { }).catch((err) => {
console.error('获取举报列表失败:', err)
this.tableData = []
this.total = 0
this.loading = false this.loading = false
}) })
}, },

View File

@ -121,11 +121,26 @@ export default {
startTime: this.searchForm.startTime || undefined, startTime: this.searchForm.startTime || undefined,
endTime: this.searchForm.endTime || undefined endTime: this.searchForm.endTime || undefined
}; };
console.log('请求参数:', params);
const res = await sensitiveWordListApi(params); const res = await sensitiveWordListApi(params);
this.tableData = res.list || []; console.log('响应数据:', res);
if (res && res.list) {
this.tableData = res.list;
this.total = res.total || 0; this.total = res.total || 0;
} else if (Array.isArray(res)) {
//
this.tableData = res;
this.total = res.length;
} else {
this.tableData = [];
this.total = 0;
}
} catch (error) { } catch (error) {
console.error('获取敏感词列表失败:', error);
this.$message.error(error.message || '获取列表失败'); this.$message.error(error.message || '获取列表失败');
this.tableData = [];
this.total = 0;
} finally { } finally {
this.loading = false; this.loading = false;
} }

View File

@ -4,16 +4,39 @@
<div class="padding-add"> <div class="padding-add">
<el-page-header @back="goBack" content="每日签到配置"></el-page-header> <el-page-header @back="goBack" content="每日签到配置"></el-page-header>
<div class="mt20"> <div class="mt20">
<el-table :data="tableData" v-loading="loading" border> <!-- 提示信息 -->
<el-table-column prop="id" label="ID" width="80" align="center" /> <el-alert
<el-table-column prop="day" label="天数" width="120" align="center" /> title="签到配置说明用户连续签到可获得对应天数的积分奖励第7天后循环"
<el-table-column prop="integral" label="转后积分奖励" width="150" align="center" /> type="info"
<el-table-column label="操作" width="120" align="center" fixed="right"> :closable="false"
show-icon
class="mb20"
/>
<el-table :data="tableData" v-loading="loading" border style="width: 100%">
<el-table-column prop="id" label="ID" width="100" align="center" />
<el-table-column prop="day" label="连续签到天数" min-width="150" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="warning" size="small" @click="handleEdit(scope.row)">编辑</el-button> <span> {{ scope.row.day }} </span>
</template>
</el-table-column>
<el-table-column prop="integral" label="奖励积分" min-width="150" align="center">
<template slot-scope="scope">
<el-tag type="warning">+{{ scope.row.integral }} 积分</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="120" align="center">
<template>
<el-tag type="success">已启用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center">
<template slot-scope="scope">
<el-button type="warning" size="mini" @click="handleEdit(scope.row)">编辑</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="acea-row row-right page mt20"> <div class="acea-row row-right page mt20">
<el-pagination <el-pagination
@size-change="handleSizeChange" @size-change="handleSizeChange"
@ -31,32 +54,34 @@
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<el-dialog <el-dialog
title="编辑" title="编辑签到配置"
:visible.sync="dialogVisible" :visible.sync="dialogVisible"
width="500px" width="450px"
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<el-form :model="editForm" :rules="rules" ref="editForm" label-width="100px"> <el-form :model="editForm" :rules="rules" ref="editForm" label-width="120px">
<el-form-item label="天数" prop="day"> <el-form-item label="连续签到天数" prop="day">
<el-input-number <el-input-number
v-model="editForm.day" v-model="editForm.day"
:min="1" :min="1"
:max="365" :max="7"
controls-position="right" controls-position="right"
style="width: 100%" style="width: 100%"
disabled
></el-input-number> ></el-input-number>
</el-form-item> </el-form-item>
<el-form-item label="转后积分" prop="integral"> <el-form-item label="奖励积分" prop="integral">
<el-input-number <el-input-number
v-model="editForm.integral" v-model="editForm.integral"
:min="0" :min="0"
:max="9999"
controls-position="right" controls-position="right"
style="width: 100%" style="width: 100%"
></el-input-number> ></el-input-number>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">返回</el-button> <el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button> <el-button type="primary" @click="handleSave">保存</el-button>
</div> </div>
</el-dialog> </el-dialog>
@ -81,16 +106,14 @@ export default {
editForm: { editForm: {
id: null, id: null,
day: 1, day: 1,
integral: 2, integral: 0,
}, },
rules: { rules: {
day: [ day: [
{ required: true, message: '请输入天数', trigger: 'blur' }, { required: true, message: '请输入天数', trigger: 'blur' },
{ type: 'number', min: 1, max: 365, message: '天数范围为1-365', trigger: 'blur' },
], ],
integral: [ integral: [
{ required: true, message: '请输入转后积分', trigger: 'blur' }, { required: true, message: '请输入奖励积分', trigger: 'blur' },
{ type: 'number', min: 0, message: '积分不能为负数', trigger: 'blur' },
], ],
}, },
}; };
@ -103,21 +126,20 @@ export default {
this.loading = true; this.loading = true;
signConfigListApi(this.searchForm) signConfigListApi(this.searchForm)
.then((res) => { .then((res) => {
this.tableData = res.data.list || []; this.tableData = Array.isArray(res) ? res : (res.list || []);
this.total = res.data.total || 0; this.total = this.tableData.length;
this.loading = false; this.loading = false;
}) })
.catch((err) => { .catch((err) => {
console.error('获取签到配置列表失败:', err); console.error('获取签到配置列表失败:', err);
// 使
this.tableData = [ this.tableData = [
{ id: 1, day: 1, integral: 2 }, { id: 39, day: 1, integral: 10 },
{ id: 2, day: 2, integral: 4 }, { id: 40, day: 2, integral: 20 },
{ id: 3, day: 3, integral: 6 }, { id: 41, day: 3, integral: 30 },
{ id: 4, day: 4, integral: 8 }, { id: 42, day: 4, integral: 40 },
{ id: 5, day: 5, integral: 8 }, { id: 43, day: 5, integral: 50 },
{ id: 6, day: 6, integral: 10 }, { id: 44, day: 6, integral: 60 },
{ id: 7, day: 7, integral: 10 }, { id: 122, day: 7, integral: 70 },
]; ];
this.total = 7; this.total = 7;
this.loading = false; this.loading = false;
@ -143,7 +165,7 @@ export default {
}) })
.catch((err) => { .catch((err) => {
console.error('保存失败:', err); console.error('保存失败:', err);
this.$message.error('保存失败,后端接口暂未实现'); this.$message.error('保存失败');
}); });
} }
}); });
@ -167,4 +189,10 @@ export default {
.mt20 { .mt20 {
margin-top: 20px; margin-top: 20px;
} }
.mb20 {
margin-bottom: 20px;
}
.dialog-footer {
text-align: right;
}
</style> </style>

View File

@ -24,7 +24,7 @@
<!-- 表格 --> <!-- 表格 -->
<el-table :data="tableData" v-loading="loading" border style="width: 100%"> <el-table :data="tableData" v-loading="loading" border style="width: 100%">
<el-table-column prop="id" label="id" width="80" align="center" /> <el-table-column prop="id" label="ID" width="80" align="center" />
<el-table-column label="用户昵称" width="150" align="center"> <el-table-column label="用户昵称" width="150" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.nickname || '-' }}</span> <span>{{ scope.row.nickname || '-' }}</span>
@ -41,12 +41,19 @@
</el-avatar> </el-avatar>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="获得的经验或积分" width="180" align="center"> <el-table-column label="获得积分" width="120" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.number }}</span> <el-tag type="warning">+{{ scope.row.number }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="时间时间" min-width="180" align="center"> <el-table-column label="类型" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.type === 1 ? 'success' : 'primary'">
{{ scope.row.type === 1 ? '积分' : '经验' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="签到时间" min-width="180" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ formatTime(scope.row.createTime) }}</span> <span>{{ formatTime(scope.row.createTime) }}</span>
</template> </template>
@ -96,8 +103,17 @@ export default {
this.loading = true; this.loading = true;
userSignListApi(this.searchForm) userSignListApi(this.searchForm)
.then((res) => { .then((res) => {
this.tableData = res.data.list || []; //
this.total = res.data.total || 0; if (res && res.list) {
this.tableData = res.list || [];
this.total = res.total || 0;
} else if (Array.isArray(res)) {
this.tableData = res;
this.total = res.length;
} else {
this.tableData = [];
this.total = 0;
}
this.loading = false; this.loading = false;
}) })
.catch((err) => { .catch((err) => {

View File

@ -11,6 +11,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -68,7 +69,7 @@ public class FanGroupController {
params.add(limit); params.add(limit);
String sql = "SELECT fg.*, " + String sql = "SELECT fg.*, " +
"(SELECT COUNT(*) FROM eb_group_message gm WHERE gm.group_id = fg.group_id AND gm.is_deleted = 0) as message_count " + "(SELECT COUNT(*) FROM eb_group_message gm WHERE gm.group_id = fg.id AND gm.is_deleted = 0) as message_count " +
"FROM eb_fan_group fg" + whereSql + " ORDER BY fg.id DESC LIMIT ?, ?"; "FROM eb_fan_group fg" + whereSql + " ORDER BY fg.id DESC LIMIT ?, ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, params.toArray()); List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, params.toArray());
@ -140,9 +141,19 @@ public class FanGroupController {
String countSql = "SELECT COUNT(*) FROM eb_fan_group_member WHERE group_id = ?"; String countSql = "SELECT COUNT(*) FROM eb_fan_group_member WHERE group_id = ?";
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, groupId); Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, groupId);
String sql = "SELECT * FROM eb_fan_group_member WHERE group_id = ? ORDER BY level DESC, intimacy DESC LIMIT ?, ?"; String sql = "SELECT m.*, u.avatar, u.nickname as user_nickname " +
"FROM eb_fan_group_member m " +
"LEFT JOIN eb_user u ON m.uid = u.uid " +
"WHERE m.group_id = ? ORDER BY m.level DESC, m.intimacy DESC LIMIT ?, ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, groupId, (page - 1) * limit, limit); List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, groupId, (page - 1) * limit, limit);
// 如果nickname为空使用user_nickname
for (Map<String, Object> item : list) {
if (item.get("nickname") == null || "".equals(item.get("nickname"))) {
item.put("nickname", item.get("user_nickname"));
}
}
CommonPage<Map<String, Object>> result = new CommonPage<>(); CommonPage<Map<String, Object>> result = new CommonPage<>();
result.setList(list); result.setList(list);
result.setTotal(total != null ? total.longValue() : 0L); result.setTotal(total != null ? total.longValue() : 0L);
@ -166,13 +177,16 @@ public class FanGroupController {
try { try {
// 获取成员所属粉丝团 // 获取成员所属粉丝团
Map<String, Object> member = jdbcTemplate.queryForMap("SELECT group_id FROM eb_fan_group_member WHERE id = ?", id); Map<String, Object> member = jdbcTemplate.queryForMap("SELECT group_id FROM eb_fan_group_member WHERE id = ?", id);
Integer groupId = (Integer) member.get("group_id"); Object groupIdObj = member.get("group_id");
Integer groupId = groupIdObj != null ? ((Number) groupIdObj).intValue() : null;
// 删除成员 // 删除成员
jdbcTemplate.update("DELETE FROM eb_fan_group_member WHERE id = ?", id); jdbcTemplate.update("DELETE FROM eb_fan_group_member WHERE id = ?", id);
// 更新粉丝团成员数量 // 更新粉丝团成员数量
if (groupId != null) {
jdbcTemplate.update("UPDATE eb_fan_group SET member_count = member_count - 1 WHERE id = ? AND member_count > 0", groupId); jdbcTemplate.update("UPDATE eb_fan_group SET member_count = member_count - 1 WHERE id = ? AND member_count > 0", groupId);
}
return CommonResult.success("删除成功"); return CommonResult.success("删除成功");
} catch (Exception e) { } catch (Exception e) {
@ -230,4 +244,142 @@ public class FanGroupController {
return CommonResult.failed("删除失败:" + e.getMessage()); return CommonResult.failed("删除失败:" + e.getMessage());
} }
} }
/**
* 获取粉丝团详情
*/
@ApiOperation(value = "粉丝团详情")
@RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
public CommonResult<Map<String, Object>> getDetail(@PathVariable Integer id) {
try {
String sql = "SELECT fg.*, u.avatar as anchor_avatar " +
"FROM eb_fan_group fg " +
"LEFT JOIN eb_user u ON fg.anchor_id = u.uid " +
"WHERE fg.id = ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, id);
if (list.isEmpty()) {
return CommonResult.failed("粉丝团不存在");
}
return CommonResult.success(list.get(0));
} catch (Exception e) {
log.error("获取粉丝团详情失败", e);
return CommonResult.failed("获取详情失败:" + e.getMessage());
}
}
/**
* 修改粉丝团状态解散/恢复
*/
@ApiOperation(value = "修改粉丝团状态")
@RequestMapping(value = "/status/{id}", method = RequestMethod.POST)
public CommonResult<String> updateStatus(
@PathVariable Integer id,
@RequestBody Map<String, Object> request) {
try {
Integer status = (Integer) request.get("status");
if (status == null || (status != 0 && status != 1)) {
return CommonResult.failed("状态值无效");
}
jdbcTemplate.update("UPDATE eb_fan_group SET status = ?, update_time = NOW() WHERE id = ?", status, id);
return CommonResult.success(status == 1 ? "已恢复" : "已解散");
} catch (Exception e) {
log.error("修改粉丝团状态失败", e);
return CommonResult.failed("操作失败:" + e.getMessage());
}
}
/**
* 修改粉丝团信息
*/
@ApiOperation(value = "修改粉丝团信息")
@RequestMapping(value = "/update/{id}", method = RequestMethod.POST)
public CommonResult<String> updateFanGroup(
@PathVariable Integer id,
@RequestBody Map<String, Object> request) {
try {
StringBuilder updateSql = new StringBuilder("UPDATE eb_fan_group SET update_time = NOW()");
List<Object> params = new ArrayList<>();
if (request.containsKey("name") && request.get("name") != null) {
updateSql.append(", name = ?");
params.add(request.get("name"));
}
if (request.containsKey("badge") && request.get("badge") != null) {
updateSql.append(", badge = ?");
params.add(request.get("badge"));
}
if (request.containsKey("badge_color") && request.get("badge_color") != null) {
updateSql.append(", badge_color = ?");
params.add(request.get("badge_color"));
}
updateSql.append(" WHERE id = ?");
params.add(id);
jdbcTemplate.update(updateSql.toString(), params.toArray());
return CommonResult.success("修改成功");
} catch (Exception e) {
log.error("修改粉丝团信息失败", e);
return CommonResult.failed("修改失败:" + e.getMessage());
}
}
/**
* 修改成员等级
*/
@ApiOperation(value = "修改成员等级")
@RequestMapping(value = "/member/update-level/{id}", method = RequestMethod.POST)
public CommonResult<String> updateMemberLevel(
@PathVariable Integer id,
@RequestBody Map<String, Object> request) {
try {
Integer level = (Integer) request.get("level");
if (level == null || level < 1 || level > 10) {
return CommonResult.failed("等级值无效1-10");
}
jdbcTemplate.update("UPDATE eb_fan_group_member SET level = ? WHERE id = ?", level, id);
return CommonResult.success("修改成功");
} catch (Exception e) {
log.error("修改成员等级失败", e);
return CommonResult.failed("修改失败:" + e.getMessage());
}
}
/**
* 统计数据
*/
@ApiOperation(value = "粉丝团统计")
@RequestMapping(value = "/statistics", method = RequestMethod.GET)
public CommonResult<Map<String, Object>> getStatistics() {
try {
Map<String, Object> stats = new HashMap<>();
// 总粉丝团数
Integer totalGroups = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM eb_fan_group", Integer.class);
stats.put("totalGroups", totalGroups != null ? totalGroups : 0);
// 正常状态粉丝团数
Integer activeGroups = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM eb_fan_group WHERE status = 1", Integer.class);
stats.put("activeGroups", activeGroups != null ? activeGroups : 0);
// 总成员数
Integer totalMembers = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM eb_fan_group_member WHERE status = 1", Integer.class);
stats.put("totalMembers", totalMembers != null ? totalMembers : 0);
// 今日新增成员
Integer todayNewMembers = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM eb_fan_group_member WHERE DATE(join_time) = CURDATE()", Integer.class);
stats.put("todayNewMembers", todayNewMembers != null ? todayNewMembers : 0);
return CommonResult.success(stats);
} catch (Exception e) {
log.error("获取统计数据失败", e);
return CommonResult.failed("获取统计失败:" + e.getMessage());
}
}
} }

View File

@ -141,19 +141,21 @@ public class NoviceTaskController {
@RequestParam(value = "nickname", required = false) String nickname) { @RequestParam(value = "nickname", required = false) String nickname) {
try { try {
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_user_task_record WHERE 1=1"); StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_user_task_record r WHERE 1=1");
StringBuilder sql = new StringBuilder("SELECT * FROM eb_user_task_record WHERE 1=1"); StringBuilder sql = new StringBuilder(
"SELECT r.*, u.avatar as user_avatar FROM eb_user_task_record r " +
"LEFT JOIN eb_user u ON r.uid = u.uid WHERE 1=1");
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
if (nickname != null && !nickname.isEmpty()) { if (nickname != null && !nickname.isEmpty()) {
countSql.append(" AND user_nickname LIKE ?"); countSql.append(" AND r.user_nickname LIKE ?");
sql.append(" AND user_nickname LIKE ?"); sql.append(" AND r.user_nickname LIKE ?");
params.add("%" + nickname + "%"); params.add("%" + nickname + "%");
} }
Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, params.toArray()); Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, params.toArray());
sql.append(" ORDER BY id DESC LIMIT ?, ?"); sql.append(" ORDER BY r.id DESC LIMIT ?, ?");
params.add((page - 1) * limit); params.add((page - 1) * limit);
params.add(limit); params.add(limit);
@ -184,19 +186,21 @@ public class NoviceTaskController {
@RequestParam(value = "nickname", required = false) String nickname) { @RequestParam(value = "nickname", required = false) String nickname) {
try { try {
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_user_signin_record WHERE 1=1"); StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_user_signin_record r WHERE 1=1");
StringBuilder sql = new StringBuilder("SELECT * FROM eb_user_signin_record WHERE 1=1"); StringBuilder sql = new StringBuilder(
"SELECT r.*, u.avatar as user_avatar FROM eb_user_signin_record r " +
"LEFT JOIN eb_user u ON r.uid = u.uid WHERE 1=1");
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
if (nickname != null && !nickname.isEmpty()) { if (nickname != null && !nickname.isEmpty()) {
countSql.append(" AND user_nickname LIKE ?"); countSql.append(" AND r.user_nickname LIKE ?");
sql.append(" AND user_nickname LIKE ?"); sql.append(" AND r.user_nickname LIKE ?");
params.add("%" + nickname + "%"); params.add("%" + nickname + "%");
} }
Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, params.toArray()); Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, params.toArray());
sql.append(" ORDER BY id DESC LIMIT ?, ?"); sql.append(" ORDER BY r.id DESC LIMIT ?, ?");
params.add((page - 1) * limit); params.add((page - 1) * limit);
params.add(limit); params.add(limit);

View File

@ -10,6 +10,8 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -36,62 +38,193 @@ public class ReportListController {
@RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "limit", defaultValue = "10") Integer limit) { @RequestParam(value = "limit", defaultValue = "10") Integer limit) {
StringBuilder sql = new StringBuilder("SELECT * FROM eb_report WHERE 1=1"); try {
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_report WHERE 1=1"); // 查询举报记录关联用户表获取举报人和被举报人信息
StringBuilder sql = new StringBuilder();
sql.append("SELECT r.id, r.uid as reporter_id, r.nickname as reporter_name, ");
sql.append("u1.phone as reporter_phone, u1.avatar as reporter_avatar, ");
sql.append("r.target_type, r.target_id, r.target_name, ");
sql.append("r.reason_type, r.reason as report_reason, r.images as screenshot, ");
sql.append("r.status, r.result as process_remark, r.handle_time as process_time, ");
sql.append("r.create_time, ");
// 根据target_type获取被举报人信息
sql.append("CASE r.target_type WHEN 1 THEN u2.uid ELSE NULL END as reported_id, ");
sql.append("CASE r.target_type WHEN 1 THEN u2.nickname ELSE r.target_name END as reported_name, ");
sql.append("CASE r.target_type WHEN 1 THEN u2.phone ELSE NULL END as reported_phone, ");
sql.append("CASE r.target_type WHEN 1 THEN u2.avatar ELSE NULL END as reported_avatar, ");
// 转换举报类型为文字
sql.append("CASE r.target_type WHEN 1 THEN '用户' WHEN 2 THEN '房间' WHEN 3 THEN '动态' WHEN 4 THEN '评论' ELSE '其他' END as report_type ");
sql.append("FROM eb_report r ");
sql.append("LEFT JOIN eb_user u1 ON r.uid = u1.uid ");
sql.append("LEFT JOIN eb_user u2 ON r.target_type = 1 AND r.target_id = u2.uid ");
sql.append("WHERE 1=1 ");
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_report r WHERE 1=1 ");
List<Object> params = new ArrayList<>();
List<Object> countParams = new ArrayList<>();
if (userName != null && !userName.isEmpty()) { if (userName != null && !userName.isEmpty()) {
sql.append(" AND reporter_name LIKE '%").append(userName).append("%'"); sql.append(" AND r.nickname LIKE ?");
countSql.append(" AND reporter_name LIKE '%").append(userName).append("%'"); countSql.append(" AND r.nickname LIKE ?");
params.add("%" + userName + "%");
countParams.add("%" + userName + "%");
} }
if (phone != null && !phone.isEmpty()) { if (phone != null && !phone.isEmpty()) {
sql.append(" AND reporter_phone LIKE '%").append(phone).append("%'"); sql.append(" AND EXISTS (SELECT 1 FROM eb_user u WHERE u.uid = r.uid AND u.phone LIKE ?)");
countSql.append(" AND reporter_phone LIKE '%").append(phone).append("%'"); countSql.append(" AND EXISTS (SELECT 1 FROM eb_user u WHERE u.uid = r.uid AND u.phone LIKE ?)");
params.add("%" + phone + "%");
countParams.add("%" + phone + "%");
} }
if (userId != null && !userId.isEmpty()) { if (userId != null && !userId.isEmpty()) {
sql.append(" AND reporter_id = ").append(userId); sql.append(" AND r.uid = ?");
countSql.append(" AND reporter_id = ").append(userId); countSql.append(" AND r.uid = ?");
params.add(Integer.valueOf(userId));
countParams.add(Integer.valueOf(userId));
} }
if (reportType != null && !reportType.isEmpty()) { if (reportType != null && !reportType.isEmpty()) {
sql.append(" AND report_type = '").append(reportType).append("'"); Integer typeValue = getReportTypeValue(reportType);
countSql.append(" AND report_type = '").append(reportType).append("'"); if (typeValue != null) {
sql.append(" AND r.target_type = ?");
countSql.append(" AND r.target_type = ?");
params.add(typeValue);
countParams.add(typeValue);
}
} }
if (status != null && !status.isEmpty()) { if (status != null && !status.isEmpty()) {
sql.append(" AND status = ").append(status); sql.append(" AND r.status = ?");
countSql.append(" AND status = ").append(status); countSql.append(" AND r.status = ?");
params.add(Integer.valueOf(status));
countParams.add(Integer.valueOf(status));
} }
if (startTime != null && !startTime.isEmpty()) { if (startTime != null && !startTime.isEmpty()) {
sql.append(" AND create_time >= '").append(startTime).append("'"); sql.append(" AND r.create_time >= ?");
countSql.append(" AND create_time >= '").append(startTime).append("'"); countSql.append(" AND r.create_time >= ?");
params.add(startTime);
countParams.add(startTime);
} }
if (endTime != null && !endTime.isEmpty()) { if (endTime != null && !endTime.isEmpty()) {
sql.append(" AND create_time <= '").append(endTime).append("'"); sql.append(" AND r.create_time <= ?");
countSql.append(" AND create_time <= '").append(endTime).append("'"); countSql.append(" AND r.create_time <= ?");
params.add(endTime);
countParams.add(endTime);
} }
sql.append(" ORDER BY create_time DESC"); // 查询总数
Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class); Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class, countParams.toArray());
// 分页查询
sql.append(" ORDER BY r.create_time DESC LIMIT ?, ?");
int offset = (page - 1) * limit; int offset = (page - 1) * limit;
sql.append(" LIMIT ").append(offset).append(", ").append(limit); params.add(offset);
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString()); params.add(limit);
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), params.toArray());
CommonPage<Map<String, Object>> result = new CommonPage<>(); CommonPage<Map<String, Object>> result = new CommonPage<>();
result.setList(list); result.setList(list);
result.setTotal(total); result.setTotal(total != null ? total : 0L);
result.setPage(page); result.setPage(page);
result.setLimit(limit); result.setLimit(limit);
result.setTotalPage((int) Math.ceil((double) total / limit)); result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit));
return CommonResult.success(result); return CommonResult.success(result);
} catch (Exception e) {
log.error("获取举报列表失败", e);
return CommonResult.failed("获取列表失败:" + e.getMessage());
}
} }
@ApiOperation(value = "处理举报") @ApiOperation(value = "处理举报")
@RequestMapping(value = "/process", method = RequestMethod.POST) @RequestMapping(value = "/process", method = RequestMethod.POST)
public CommonResult<String> process(@RequestBody Map<String, Object> data) { public CommonResult<String> process(@RequestBody Map<String, Object> data) {
Integer id = (Integer) data.get("id"); try {
Integer id = data.get("id") != null ? Integer.valueOf(data.get("id").toString()) : null;
String remark = (String) data.get("remark"); String remark = (String) data.get("remark");
String sql = "UPDATE eb_report SET status = 1, process_remark = ?, process_time = NOW() WHERE id = ?";
if (id == null) {
return CommonResult.failed("举报ID不能为空");
}
String sql = "UPDATE eb_report SET status = 2, result = ?, handle_time = NOW(), update_time = NOW() WHERE id = ?";
jdbcTemplate.update(sql, remark, id); jdbcTemplate.update(sql, remark, id);
return CommonResult.success("处理成功"); return CommonResult.success("处理成功");
} catch (Exception e) {
log.error("处理举报失败", e);
return CommonResult.failed("处理失败:" + e.getMessage());
}
}
@ApiOperation(value = "删除举报")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
public CommonResult<String> delete(@PathVariable Integer id) {
try {
String sql = "DELETE FROM eb_report WHERE id = ?";
jdbcTemplate.update(sql, id);
return CommonResult.success("删除成功");
} catch (Exception e) {
log.error("删除举报失败", e);
return CommonResult.failed("删除失败:" + e.getMessage());
}
}
@ApiOperation(value = "批量删除举报")
@RequestMapping(value = "/batch-delete", method = RequestMethod.POST)
public CommonResult<String> batchDelete(@RequestBody List<Integer> ids) {
try {
if (ids == null || ids.isEmpty()) {
return CommonResult.failed("请选择要删除的记录");
}
String placeholders = String.join(",", java.util.Collections.nCopies(ids.size(), "?"));
String sql = "DELETE FROM eb_report WHERE id IN (" + placeholders + ")";
int rows = jdbcTemplate.update(sql, ids.toArray());
return CommonResult.success("批量删除成功,删除" + rows + "条记录");
} catch (Exception e) {
log.error("批量删除举报失败", e);
return CommonResult.failed("删除失败:" + e.getMessage());
}
}
@ApiOperation(value = "举报统计")
@RequestMapping(value = "/statistics", method = RequestMethod.GET)
public CommonResult<Map<String, Object>> getStatistics() {
try {
Map<String, Object> stats = new HashMap<>();
// 总举报数
Integer total = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM eb_report", Integer.class);
stats.put("total", total != null ? total : 0);
// 待处理数
Integer pending = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM eb_report WHERE status = 0", Integer.class);
stats.put("pending", pending != null ? pending : 0);
// 已处理数
Integer processed = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM eb_report WHERE status = 2", Integer.class);
stats.put("processed", processed != null ? processed : 0);
// 今日新增
Integer todayNew = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM eb_report WHERE DATE(create_time) = CURDATE()", Integer.class);
stats.put("todayNew", todayNew != null ? todayNew : 0);
return CommonResult.success(stats);
} catch (Exception e) {
log.error("获取举报统计失败", e);
return CommonResult.failed("获取统计失败:" + e.getMessage());
}
}
/**
* 将举报类型文字转换为数值
*/
private Integer getReportTypeValue(String reportType) {
if (reportType == null) return null;
switch (reportType) {
case "用户": return 1;
case "房间": return 2;
case "动态": return 3;
case "评论": return 4;
default: return null;
}
} }
} }

View File

@ -40,28 +40,52 @@ public class SensitiveWordController {
@RequestParam(value = "endTime", required = false) String endTime) { @RequestParam(value = "endTime", required = false) String endTime) {
try { try {
// 先检查表是否存在
try {
jdbcTemplate.queryForObject("SELECT 1 FROM eb_sensitive_word LIMIT 1", Integer.class);
} catch (Exception e) {
// 表不存在创建表
log.info("敏感词表不存在,正在创建...");
String createTableSql = "CREATE TABLE IF NOT EXISTS `eb_sensitive_word` (" +
"`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID', " +
"`word` varchar(128) NOT NULL DEFAULT '' COMMENT '敏感词', " +
"`category` varchar(32) NOT NULL DEFAULT 'default' COMMENT '分类', " +
"`level` tinyint NOT NULL DEFAULT 1 COMMENT '级别', " +
"`action` tinyint NOT NULL DEFAULT 1 COMMENT '处理方式', " +
"`replace_text` varchar(32) NOT NULL DEFAULT '***' COMMENT '替换文本', " +
"`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态', " +
"`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
"`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
"PRIMARY KEY (`id`), " +
"UNIQUE KEY `uk_word` (`word`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='敏感词表'";
jdbcTemplate.execute(createTableSql);
log.info("敏感词表创建成功");
}
StringBuilder whereSql = new StringBuilder(" WHERE 1=1"); StringBuilder whereSql = new StringBuilder(" WHERE 1=1");
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
if (word != null && !word.isEmpty()) { if (word != null && !word.trim().isEmpty()) {
whereSql.append(" AND word LIKE ?"); whereSql.append(" AND word LIKE ?");
params.add("%" + word + "%"); params.add("%" + word.trim() + "%");
} }
if (startTime != null && !startTime.isEmpty()) { if (startTime != null && !startTime.trim().isEmpty()) {
whereSql.append(" AND create_time >= ?"); whereSql.append(" AND create_time >= ?");
params.add(startTime + " 00:00:00"); params.add(startTime + " 00:00:00");
} }
if (endTime != null && !endTime.isEmpty()) { if (endTime != null && !endTime.trim().isEmpty()) {
whereSql.append(" AND create_time <= ?"); whereSql.append(" AND create_time <= ?");
params.add(endTime + " 23:59:59"); params.add(endTime + " 23:59:59");
} }
// 查询总数
String countSql = "SELECT COUNT(*) FROM eb_sensitive_word" + whereSql; String countSql = "SELECT COUNT(*) FROM eb_sensitive_word" + whereSql;
Integer total = jdbcTemplate.queryForObject(countSql, params.toArray(), Integer.class); Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, params.toArray());
params.add((page - 1) * limit); // 查询列表
params.add(limit); int offset = (page - 1) * limit;
String sql = "SELECT * FROM eb_sensitive_word" + whereSql + " ORDER BY id DESC LIMIT ?, ?"; String sql = "SELECT * FROM eb_sensitive_word" + whereSql + " ORDER BY id DESC LIMIT " + offset + ", " + limit;
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, params.toArray()); List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, params.toArray());
CommonPage<Map<String, Object>> result = new CommonPage<>(); CommonPage<Map<String, Object>> result = new CommonPage<>();
@ -71,6 +95,7 @@ public class SensitiveWordController {
result.setLimit(limit); result.setLimit(limit);
result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit)); result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit));
log.info("敏感词列表查询成功,总数: {}, 当前页数据: {}", total, list.size());
return CommonResult.success(result); return CommonResult.success(result);
} catch (Exception e) { } catch (Exception e) {
log.error("获取敏感词列表失败", e); log.error("获取敏感词列表失败", e);

View File

@ -0,0 +1,209 @@
package com.zbkj.admin.controller;
import com.zbkj.common.page.CommonPage;
import com.zbkj.common.result.CommonResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 社会动态管理控制器
*/
@Slf4j
@RestController
@RequestMapping("api/admin/social/dynamic")
@Api(tags = "社会动态管理")
@Validated
public class SocialDynamicController {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 动态列表
*/
@ApiOperation(value = "动态列表")
@RequestMapping(value = "/list", method = RequestMethod.GET)
public CommonResult<CommonPage<Map<String, Object>>> getList(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "limit", defaultValue = "10") Integer limit,
@RequestParam(value = "nickname", required = false) String nickname,
@RequestParam(value = "status", required = false) Integer status,
@RequestParam(value = "categoryId", required = false) Integer categoryId) {
try {
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_dynamic WHERE 1=1");
StringBuilder sql = new StringBuilder(
"SELECT d.*, u.avatar as user_avatar FROM eb_dynamic d " +
"LEFT JOIN eb_user u ON d.uid = u.uid WHERE 1=1");
List<Object> params = new ArrayList<>();
if (nickname != null && !nickname.isEmpty()) {
countSql.append(" AND nickname LIKE ?");
sql.append(" AND d.nickname LIKE ?");
params.add("%" + nickname + "%");
}
if (status != null) {
countSql.append(" AND status = ?");
sql.append(" AND d.status = ?");
params.add(status);
}
if (categoryId != null) {
countSql.append(" AND category_id = ?");
sql.append(" AND d.category_id = ?");
params.add(categoryId);
}
Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, params.toArray());
sql.append(" ORDER BY d.id DESC LIMIT ?, ?");
params.add((page - 1) * limit);
params.add(limit);
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), params.toArray());
CommonPage<Map<String, Object>> result = new CommonPage<>();
result.setList(list);
result.setTotal(total != null ? total.longValue() : 0L);
result.setPage(page);
result.setLimit(limit);
result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit));
return CommonResult.success(result);
} catch (Exception e) {
log.error("获取动态列表失败", e);
return CommonResult.failed("获取列表失败:" + e.getMessage());
}
}
/**
* 动态详情
*/
@ApiOperation(value = "动态详情")
@RequestMapping(value = "/detail/{id}", method = RequestMethod.GET)
public CommonResult<Map<String, Object>> getDetail(@PathVariable Integer id) {
try {
String sql = "SELECT d.*, u.avatar as user_avatar FROM eb_dynamic d " +
"LEFT JOIN eb_user u ON d.uid = u.uid WHERE d.id = ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, id);
if (list.isEmpty()) {
return CommonResult.failed("动态不存在");
}
return CommonResult.success(list.get(0));
} catch (Exception e) {
log.error("获取动态详情失败", e);
return CommonResult.failed("获取详情失败:" + e.getMessage());
}
}
/**
* 添加动态
*/
@ApiOperation(value = "添加动态")
@RequestMapping(value = "/add", method = RequestMethod.POST)
public CommonResult<String> add(@RequestBody Map<String, Object> params) {
try {
String sql = "INSERT INTO eb_dynamic (uid, nickname, avatar, content, images, location, " +
"category_id, status, create_time, update_time) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
jdbcTemplate.update(sql,
params.get("uid"),
params.get("nickname"),
params.get("avatar"),
params.get("content"),
params.get("images"),
params.get("location"),
params.get("categoryId"),
params.get("status") != null ? params.get("status") : 1
);
return CommonResult.success("添加成功");
} catch (Exception e) {
log.error("添加动态失败", e);
return CommonResult.failed("添加失败:" + e.getMessage());
}
}
/**
* 更新动态
*/
@ApiOperation(value = "更新动态")
@RequestMapping(value = "/update", method = RequestMethod.POST)
public CommonResult<String> update(@RequestBody Map<String, Object> params) {
try {
String sql = "UPDATE eb_dynamic SET content = ?, images = ?, location = ?, " +
"category_id = ?, status = ?, update_time = NOW() WHERE id = ?";
jdbcTemplate.update(sql,
params.get("content"),
params.get("images"),
params.get("location"),
params.get("categoryId"),
params.get("status"),
params.get("id")
);
return CommonResult.success("更新成功");
} catch (Exception e) {
log.error("更新动态失败", e);
return CommonResult.failed("更新失败:" + e.getMessage());
}
}
/**
* 删除动态
*/
@ApiOperation(value = "删除动态")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
public CommonResult<String> delete(@PathVariable Integer id) {
try {
String sql = "DELETE FROM eb_dynamic WHERE id = ?";
jdbcTemplate.update(sql, id);
return CommonResult.success("删除成功");
} catch (Exception e) {
log.error("删除动态失败", e);
return CommonResult.failed("删除失败:" + e.getMessage());
}
}
/**
* 审核动态
*/
@ApiOperation(value = "审核动态")
@RequestMapping(value = "/audit/{id}", method = RequestMethod.POST)
public CommonResult<String> audit(@PathVariable Integer id, @RequestBody Map<String, Object> params) {
try {
Integer auditStatus = (Integer) params.get("auditStatus");
String auditRemark = (String) params.get("auditRemark");
String sql = "UPDATE eb_dynamic SET status = ?, audit_remark = ?, update_time = NOW() WHERE id = ?";
jdbcTemplate.update(sql, auditStatus, auditRemark, id);
return CommonResult.success("审核成功");
} catch (Exception e) {
log.error("审核动态失败", e);
return CommonResult.failed("审核失败:" + e.getMessage());
}
}
/**
* 切换状态
*/
@ApiOperation(value = "切换状态")
@RequestMapping(value = "/status/{id}", method = RequestMethod.POST)
public CommonResult<String> changeStatus(@PathVariable Integer id, @RequestBody Map<String, Object> params) {
try {
Integer status = (Integer) params.get("status");
String sql = "UPDATE eb_dynamic SET status = ?, update_time = NOW() WHERE id = ?";
jdbcTemplate.update(sql, status, id);
return CommonResult.success("状态更新成功");
} catch (Exception e) {
log.error("更新状态失败", e);
return CommonResult.failed("状态更新失败:" + e.getMessage());
}
}
}

View File

@ -44,7 +44,7 @@ public class UserSignAdminController {
/** /**
* 签到配置列表 * 签到配置列表
*/ */
@PreAuthorize("hasAuthority('admin:sign:config:list')") // @PreAuthorize("hasAuthority('admin:sign:config:list')")
@ApiOperation(value = "签到配置列表") @ApiOperation(value = "签到配置列表")
@RequestMapping(value = "/config/list", method = RequestMethod.GET) @RequestMapping(value = "/config/list", method = RequestMethod.GET)
public CommonResult<List<SystemGroupDataSignConfigVo>> getConfigList() { public CommonResult<List<SystemGroupDataSignConfigVo>> getConfigList() {
@ -55,7 +55,7 @@ public class UserSignAdminController {
/** /**
* 签到配置更新 * 签到配置更新
*/ */
@PreAuthorize("hasAuthority('admin:sign:config:update')") // @PreAuthorize("hasAuthority('admin:sign:config:update')")
@ApiOperation(value = "签到配置更新") @ApiOperation(value = "签到配置更新")
@RequestMapping(value = "/config/update", method = RequestMethod.POST) @RequestMapping(value = "/config/update", method = RequestMethod.POST)
public CommonResult<String> updateConfig(@RequestBody @Validated SystemGroupDataRequest request) { public CommonResult<String> updateConfig(@RequestBody @Validated SystemGroupDataRequest request) {
@ -68,7 +68,7 @@ public class UserSignAdminController {
/** /**
* 用户签到记录列表 * 用户签到记录列表
*/ */
@PreAuthorize("hasAuthority('admin:sign:user:list')") // @PreAuthorize("hasAuthority('admin:sign:user:list')")
@ApiOperation(value = "用户签到记录列表") @ApiOperation(value = "用户签到记录列表")
@RequestMapping(value = "/user/list", method = RequestMethod.GET) @RequestMapping(value = "/user/list", method = RequestMethod.GET)
public CommonResult<CommonPage<UserSignAdminVo>> getUserSignList( public CommonResult<CommonPage<UserSignAdminVo>> getUserSignList(

View File

@ -446,4 +446,152 @@ public class FanGroupController {
log.warn("检查升级失败", e); log.warn("检查升级失败", e);
} }
} }
// ==================== 粉丝团聊天消息接口 ====================
/**
* 获取粉丝团消息列表
*/
@ApiOperation(value = "获取粉丝团消息")
@GetMapping("/{fanGroupId}/messages")
public CommonResult<CommonPage<Map<String, Object>>> getFanGroupMessages(
@PathVariable Integer fanGroupId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "30") Integer limit) {
try {
Integer userId = userService.getUserIdException();
// 检查是否是粉丝团成员或主播
String checkSql = "SELECT COUNT(*) FROM eb_fan_group_member WHERE group_id = ? AND uid = ? AND status = 1";
Integer memberCount = jdbcTemplate.queryForObject(checkSql, Integer.class, fanGroupId, userId);
String checkOwnerSql = "SELECT COUNT(*) FROM eb_fan_group WHERE id = ? AND anchor_id = ?";
Integer ownerCount = jdbcTemplate.queryForObject(checkOwnerSql, Integer.class, fanGroupId, userId);
if ((memberCount == null || memberCount == 0) && (ownerCount == null || ownerCount == 0)) {
return CommonResult.failed("您不是该粉丝团成员");
}
String countSql = "SELECT COUNT(*) FROM eb_group_message WHERE group_id = ? AND is_deleted = 0";
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, fanGroupId);
String sql = "SELECT gm.id, gm.group_id, gm.sender_id as senderId, " +
"u.nickname as senderName, u.avatar as senderAvatar, " +
"gm.content, gm.message_type as messageType, gm.create_time as createTime " +
"FROM eb_group_message gm " +
"LEFT JOIN eb_user u ON gm.sender_id = u.uid " +
"WHERE gm.group_id = ? AND gm.is_deleted = 0 " +
"ORDER BY gm.create_time DESC LIMIT ?, ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, fanGroupId, (page - 1) * limit, limit);
// 反转列表让最新消息在最后
java.util.Collections.reverse(list);
CommonPage<Map<String, Object>> result = new CommonPage<>();
result.setList(list);
result.setTotal(total != null ? total.longValue() : 0L);
result.setPage(page);
result.setLimit(limit);
return CommonResult.success(result);
} catch (Exception e) {
log.error("获取粉丝团消息失败", e);
return CommonResult.failed("获取消息失败:" + e.getMessage());
}
}
/**
* 发送粉丝团消息
*/
@ApiOperation(value = "发送粉丝团消息")
@PostMapping("/{fanGroupId}/messages")
public CommonResult<Map<String, Object>> sendFanGroupMessage(
@PathVariable Integer fanGroupId,
@RequestBody Map<String, Object> request) {
try {
Integer userId = userService.getUserIdException();
// 检查是否是粉丝团成员或主播
String checkSql = "SELECT COUNT(*) FROM eb_fan_group_member WHERE group_id = ? AND uid = ? AND status = 1";
Integer memberCount = jdbcTemplate.queryForObject(checkSql, Integer.class, fanGroupId, userId);
String checkOwnerSql = "SELECT COUNT(*) FROM eb_fan_group WHERE id = ? AND anchor_id = ?";
Integer ownerCount = jdbcTemplate.queryForObject(checkOwnerSql, Integer.class, fanGroupId, userId);
if ((memberCount == null || memberCount == 0) && (ownerCount == null || ownerCount == 0)) {
return CommonResult.failed("您不是该粉丝团成员,无法发送消息");
}
String content = (String) request.get("content");
String messageType = (String) request.getOrDefault("messageType", "text");
if (content == null || content.trim().isEmpty()) {
return CommonResult.failed("消息内容不能为空");
}
// 获取用户信息
String userSql = "SELECT nickname, avatar FROM eb_user WHERE uid = ?";
Map<String, Object> userInfo = jdbcTemplate.queryForMap(userSql, userId);
// 插入消息
String insertSql = "INSERT INTO eb_group_message (group_id, sender_id, content, message_type, create_time) VALUES (?, ?, ?, ?, NOW())";
jdbcTemplate.update(insertSql, fanGroupId, userId, content.trim(), messageType);
// 获取新插入的消息ID
Long messageId = jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
// 返回消息详情
Map<String, Object> result = new HashMap<>();
result.put("id", messageId);
result.put("senderId", userId);
result.put("senderName", userInfo.get("nickname"));
result.put("senderAvatar", userInfo.get("avatar"));
result.put("content", content.trim());
result.put("messageType", messageType);
result.put("createTime", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
return CommonResult.success(result);
} catch (Exception e) {
log.error("发送粉丝团消息失败", e);
return CommonResult.failed("发送失败:" + e.getMessage());
}
}
/**
* 删除粉丝团消息仅发送者或主播可删除
*/
@ApiOperation(value = "删除粉丝团消息")
@PostMapping("/{fanGroupId}/messages/{messageId}/delete")
public CommonResult<String> deleteFanGroupMessage(
@PathVariable Integer fanGroupId,
@PathVariable Long messageId) {
try {
Integer userId = userService.getUserIdException();
// 检查消息是否存在
String checkMsgSql = "SELECT sender_id FROM eb_group_message WHERE id = ? AND group_id = ?";
List<Map<String, Object>> msgList = jdbcTemplate.queryForList(checkMsgSql, messageId, fanGroupId);
if (msgList.isEmpty()) {
return CommonResult.failed("消息不存在");
}
Integer senderId = ((Number) msgList.get(0).get("sender_id")).intValue();
// 检查是否是发送者或主播
String checkOwnerSql = "SELECT anchor_id FROM eb_fan_group WHERE id = ?";
Integer anchorId = jdbcTemplate.queryForObject(checkOwnerSql, Integer.class, fanGroupId);
if (!userId.equals(senderId) && !userId.equals(anchorId)) {
return CommonResult.failed("无权删除此消息");
}
// 软删除消息
jdbcTemplate.update("UPDATE eb_group_message SET is_deleted = 1 WHERE id = ?", messageId);
return CommonResult.success("删除成功");
} catch (Exception e) {
log.error("删除粉丝团消息失败", e);
return CommonResult.failed("删除失败:" + e.getMessage());
}
}
} }

View File

@ -369,6 +369,12 @@ public class LiveRoomController {
try { try {
userId = frontTokenComponent.getUserId(); userId = frontTokenComponent.getUserId();
if (userId != null) { if (userId != null) {
// 检查用户是否被封禁
String banCheckResult = checkUserBanStatus(userId);
if (banCheckResult != null) {
return CommonResult.failed(banCheckResult);
}
// 已登录用户获取用户信息 // 已登录用户获取用户信息
com.zbkj.common.model.user.User user = userService.getById(userId); com.zbkj.common.model.user.User user = userService.getById(userId);
if (user != null) { if (user != null) {
@ -392,6 +398,37 @@ public class LiveRoomController {
return CommonResult.success(ChatMessageResponse.from(savedChat)); return CommonResult.success(ChatMessageResponse.from(savedChat));
} }
/**
* 检查用户封禁状态
* @return 如果被封禁返回封禁信息否则返回null
*/
private String checkUserBanStatus(Integer userId) {
try {
String sql = "SELECT ban_type, reason, expire_time FROM eb_user_ban " +
"WHERE user_id = ? AND status = 1 " +
"AND (expire_time IS NULL OR expire_time > NOW()) " +
"ORDER BY create_time DESC LIMIT 1";
List<Map<String, Object>> records = jdbcTemplate.queryForList(sql, userId);
if (!records.isEmpty()) {
Map<String, Object> banInfo = records.get(0);
String banType = (String) banInfo.get("ban_type");
String reason = banInfo.get("reason") != null ? banInfo.get("reason").toString() : "违规操作";
if ("permanent".equals(banType)) {
return "您的账号已被永久封禁,无法发送消息。原因:" + reason;
} else {
Object expireTime = banInfo.get("expire_time");
return "您的账号已被临时封禁,无法发送消息。解封时间:" + expireTime;
}
}
} catch (Exception e) {
// 封禁表可能不存在忽略错误
log.warn("检查用户封禁状态失败: {}", e.getMessage());
}
return null;
}
@ApiOperation(value = "公开:获取观看人数") @ApiOperation(value = "公开:获取观看人数")
@GetMapping("/public/rooms/{roomId}/viewers/count") @GetMapping("/public/rooms/{roomId}/viewers/count")
public CommonResult<Map<String, Object>> getViewerCount(@PathVariable Integer roomId) { public CommonResult<Map<String, Object>> getViewerCount(@PathVariable Integer roomId) {
@ -477,6 +514,12 @@ public class LiveRoomController {
return CommonResult.failed("请先登录"); return CommonResult.failed("请先登录");
} }
// 检查用户是否被封禁
String banCheckResult = checkUserBanStatus(currentUserId);
if (banCheckResult != null) {
return CommonResult.failed(banCheckResult);
}
if (roomId == null) { if (roomId == null) {
return CommonResult.failed("房间ID不能为空"); return CommonResult.failed("房间ID不能为空");
} }

View File

@ -0,0 +1,213 @@
package com.zbkj.front.controller;
import com.zbkj.common.page.CommonPage;
import com.zbkj.common.result.CommonResult;
import com.zbkj.service.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 举报功能控制器移动端API
* 提供用户举报功能
*/
@Slf4j
@RestController
@RequestMapping("api/front/report")
@Api(tags = "举报管理-移动端")
@Validated
public class ReportFrontController {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserService userService;
/**
* 提交举报
*/
@ApiOperation(value = "提交举报")
@PostMapping("/submit")
public CommonResult<Map<String, Object>> submitReport(@RequestBody Map<String, Object> body) {
Integer userId = userService.getUserId();
if (userId == null || userId == 0) {
return CommonResult.failed("请先登录");
}
// 获取参数
Integer targetType = body.get("targetType") != null ?
Integer.valueOf(body.get("targetType").toString()) : null;
Integer targetId = body.get("targetId") != null ?
Integer.valueOf(body.get("targetId").toString()) : null;
String targetName = body.get("targetName") != null ?
body.get("targetName").toString() : "";
Integer reasonType = body.get("reasonType") != null ?
Integer.valueOf(body.get("reasonType").toString()) : 1;
String reason = body.get("reason") != null ?
body.get("reason").toString() : "";
String images = body.get("images") != null ?
body.get("images").toString() : "[]";
// 参数验证
if (targetType == null || targetId == null) {
return CommonResult.failed("举报目标不能为空");
}
if (reason == null || reason.trim().isEmpty()) {
return CommonResult.failed("举报原因不能为空");
}
// 不能举报自己
if (targetType == 1 && targetId.equals(userId)) {
return CommonResult.failed("不能举报自己");
}
// 检查是否已举报过同一用户对同一目标24小时内只能举报一次
String checkSql = "SELECT COUNT(*) FROM eb_report WHERE uid = ? AND target_type = ? AND target_id = ? " +
"AND create_time > DATE_SUB(NOW(), INTERVAL 24 HOUR)";
Integer existCount = jdbcTemplate.queryForObject(checkSql, Integer.class, userId, targetType, targetId);
if (existCount != null && existCount > 0) {
return CommonResult.failed("您已举报过该内容,请勿重复举报");
}
// 获取举报人信息
String getUserSql = "SELECT nickname FROM eb_user WHERE uid = ?";
List<Map<String, Object>> users = jdbcTemplate.queryForList(getUserSql, userId);
String nickname = "";
if (!users.isEmpty() && users.get(0).get("nickname") != null) {
nickname = users.get(0).get("nickname").toString();
}
// 插入举报记录
String insertSql = "INSERT INTO eb_report (uid, nickname, target_type, target_id, target_name, " +
"reason_type, reason, images, status, create_time, update_time) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, NOW(), NOW())";
jdbcTemplate.update(insertSql, userId, nickname, targetType, targetId, targetName,
reasonType, reason, images);
log.info("【提交举报】userId={}, targetType={}, targetId={}, reason={}",
userId, targetType, targetId, reason);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "举报提交成功,我们会尽快处理");
return CommonResult.success(result);
}
/**
* 获取举报类型列表
*/
@ApiOperation(value = "获取举报类型列表")
@GetMapping("/types")
public CommonResult<List<Map<String, Object>>> getReportTypes() {
List<Map<String, Object>> types = new ArrayList<>();
// 举报目标类型
Map<String, Object> targetTypes = new HashMap<>();
targetTypes.put("name", "targetTypes");
targetTypes.put("label", "举报目标类型");
List<Map<String, Object>> targetTypeList = new ArrayList<>();
targetTypeList.add(createOption(1, "用户"));
targetTypeList.add(createOption(2, "直播间"));
targetTypeList.add(createOption(3, "动态"));
targetTypeList.add(createOption(4, "评论"));
targetTypes.put("options", targetTypeList);
types.add(targetTypes);
// 举报原因类型
Map<String, Object> reasonTypes = new HashMap<>();
reasonTypes.put("name", "reasonTypes");
reasonTypes.put("label", "举报原因类型");
List<Map<String, Object>> reasonTypeList = new ArrayList<>();
reasonTypeList.add(createOption(1, "色情低俗"));
reasonTypeList.add(createOption(2, "广告骚扰"));
reasonTypeList.add(createOption(3, "政治敏感"));
reasonTypeList.add(createOption(4, "违法违规"));
reasonTypeList.add(createOption(5, "人身攻击"));
reasonTypeList.add(createOption(6, "欺诈骗钱"));
reasonTypeList.add(createOption(7, "侵权抄袭"));
reasonTypeList.add(createOption(8, "其他"));
reasonTypes.put("options", reasonTypeList);
types.add(reasonTypes);
return CommonResult.success(types);
}
/**
* 获取我的举报记录
*/
@ApiOperation(value = "获取我的举报记录")
@GetMapping("/my/list")
public CommonResult<CommonPage<Map<String, Object>>> getMyReports(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize) {
Integer userId = userService.getUserId();
if (userId == null || userId == 0) {
return CommonResult.failed("请先登录");
}
int offset = (page - 1) * pageSize;
// 查询总数
String countSql = "SELECT COUNT(*) FROM eb_report WHERE uid = ?";
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
// 查询列表
String sql = "SELECT id, target_type as targetType, target_id as targetId, " +
"target_name as targetName, reason_type as reasonType, reason, " +
"status, result, create_time as createTime, handle_time as handleTime " +
"FROM eb_report WHERE uid = ? ORDER BY create_time DESC LIMIT ? OFFSET ?";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, pageSize, offset);
// 转换状态文本
for (Map<String, Object> item : list) {
Integer status = item.get("status") != null ? ((Number) item.get("status")).intValue() : 0;
String statusText;
switch (status) {
case 1: statusText = "处理中"; break;
case 2: statusText = "已处理"; break;
case 3: statusText = "已忽略"; break;
default: statusText = "待处理";
}
item.put("statusText", statusText);
// 转换目标类型文本
Integer targetType = item.get("targetType") != null ? ((Number) item.get("targetType")).intValue() : 0;
String targetTypeText;
switch (targetType) {
case 1: targetTypeText = "用户"; break;
case 2: targetTypeText = "直播间"; break;
case 3: targetTypeText = "动态"; break;
case 4: targetTypeText = "评论"; break;
default: targetTypeText = "未知";
}
item.put("targetTypeText", targetTypeText);
}
return CommonResult.success(buildPage(list, total, page, pageSize));
}
private Map<String, Object> createOption(int value, String label) {
Map<String, Object> option = new HashMap<>();
option.put("value", value);
option.put("label", label);
return option;
}
private CommonPage<Map<String, Object>> buildPage(List<Map<String, Object>> list, Integer total, Integer page, Integer pageSize) {
CommonPage<Map<String, Object>> result = new CommonPage<>();
result.setList(list);
result.setTotal(total != null ? total.longValue() : 0L);
result.setPage(page);
result.setLimit(pageSize);
result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / pageSize));
return result;
}
}

View File

@ -22,10 +22,13 @@ import com.zbkj.service.service.UserService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
/** /**
@ -59,6 +62,9 @@ public class LoginServiceImpl implements LoginService {
@Autowired @Autowired
private SystemConfigService systemConfigService; private SystemConfigService systemConfigService;
@Autowired
private JdbcTemplate jdbcTemplate;
/** /**
* 账号密码登录 * 账号密码登录
* *
@ -74,6 +80,9 @@ public class LoginServiceImpl implements LoginService {
throw new CrmebException("此账号被禁用"); throw new CrmebException("此账号被禁用");
} }
// 检查用户是否被封禁
checkUserBanStatus(user.getUid());
// 校验密码 // 校验密码
String password = CrmebUtil.encryptPassword(loginRequest.getPassword(), loginRequest.getPhone()); String password = CrmebUtil.encryptPassword(loginRequest.getPassword(), loginRequest.getPhone());
if (!user.getPwd().equals(password)) { if (!user.getPwd().equals(password)) {
@ -114,6 +123,10 @@ public class LoginServiceImpl implements LoginService {
if (!user.getStatus()) { if (!user.getStatus()) {
throw new CrmebException("当前账户已禁用,请联系管理员!"); throw new CrmebException("当前账户已禁用,请联系管理员!");
} }
// 检查用户是否被封禁
checkUserBanStatus(user.getUid());
if (user.getSpreadUid().equals(0) && spreadPid > 0) { if (user.getSpreadUid().equals(0) && spreadPid > 0) {
// 绑定推广关系 // 绑定推广关系
bindSpread(user, spreadPid); bindSpread(user, spreadPid);
@ -270,4 +283,38 @@ public class LoginServiceImpl implements LoginService {
return loginResponse; return loginResponse;
} }
/**
* 检查用户封禁状态
* 如果用户被封禁抛出异常阻止登录
*/
private void checkUserBanStatus(Integer userId) {
try {
String sql = "SELECT id, ban_type, reason, expire_time, create_time " +
"FROM eb_user_ban WHERE user_id = ? AND status = 1 " +
"AND (expire_time IS NULL OR expire_time > NOW()) " +
"ORDER BY create_time DESC LIMIT 1";
List<Map<String, Object>> records = jdbcTemplate.queryForList(sql, userId);
if (!records.isEmpty()) {
Map<String, Object> banInfo = records.get(0);
String banType = (String) banInfo.get("ban_type");
String reason = banInfo.get("reason") != null ? banInfo.get("reason").toString() : "违规操作";
Object expireTime = banInfo.get("expire_time");
String message;
if ("permanent".equals(banType)) {
message = "您的账号已被永久封禁,原因:" + reason;
} else {
message = "您的账号已被临时封禁,解封时间:" + expireTime + ",原因:" + reason;
}
throw new CrmebException(message);
}
} catch (CrmebException e) {
throw e;
} catch (Exception e) {
// 封禁表可能不存在忽略错误继续登录
logger.warn("检查用户封禁状态失败: {}", e.getMessage());
}
}
} }

View File

@ -17,12 +17,14 @@ import com.zbkj.service.service.ConversationService;
import com.zbkj.service.service.OnlineStatusService; import com.zbkj.service.service.OnlineStatusService;
import com.zbkj.service.service.UserService; import com.zbkj.service.service.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -43,6 +45,9 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
@Autowired @Autowired
private UserBlacklistDao userBlacklistDao; private UserBlacklistDao userBlacklistDao;
@Autowired
private JdbcTemplate jdbcTemplate;
@Override @Override
public List<ConversationResponse> getConversationList(Integer userId) { public List<ConversationResponse> getConversationList(Integer userId) {
LambdaQueryWrapper<Conversation> qw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Conversation> qw = new LambdaQueryWrapper<>();
@ -181,12 +186,8 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
? conversation.getUser2Id() ? conversation.getUser2Id()
: conversation.getUser1Id(); : conversation.getUser1Id();
// 检查黑名单双向检查任何一方拉黑都不能发送消息 // 检查黑名单状态
Long blockedByMe = userBlacklistDao.checkBlacklist(userId, receiverId); checkBlacklistStatus(userId, receiverId);
Long blockedByOther = userBlacklistDao.checkBlacklist(receiverId, userId);
if ((blockedByMe != null && blockedByMe > 0) || (blockedByOther != null && blockedByOther > 0)) {
throw new CrmebException("无法发送消息,对方已被拉黑或您已被对方拉黑");
}
// 获取消息内容兼容新旧字段 // 获取消息内容兼容新旧字段
String content = request.getContent() != null ? request.getContent() : request.getMessage(); String content = request.getContent() != null ? request.getContent() : request.getMessage();
@ -264,6 +265,9 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
throw new CrmebException("不能与自己创建会话"); throw new CrmebException("不能与自己创建会话");
} }
// 检查黑名单状态
checkBlacklistStatus(userId, otherUserId);
// 查找现有会话无论谁是user1还是user2 // 查找现有会话无论谁是user1还是user2
// 使用 list() 而不是 getOne()因为可能存在重复数据 // 使用 list() 而不是 getOne()因为可能存在重复数据
LambdaQueryWrapper<Conversation> qw = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Conversation> qw = new LambdaQueryWrapper<>();
@ -594,4 +598,31 @@ public class ConversationServiceImpl extends ServiceImpl<ConversationDao, Conver
update(uw); update(uw);
return message; return message;
} }
/**
* 检查黑名单状态
* 如果任一方拉黑了对方则抛出异常
*/
private void checkBlacklistStatus(Integer userId, Integer otherUserId) {
try {
// 检查我是否拉黑了对方
String sql1 = "SELECT id FROM eb_user_blacklist WHERE user_id = ? AND blocked_user_id = ?";
List<Map<String, Object>> myBlacklist = jdbcTemplate.queryForList(sql1, userId, otherUserId);
if (!myBlacklist.isEmpty()) {
throw new CrmebException("您已将对方加入黑名单,无法发送消息");
}
// 检查对方是否拉黑了我
String sql2 = "SELECT id FROM eb_user_blacklist WHERE user_id = ? AND blocked_user_id = ?";
List<Map<String, Object>> theirBlacklist = jdbcTemplate.queryForList(sql2, otherUserId, userId);
if (!theirBlacklist.isEmpty()) {
throw new CrmebException("对方已将您加入黑名单,无法发送消息");
}
} catch (CrmebException e) {
throw e;
} catch (Exception e) {
// 黑名单表可能不存在忽略错误继续执行
// log.warn("检查黑名单状态失败: {}", e.getMessage());
}
}
} }

19
add_dynamic_test_data.sql Normal file
View File

@ -0,0 +1,19 @@
-- 添加社会动态测试数据
-- 先检查表是否存在
SELECT COUNT(*) as count FROM eb_dynamic;
-- 插入测试数据(使用已有用户)
INSERT INTO `eb_dynamic` (`uid`, `nickname`, `avatar`, `content`, `images`, `location`, `like_count`, `comment_count`, `share_count`, `view_count`, `is_top`, `status`, `create_time`) VALUES
(41, '夏至已至', 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKq2CRmib1mpu4hOFYtPNLkpU5ILwYTYsvVYB0WYQ0hL4sKAGicicO0OicYCT1/132', '今天天气真好,出来直播啦!大家快来看我~', '["https://picsum.photos/400/300?random=101"]', '北京市', 128, 32, 8, 560, 0, 1, NOW() - INTERVAL 2 HOUR),
(42, '测试用户', '', '分享一下我的日常生活,希望大家喜欢❤️', '["https://picsum.photos/400/300?random=102","https://picsum.photos/400/300?random=103"]', '上海市', 256, 45, 12, 890, 0, 1, NOW() - INTERVAL 5 HOUR),
(43, 'xiaofeng', '', '新的一天新的开始今晚8点准时开播不见不散~', '["https://picsum.photos/400/300?random=104"]', '广州市', 89, 18, 5, 320, 0, 1, NOW() - INTERVAL 1 DAY),
(100, '吉惟同学', 'https://img.zcool.cn/community/01a9a65d143edaa8012187f447cfef.jpg', '周末愉快!有没有想一起玩游戏的小伙伴?', '[]', '深圳市', 167, 28, 9, 450, 1, 1, NOW() - INTERVAL 3 HOUR),
(101, '国瑞哥', 'https://img.zcool.cn/community/01b72057a7e0790000018c1bf4fce0.png', '感谢大家的支持,粉丝破万啦!撒花🎉', '["https://picsum.photos/400/300?random=105","https://picsum.photos/400/300?random=106","https://picsum.photos/400/300?random=107"]', '成都市', 512, 89, 35, 1580, 0, 1, NOW() - INTERVAL 6 HOUR),
(102, '玖书小姐姐', '', '今天学了一首新歌,晚上直播间唱给大家听~', '["https://picsum.photos/400/300?random=108"]', '杭州市', 78, 15, 3, 210, 0, 1, NOW() - INTERVAL 8 HOUR);
-- 查看插入的数据
SELECT d.*, u.avatar as user_avatar
FROM eb_dynamic d
LEFT JOIN eb_user u ON d.uid = u.uid
ORDER BY d.id DESC
LIMIT 10;

View File

@ -71,8 +71,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_21
} }
buildFeatures { buildFeatures {

View File

@ -297,6 +297,12 @@
android:exported="false" android:exported="false"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<!-- 粉丝团详情Activity粉丝视角 -->
<activity
android:name="com.example.livestreaming.FanGroupDetailActivity"
android:exported="false"
android:screenOrientation="portrait" />
<!-- 手机开播Activity --> <!-- 手机开播Activity -->
<activity <activity
android:name="com.example.livestreaming.BroadcastActivity" android:name="com.example.livestreaming.BroadcastActivity"

View File

@ -0,0 +1,322 @@
package com.example.livestreaming;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.livestreaming.databinding.ActivityFanGroupDetailBinding;
import com.example.livestreaming.net.ApiClient;
import com.example.livestreaming.net.ApiResponse;
import com.example.livestreaming.net.PageResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* 粉丝团详情页面粉丝视角
* 用于查看主播的粉丝团信息并加入
*/
public class FanGroupDetailActivity extends AppCompatActivity {
private ActivityFanGroupDetailBinding binding;
private int fanGroupId;
private int streamerId;
private Map<String, Object> fanGroupData;
private MemberAdapter memberAdapter;
private boolean isJoined = false;
public static void start(Context context, int streamerId) {
Intent intent = new Intent(context, FanGroupDetailActivity.class);
intent.putExtra("streamerId", streamerId);
context.startActivity(intent);
}
public static void startWithGroupId(Context context, int fanGroupId) {
Intent intent = new Intent(context, FanGroupDetailActivity.class);
intent.putExtra("fanGroupId", fanGroupId);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityFanGroupDetailBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
fanGroupId = getIntent().getIntExtra("fanGroupId", 0);
streamerId = getIntent().getIntExtra("streamerId", 0);
setupToolbar();
setupRecyclerView();
loadFanGroupData();
}
private void setupToolbar() {
binding.toolbar.setNavigationOnClickListener(v -> finish());
}
private void setupRecyclerView() {
memberAdapter = new MemberAdapter();
binding.rvMembers.setLayoutManager(new LinearLayoutManager(this));
binding.rvMembers.setAdapter(memberAdapter);
}
private void loadFanGroupData() {
binding.progressBar.setVisibility(View.VISIBLE);
Call<ApiResponse<Map<String, Object>>> call;
if (fanGroupId > 0) {
// 直接通过粉丝团ID获取暂时用主播接口
call = ApiClient.getService(this).getStreamerFanGroup(fanGroupId);
} else {
call = ApiClient.getService(this).getStreamerFanGroup(streamerId);
}
call.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
binding.progressBar.setVisibility(View.GONE);
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
fanGroupData = response.body().getData();
if (fanGroupData != null && !fanGroupData.isEmpty() && fanGroupData.containsKey("id")) {
fanGroupId = ((Number) fanGroupData.get("id")).intValue();
showFanGroupInfo();
checkJoinStatus();
loadMembers();
} else {
showNoFanGroup();
}
} else {
showNoFanGroup();
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
binding.progressBar.setVisibility(View.GONE);
Toast.makeText(FanGroupDetailActivity.this, "加载失败", Toast.LENGTH_SHORT).show();
}
});
}
private void showFanGroupInfo() {
binding.layoutFanGroupInfo.setVisibility(View.VISIBLE);
binding.layoutNoFanGroup.setVisibility(View.GONE);
String name = (String) fanGroupData.get("name");
String badge = (String) fanGroupData.get("badge");
String anchorName = (String) fanGroupData.get("anchor_name");
Object memberCountObj = fanGroupData.get("member_count");
int memberCount = memberCountObj != null ? ((Number) memberCountObj).intValue() : 0;
binding.tvFanGroupName.setText(name);
binding.tvBadge.setText(badge);
binding.tvAnchorName.setText("主播:" + anchorName);
binding.tvMemberCount.setText(memberCount + " 位成员");
binding.btnJoin.setOnClickListener(v -> {
if (isJoined) {
showLeaveConfirm();
} else {
joinFanGroup();
}
});
}
private void showNoFanGroup() {
binding.layoutFanGroupInfo.setVisibility(View.GONE);
binding.layoutNoFanGroup.setVisibility(View.VISIBLE);
}
private void checkJoinStatus() {
ApiClient.getService(this).checkFanGroupJoined(fanGroupId)
.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
Map<String, Object> data = response.body().getData();
isJoined = data != null && Boolean.TRUE.equals(data.get("joined"));
updateJoinButton();
if (isJoined && data != null) {
Object levelObj = data.get("level");
Object intimacyObj = data.get("intimacy");
if (levelObj != null) {
binding.tvMyLevel.setVisibility(View.VISIBLE);
binding.tvMyLevel.setText("我的等级Lv." + levelObj);
}
if (intimacyObj != null) {
binding.tvMyIntimacy.setVisibility(View.VISIBLE);
binding.tvMyIntimacy.setText("亲密度:" + intimacyObj);
}
}
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
// 忽略错误
}
});
}
private void updateJoinButton() {
if (isJoined) {
binding.btnJoin.setText("退出粉丝团");
binding.btnJoin.setBackgroundColor(getResources().getColor(android.R.color.darker_gray));
} else {
binding.btnJoin.setText("加入粉丝团");
binding.btnJoin.setBackgroundColor(getResources().getColor(android.R.color.holo_red_light));
}
}
private void joinFanGroup() {
binding.btnJoin.setEnabled(false);
ApiClient.getService(this).joinFanGroup(fanGroupId)
.enqueue(new Callback<ApiResponse<String>>() {
@Override
public void onResponse(Call<ApiResponse<String>> call,
Response<ApiResponse<String>> response) {
binding.btnJoin.setEnabled(true);
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
Toast.makeText(FanGroupDetailActivity.this, "加入成功!", Toast.LENGTH_SHORT).show();
isJoined = true;
updateJoinButton();
loadFanGroupData(); // 刷新数据
} else {
String msg = response.body() != null ? response.body().getMessage() : "加入失败";
Toast.makeText(FanGroupDetailActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<String>> call, Throwable t) {
binding.btnJoin.setEnabled(true);
Toast.makeText(FanGroupDetailActivity.this, "加入失败", Toast.LENGTH_SHORT).show();
}
});
}
private void showLeaveConfirm() {
new AlertDialog.Builder(this)
.setTitle("退出粉丝团")
.setMessage("确定要退出该粉丝团吗?退出后亲密度将清零。")
.setPositiveButton("确定退出", (dialog, which) -> leaveFanGroup())
.setNegativeButton("取消", null)
.show();
}
private void leaveFanGroup() {
binding.btnJoin.setEnabled(false);
ApiClient.getService(this).leaveFanGroup(fanGroupId)
.enqueue(new Callback<ApiResponse<String>>() {
@Override
public void onResponse(Call<ApiResponse<String>> call,
Response<ApiResponse<String>> response) {
binding.btnJoin.setEnabled(true);
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
Toast.makeText(FanGroupDetailActivity.this, "已退出", Toast.LENGTH_SHORT).show();
isJoined = false;
updateJoinButton();
binding.tvMyLevel.setVisibility(View.GONE);
binding.tvMyIntimacy.setVisibility(View.GONE);
loadFanGroupData();
} else {
Toast.makeText(FanGroupDetailActivity.this, "退出失败", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<String>> call, Throwable t) {
binding.btnJoin.setEnabled(true);
Toast.makeText(FanGroupDetailActivity.this, "退出失败", Toast.LENGTH_SHORT).show();
}
});
}
private void loadMembers() {
ApiClient.getService(this).getFanGroupMembers(fanGroupId, 1, 10)
.enqueue(new Callback<ApiResponse<PageResponse<Map<String, Object>>>>() {
@Override
public void onResponse(Call<ApiResponse<PageResponse<Map<String, Object>>>> call,
Response<ApiResponse<PageResponse<Map<String, Object>>>> response) {
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
PageResponse<Map<String, Object>> page = response.body().getData();
if (page != null && page.getList() != null) {
memberAdapter.setData(page.getList());
}
}
}
@Override
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
// 忽略
}
});
}
// 成员列表适配器
private static class MemberAdapter extends RecyclerView.Adapter<MemberAdapter.ViewHolder> {
private List<Map<String, Object>> data = new ArrayList<>();
public void setData(List<Map<String, Object>> data) {
this.data = data != null ? data : new ArrayList<>();
notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_fan_group_member, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Map<String, Object> item = data.get(position);
holder.tvNickname.setText((String) item.get("nickname"));
Object levelObj = item.get("level");
holder.tvLevel.setText("Lv." + (levelObj != null ? levelObj : 1));
Object intimacyObj = item.get("intimacy");
holder.tvIntimacy.setText("亲密度:" + (intimacyObj != null ? intimacyObj : 0));
}
@Override
public int getItemCount() {
return data.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView tvNickname, tvLevel, tvIntimacy;
ViewHolder(View itemView) {
super(itemView);
tvNickname = itemView.findViewById(R.id.tvNickname);
tvLevel = itemView.findViewById(R.id.tvLevel);
tvIntimacy = itemView.findViewById(R.id.tvIntimacy);
}
}
}
}

View File

@ -182,7 +182,16 @@ public class GroupChatActivity extends AppCompatActivity {
} }
ApiService apiService = ApiClient.getService(this); ApiService apiService = ApiClient.getService(this);
apiService.getGroupMessages(groupId, currentPage, 30).enqueue(new Callback<ApiResponse<PageResponse<GroupMessageResponse>>>() {
// 根据聊天类型调用不同的接口
Call<ApiResponse<PageResponse<GroupMessageResponse>>> call;
if ("fangroup".equals(chatType)) {
call = apiService.getFanGroupMessages((int) groupId, currentPage, 30);
} else {
call = apiService.getGroupMessages(groupId, currentPage, 30);
}
call.enqueue(new Callback<ApiResponse<PageResponse<GroupMessageResponse>>>() {
@Override @Override
public void onResponse(Call<ApiResponse<PageResponse<GroupMessageResponse>>> call, public void onResponse(Call<ApiResponse<PageResponse<GroupMessageResponse>>> call,
Response<ApiResponse<PageResponse<GroupMessageResponse>>> response) { Response<ApiResponse<PageResponse<GroupMessageResponse>>> response) {
@ -263,7 +272,16 @@ public class GroupChatActivity extends AppCompatActivity {
body.put("messageType", "text"); body.put("messageType", "text");
ApiService apiService = ApiClient.getService(this); ApiService apiService = ApiClient.getService(this);
apiService.sendGroupMessage(groupId, body).enqueue(new Callback<ApiResponse<GroupMessageResponse>>() {
// 根据聊天类型调用不同的发送消息接口
Call<ApiResponse<GroupMessageResponse>> sendCall;
if ("fangroup".equals(chatType)) {
sendCall = apiService.sendFanGroupMessage((int) groupId, body);
} else {
sendCall = apiService.sendGroupMessage(groupId, body);
}
sendCall.enqueue(new Callback<ApiResponse<GroupMessageResponse>>() {
@Override @Override
public void onResponse(Call<ApiResponse<GroupMessageResponse>> call, public void onResponse(Call<ApiResponse<GroupMessageResponse>> call,
Response<ApiResponse<GroupMessageResponse>> response) { Response<ApiResponse<GroupMessageResponse>> response) {
@ -334,7 +352,16 @@ public class GroupChatActivity extends AppCompatActivity {
private void refreshNewMessages() { private void refreshNewMessages() {
// 只刷新第一页获取新消息 // 只刷新第一页获取新消息
ApiService apiService = ApiClient.getService(this); ApiService apiService = ApiClient.getService(this);
apiService.getGroupMessages(groupId, 1, 30).enqueue(new Callback<ApiResponse<PageResponse<GroupMessageResponse>>>() {
// 根据聊天类型调用不同的接口
Call<ApiResponse<PageResponse<GroupMessageResponse>>> call;
if ("fangroup".equals(chatType)) {
call = apiService.getFanGroupMessages((int) groupId, 1, 30);
} else {
call = apiService.getGroupMessages(groupId, 1, 30);
}
call.enqueue(new Callback<ApiResponse<PageResponse<GroupMessageResponse>>>() {
@Override @Override
public void onResponse(Call<ApiResponse<PageResponse<GroupMessageResponse>>> call, public void onResponse(Call<ApiResponse<PageResponse<GroupMessageResponse>>> call,
Response<ApiResponse<PageResponse<GroupMessageResponse>>> response) { Response<ApiResponse<PageResponse<GroupMessageResponse>>> response) {

View File

@ -141,13 +141,9 @@ public class MessagesActivity extends AppCompatActivity {
if (item.isFanGroup()) { if (item.isFanGroup()) {
// 粉丝团跳转到粉丝团群聊页面 // 粉丝团跳转到粉丝团群聊页面
long fanGroupId = item.getFanGroupId(); long fanGroupId = item.getFanGroupId();
long groupId = item.getGroupId();
// 优先使用关联的群组ID如果没有则使用粉丝团ID // 粉丝团聊天直接使用粉丝团ID
long chatId = groupId > 0 ? groupId : fanGroupId; GroupChatActivity.startFanGroup(this, fanGroupId, "fangroup",
String chatType = groupId > 0 ? "group" : "fangroup";
GroupChatActivity.startFanGroup(this, chatId, chatType,
item.getTitle().replace("🎀 ", ""), item.getTitle().replace("🎀 ", ""),
item.getAvatarUrl()); item.getAvatarUrl());
} else if (item.isGroup()) { } else if (item.isGroup()) {

View File

@ -362,7 +362,7 @@ public class ProfileActivity extends AppCompatActivity {
} }
MyRecordsActivity.start(this); MyRecordsActivity.start(this);
}); });
binding.topActionMore.setOnClickListener(v -> TabPlaceholderActivity.start(this, "更多")); binding.topActionMore.setOnClickListener(v -> showMoreOptionsMenu());
binding.copyIdBtn.setOnClickListener(v -> { binding.copyIdBtn.setOnClickListener(v -> {
String idText = binding.idLine.getText() != null ? binding.idLine.getText().toString() : ""; String idText = binding.idLine.getText() != null ? binding.idLine.getText().toString() : "";
@ -849,6 +849,35 @@ public class ProfileActivity extends AppCompatActivity {
} }
} }
/**
* 显示更多选项菜单
*/
private void showMoreOptionsMenu() {
String[] options = {"黑名单管理", "设置", "关于"};
new androidx.appcompat.app.AlertDialog.Builder(this)
.setItems(options, (dialog, which) -> {
switch (which) {
case 0:
// 黑名单管理
if (!AuthHelper.requireLogin(this, "查看黑名单需要登录")) {
return;
}
BlacklistActivity.start(this);
break;
case 1:
// 设置
TabPlaceholderActivity.start(this, "设置");
break;
case 2:
// 关于
TabPlaceholderActivity.start(this, "关于");
break;
}
})
.show();
}
/** /**
* 显示分享个人主页对话框 * 显示分享个人主页对话框
*/ */

View File

@ -2587,6 +2587,8 @@ public class RoomDetailActivity extends AppCompatActivity implements SurfaceHold
return; return;
} }
// 先检查房间是否被封禁
checkRoomBanStatus(() -> {
ApiService apiService = ApiClient.getService(getApplicationContext()); ApiService apiService = ApiClient.getService(getApplicationContext());
Call<ApiResponse<Room>> call = apiService.getRoom(roomId); Call<ApiResponse<Room>> call = apiService.getRoom(roomId);
@ -2615,6 +2617,69 @@ public class RoomDetailActivity extends AppCompatActivity implements SurfaceHold
android.util.Log.e("RoomDetail", "加载房间信息网络错误", t); android.util.Log.e("RoomDetail", "加载房间信息网络错误", t);
} }
}); });
});
}
/**
* 检查房间封禁状态
*/
private void checkRoomBanStatus(Runnable onSuccess) {
try {
int roomIdInt = Integer.parseInt(roomId);
ApiClient.getService(getApplicationContext()).checkRoomBanStatus(roomIdInt)
.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
if (response.isSuccessful() && response.body() != null &&
response.body().getCode() == 200) {
Map<String, Object> data = response.body().getData();
if (data != null) {
Object isBannedObj = data.get("isBanned");
boolean isBanned = isBannedObj instanceof Boolean && (Boolean) isBannedObj;
if (isBanned) {
// 房间被封禁显示提示并退出
String message = data.get("message") != null ?
data.get("message").toString() : "该直播间已被封禁";
showBanDialog(message);
return;
}
}
}
// 房间未被封禁继续加载
if (onSuccess != null) {
onSuccess.run();
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
android.util.Log.e("RoomDetail", "检查房间封禁状态失败", t);
// 检查失败时继续加载不阻塞用户
if (onSuccess != null) {
onSuccess.run();
}
}
});
} catch (NumberFormatException e) {
// roomId不是数字跳过检查
if (onSuccess != null) {
onSuccess.run();
}
}
}
/**
* 显示封禁提示对话框
*/
private void showBanDialog(String message) {
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("直播间已封禁")
.setMessage(message)
.setCancelable(false)
.setPositiveButton("确定", (dialog, which) -> finish())
.show();
} }
/** /**

View File

@ -7,6 +7,7 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
@ -38,6 +39,7 @@ public class UserProfileActivity extends AppCompatActivity {
private boolean isStreamer = false; private boolean isStreamer = false;
private boolean isFollowing = false; private boolean isFollowing = false;
private boolean hasJoinedFanGroup = false; private boolean hasJoinedFanGroup = false;
private boolean isBlocked = false; // 是否已拉黑
public static void start(Context context, int userId) { public static void start(Context context, int userId) {
Intent intent = new Intent(context, UserProfileActivity.class); Intent intent = new Intent(context, UserProfileActivity.class);
@ -65,10 +67,7 @@ public class UserProfileActivity extends AppCompatActivity {
private void setupClickListeners() { private void setupClickListeners() {
binding.backButton.setOnClickListener(v -> finish()); binding.backButton.setOnClickListener(v -> finish());
binding.moreButton.setOnClickListener(v -> { binding.moreButton.setOnClickListener(v -> showMoreOptions());
// TODO: 显示更多选项举报拉黑等
Toast.makeText(this, "更多选项", Toast.LENGTH_SHORT).show();
});
binding.followButton.setOnClickListener(v -> handleFollowClick()); binding.followButton.setOnClickListener(v -> handleFollowClick());
@ -377,6 +376,142 @@ public class UserProfileActivity extends AppCompatActivity {
} }
} }
/**
* 显示更多选项菜单
*/
private void showMoreOptions() {
if (!AuthHelper.requireLogin(this, "请先登录")) {
return;
}
// 先检查黑名单状态
checkBlacklistStatus(() -> {
String[] options = isBlocked ?
new String[]{"取消拉黑", "举报"} :
new String[]{"拉黑", "举报"};
new AlertDialog.Builder(this)
.setItems(options, (dialog, which) -> {
if (which == 0) {
// 拉黑/取消拉黑
if (isBlocked) {
removeFromBlacklist();
} else {
addToBlacklist();
}
} else if (which == 1) {
// 举报
Toast.makeText(this, "举报功能开发中", Toast.LENGTH_SHORT).show();
}
})
.show();
});
}
/**
* 检查黑名单状态
*/
private void checkBlacklistStatus(Runnable onComplete) {
ApiClient.getService(this).checkBlacklistStatus(userId)
.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
if (response.isSuccessful() && response.body() != null &&
response.body().getCode() == 200) {
Map<String, Object> data = response.body().getData();
if (data != null) {
isBlocked = getBool(data.get("isBlocked"), false);
}
}
if (onComplete != null) {
onComplete.run();
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
Log.e(TAG, "检查黑名单状态失败", t);
if (onComplete != null) {
onComplete.run();
}
}
});
}
/**
* 添加到黑名单
*/
private void addToBlacklist() {
String nickname = getString(profileData != null ? profileData.get("nickname") : null, "该用户");
new AlertDialog.Builder(this)
.setTitle("拉黑用户")
.setMessage("确定要将 " + nickname + " 加入黑名单吗?\n\n拉黑后\n• 对方无法给你发送私信\n• 对方无法评论你的内容\n• 你们将互相看不到对方的动态")
.setPositiveButton("确定", (dialog, which) -> {
Map<String, Object> body = new HashMap<>();
body.put("targetUserId", userId);
ApiClient.getService(this).addToBlacklist(body)
.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
if (response.isSuccessful() && response.body() != null &&
response.body().getCode() == 200) {
isBlocked = true;
Toast.makeText(UserProfileActivity.this,
"已将该用户加入黑名单", Toast.LENGTH_SHORT).show();
} else {
String msg = response.body() != null ?
response.body().getMessage() : "操作失败";
Toast.makeText(UserProfileActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
Toast.makeText(UserProfileActivity.this,
"网络错误", Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton("取消", null)
.show();
}
/**
* 从黑名单移除
*/
private void removeFromBlacklist() {
Map<String, Object> body = new HashMap<>();
body.put("targetUserId", userId);
ApiClient.getService(this).removeFromBlacklist(body)
.enqueue(new Callback<ApiResponse<Map<String, Object>>>() {
@Override
public void onResponse(Call<ApiResponse<Map<String, Object>>> call,
Response<ApiResponse<Map<String, Object>>> response) {
if (response.isSuccessful() && response.body() != null &&
response.body().getCode() == 200) {
isBlocked = false;
Toast.makeText(UserProfileActivity.this,
"已将该用户从黑名单移除", Toast.LENGTH_SHORT).show();
} else {
String msg = response.body() != null ?
response.body().getMessage() : "操作失败";
Toast.makeText(UserProfileActivity.this, msg, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<ApiResponse<Map<String, Object>>> call, Throwable t) {
Toast.makeText(UserProfileActivity.this,
"网络错误", Toast.LENGTH_SHORT).show();
}
});
}
// 工具方法 // 工具方法
private String getString(Object obj, String defaultValue) { private String getString(Object obj, String defaultValue) {
if (obj == null) return defaultValue; if (obj == null) return defaultValue;

View File

@ -785,6 +785,31 @@ public interface ApiService {
@GET("api/front/fan-group/all") @GET("api/front/fan-group/all")
Call<ApiResponse<List<Map<String, Object>>>> getAllMyFanGroups(); Call<ApiResponse<List<Map<String, Object>>>> getAllMyFanGroups();
/**
* 获取粉丝团消息列表
*/
@GET("api/front/fan-group/{fanGroupId}/messages")
Call<ApiResponse<PageResponse<GroupMessageResponse>>> getFanGroupMessages(
@Path("fanGroupId") int fanGroupId,
@Query("page") int page,
@Query("limit") int limit);
/**
* 发送粉丝团消息
*/
@POST("api/front/fan-group/{fanGroupId}/messages")
Call<ApiResponse<GroupMessageResponse>> sendFanGroupMessage(
@Path("fanGroupId") int fanGroupId,
@Body Map<String, Object> body);
/**
* 删除粉丝团消息
*/
@POST("api/front/fan-group/{fanGroupId}/messages/{messageId}/delete")
Call<ApiResponse<String>> deleteFanGroupMessage(
@Path("fanGroupId") int fanGroupId,
@Path("messageId") long messageId);
// ==================== 用户活动记录 ==================== // ==================== 用户活动记录 ====================
/** /**
@ -847,33 +872,51 @@ public interface ApiService {
@GET("api/front/activity/debug/token") @GET("api/front/activity/debug/token")
Call<ApiResponse<Map<String, Object>>> debugToken(); Call<ApiResponse<Map<String, Object>>> debugToken();
// ==================== 黑名单接口 ==================== // ==================== 封禁/黑名单接口 ====================
/** /**
* 获取我的黑名单 * 检查当前用户封禁状态
*/ */
@GET("api/front/blacklist/list") @GET("api/front/ban/check/me")
Call<ApiResponse<Map<String, Object>>> checkMyBanStatus();
/**
* 检查指定用户封禁状态
*/
@GET("api/front/ban/check/user/{userId}")
Call<ApiResponse<Map<String, Object>>> checkUserBanStatus(@Path("userId") int userId);
/**
* 检查房间封禁状态
*/
@GET("api/front/ban/check/room/{roomId}")
Call<ApiResponse<Map<String, Object>>> checkRoomBanStatus(@Path("roomId") int roomId);
/**
* 获取我的黑名单列表
*/
@GET("api/front/ban/blacklist/list")
Call<ApiResponse<PageResponse<Map<String, Object>>>> getMyBlacklist( Call<ApiResponse<PageResponse<Map<String, Object>>>> getMyBlacklist(
@Query("page") int page, @Query("page") int page,
@Query("pageSize") int pageSize); @Query("pageSize") int pageSize);
/** /**
* 添加到黑名单 * 添加用户到黑名单
*/ */
@POST("api/front/blacklist/add") @POST("api/front/ban/blacklist/add")
Call<ApiResponse<String>> addToBlacklist(@Body Map<String, Object> body); Call<ApiResponse<Map<String, Object>>> addToBlacklist(@Body Map<String, Object> body);
/** /**
* 从黑名单移除 * 从黑名单移除用户
*/ */
@POST("api/front/blacklist/remove") @POST("api/front/ban/blacklist/remove")
Call<ApiResponse<Map<String, Object>>> removeFromBlacklist(@Body Map<String, Object> body); Call<ApiResponse<Map<String, Object>>> removeFromBlacklist(@Body Map<String, Object> body);
/** /**
* 检查是否在黑名单中 * 检查用户是否在黑名单中
*/ */
@GET("api/front/blacklist/check/{userId}") @GET("api/front/ban/blacklist/check/{targetUserId}")
Call<ApiResponse<Map<String, Object>>> checkBlacklist(@Path("userId") int userId); Call<ApiResponse<Map<String, Object>>> checkBlacklistStatus(@Path("targetUserId") int targetUserId);
// ==================== 直播分类 ==================== // ==================== 直播分类 ====================

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#F5F5F5">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:navigationIcon="@drawable/ic_back"
app:title="粉丝团"
app:titleTextColor="@android:color/white" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="100dp"
android:visibility="gone" />
<!-- 粉丝团信息 -->
<LinearLayout
android:id="@+id/layoutFanGroupInfo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<!-- 头部信息卡片 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvFanGroupName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#333333" />
<TextView
android:id="@+id/tvBadge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_badge"
android:paddingHorizontal="12dp"
android:paddingVertical="4dp"
android:textColor="@android:color/white"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/tvAnchorName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:id="@+id/tvMemberCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="#999999"
android:textSize="13sp" />
<!-- 我的信息(已加入时显示) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tvMyLevel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF6B6B"
android:textSize="13sp"
android:visibility="gone" />
<TextView
android:id="@+id/tvMyIntimacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textColor="#4ECDC4"
android:textSize="13sp"
android:visibility="gone" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnJoin"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="16dp"
android:text="加入粉丝团"
android:textColor="@android:color/white"
app:cornerRadius="24dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 成员列表 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="粉丝团成员"
android:textColor="#333333"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMembers"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="8dp"
android:padding="8dp" />
</LinearLayout>
<!-- 无粉丝团提示 -->
<LinearLayout
android:id="@+id/layoutNoFanGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_empty"
android:alpha="0.5" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="该主播还没有创建粉丝团"
android:textColor="#999999"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>

View File

@ -1,3 +1,3 @@
plugins { plugins {
id("com.android.application") version "8.1.2" apply false id("com.android.application") version "8.7.3" apply false
} }

View File

@ -1,15 +1,13 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true android.useAndroidX=true
# 禁用 JDK Image Transform解决 jlink 问题) # 禁用配置缓存
android.enableJdkImageTransform=false org.gradle.configuration-cache=false
org.gradle.parallel=true
org.gradle.caching=false
# 网络超时设置 # 网络超时设置
systemProp.http.connectionTimeout=60000 systemProp.http.connectionTimeout=60000
systemProp.http.socketTimeout=60000 systemProp.http.socketTimeout=60000
systemProp.https.connectionTimeout=60000 systemProp.https.connectionTimeout=60000
systemProp.https.socketTimeout=60000 systemProp.https.socketTimeout=60000
# 启用配置缓存
org.gradle.configuration-cache=true
org.gradle.parallel=true

View File

@ -0,0 +1,133 @@
-- ============================================
-- 粉丝团系统完整检查与完善脚本
-- 执行此脚本以检查和完善粉丝团功能
-- ============================================
-- ==================== 第一部分:检查表结构 ====================
-- 1. 检查粉丝团表是否存在及结构
SELECT '=== 1. 粉丝团表结构 ===' as info;
DESC eb_fan_group;
-- 2. 检查粉丝团成员表结构
SELECT '=== 2. 粉丝团成员表结构 ===' as info;
DESC eb_fan_group_member;
-- 3. 检查群组消息表结构
SELECT '=== 3. 群组消息表结构 ===' as info;
DESC eb_group_message;
-- ==================== 第二部分:检查现有数据 ====================
-- 4. 查看当前粉丝团数据
SELECT '=== 4. 当前粉丝团列表 ===' as info;
SELECT
fg.id,
fg.name,
fg.anchor_id,
fg.anchor_name,
u.nickname as anchor_real_name,
u.avatar as anchor_avatar,
fg.badge,
fg.badge_color,
fg.member_count,
fg.status,
fg.create_time
FROM eb_fan_group fg
LEFT JOIN eb_user u ON fg.anchor_id = u.uid
ORDER BY fg.id;
-- 5. 查看粉丝团成员数据
SELECT '=== 5. 粉丝团成员列表 ===' as info;
SELECT
m.id,
m.group_id as fan_group_id,
fg.name as fan_group_name,
m.uid,
m.nickname as member_nickname,
u.nickname as real_nickname,
u.avatar,
m.level,
m.intimacy,
m.status,
m.join_time
FROM eb_fan_group_member m
LEFT JOIN eb_fan_group fg ON m.group_id = fg.id
LEFT JOIN eb_user u ON m.uid = u.uid
WHERE m.status = 1
ORDER BY m.group_id, m.level DESC, m.intimacy DESC;
-- 6. 查看群组消息
SELECT '=== 6. 粉丝团聊天消息 ===' as info;
SELECT
gm.id,
gm.group_id,
fg.name as fan_group_name,
gm.sender_id,
u.nickname as sender_name,
gm.content,
gm.message_type,
gm.is_deleted,
gm.create_time
FROM eb_group_message gm
LEFT JOIN eb_fan_group fg ON gm.group_id = fg.id
LEFT JOIN eb_user u ON gm.sender_id = u.uid
WHERE gm.is_deleted = 0
ORDER BY gm.group_id, gm.create_time DESC
LIMIT 50;
-- ==================== 第三部分:数据统计 ====================
-- 7. 粉丝团统计
SELECT '=== 7. 粉丝团统计 ===' as info;
SELECT
COUNT(*) as total_fan_groups,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as active_fan_groups,
SUM(member_count) as total_members
FROM eb_fan_group;
-- 8. 各粉丝团成员数量对比(实际 vs 记录)
SELECT '=== 8. 成员数量校验 ===' as info;
SELECT
fg.id,
fg.name,
fg.member_count as recorded_count,
COUNT(m.id) as actual_count,
CASE
WHEN fg.member_count = COUNT(m.id) THEN '✓ 一致'
ELSE '✗ 不一致'
END as status
FROM eb_fan_group fg
LEFT JOIN eb_fan_group_member m ON fg.id = m.group_id AND m.status = 1
GROUP BY fg.id, fg.name, fg.member_count;
-- ==================== 第四部分:数据修复 ====================
-- 9. 修复粉丝团成员数量(如果不一致)
SELECT '=== 9. 修复成员数量 ===' as info;
UPDATE eb_fan_group fg SET member_count = (
SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1
);
-- 10. 更新粉丝团主播名称(从用户表同步)
SELECT '=== 10. 同步主播名称 ===' as info;
UPDATE eb_fan_group fg
JOIN eb_user u ON fg.anchor_id = u.uid
SET fg.anchor_name = u.nickname
WHERE fg.anchor_name IS NULL OR fg.anchor_name = '' OR fg.anchor_name != u.nickname;
-- ==================== 第五部分:验证修复结果 ====================
-- 11. 再次检查成员数量
SELECT '=== 11. 修复后成员数量校验 ===' as info;
SELECT
fg.id,
fg.name,
fg.anchor_name,
fg.member_count as recorded_count,
COUNT(m.id) as actual_count
FROM eb_fan_group fg
LEFT JOIN eb_fan_group_member m ON fg.id = m.group_id AND m.status = 1
GROUP BY fg.id, fg.name, fg.anchor_name, fg.member_count;
SELECT '=== 粉丝团检查完成 ===' as info;

44
check_fan_group_data.sql Normal file
View File

@ -0,0 +1,44 @@
-- 检查粉丝团表是否存在
SHOW TABLES LIKE 'eb_fan_group%';
-- 检查粉丝团表结构
DESC eb_fan_group;
-- 检查粉丝团数据
SELECT * FROM eb_fan_group LIMIT 10;
-- 检查粉丝团成员表
DESC eb_fan_group_member;
-- 检查粉丝团成员数据
SELECT * FROM eb_fan_group_member LIMIT 10;
-- 如果没有数据,插入测试数据
INSERT INTO eb_fan_group (id, name, anchor_id, anchor_name, avatar, member_count, level, description, create_time, update_time)
SELECT 1, '测试粉丝团1', 121, '主播小明', 'https://example.com/avatar1.jpg', 100, 3, '这是一个测试粉丝团', NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group WHERE id = 1);
INSERT INTO eb_fan_group (id, name, anchor_id, anchor_name, avatar, member_count, level, description, create_time, update_time)
SELECT 2, '星光粉丝团', 122, '主播小红', 'https://example.com/avatar2.jpg', 250, 5, '星光闪耀的粉丝团', NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group WHERE id = 2);
INSERT INTO eb_fan_group (id, name, anchor_id, anchor_name, avatar, member_count, level, description, create_time, update_time)
SELECT 3, '梦想家族', 123, '主播小华', 'https://example.com/avatar3.jpg', 500, 8, '追逐梦想的大家庭', NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group WHERE id = 3);
-- 插入粉丝团成员测试数据
INSERT INTO eb_fan_group_member (id, group_id, user_id, nickname, avatar, level, intimacy, join_time, create_time)
SELECT 1, 1, 101, '粉丝A', 'https://example.com/fan1.jpg', 2, 1000, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE id = 1);
INSERT INTO eb_fan_group_member (id, group_id, user_id, nickname, avatar, level, intimacy, join_time, create_time)
SELECT 2, 1, 102, '粉丝B', 'https://example.com/fan2.jpg', 3, 2500, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE id = 2);
INSERT INTO eb_fan_group_member (id, group_id, user_id, nickname, avatar, level, intimacy, join_time, create_time)
SELECT 3, 2, 103, '粉丝C', 'https://example.com/fan3.jpg', 1, 500, NOW(), NOW()
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE id = 3);
-- 验证插入结果
SELECT * FROM eb_fan_group;
SELECT * FROM eb_fan_group_member;

35
check_fan_group_menu.sql Normal file
View File

@ -0,0 +1,35 @@
-- 检查粉丝团菜单是否存在
SELECT '=== 检查粉丝团菜单 ===' as info;
SELECT id, pid, name, path, component, perms, sort, is_show
FROM eb_system_menu
WHERE name LIKE '%粉丝团%' OR path LIKE '%fanGroup%' OR path LIKE '%fan-group%';
-- 如果不存在,添加粉丝团菜单
-- 首先找到"社交管理"或"直播管理"的父菜单ID
SELECT '=== 查找父菜单 ===' as info;
SELECT id, name, path FROM eb_system_menu WHERE name IN ('社交管理', '直播管理', '用户管理') AND pid = 0;
-- 添加粉丝团菜单(如果不存在)
-- 注意需要根据实际的父菜单ID调整pid值
INSERT INTO eb_system_menu (pid, name, icon, path, component, perms, menu_type, sort, is_show, create_time, update_time)
SELECT
(SELECT id FROM eb_system_menu WHERE name = '社交管理' AND pid = 0 LIMIT 1) as pid,
'粉丝团管理' as name,
'el-icon-star-on' as icon,
'/fanGroup/list' as path,
'fanGroup/list/index' as component,
'admin:fan:group:list' as perms,
'C' as menu_type,
10 as sort,
1 as is_show,
NOW() as create_time,
NOW() as update_time
WHERE NOT EXISTS (
SELECT 1 FROM eb_system_menu WHERE path = '/fanGroup/list'
);
-- 验证菜单
SELECT '=== 验证粉丝团菜单 ===' as info;
SELECT id, pid, name, path, component, perms, sort, is_show
FROM eb_system_menu
WHERE name LIKE '%粉丝团%' OR path LIKE '%fanGroup%';

43
check_report_data.sql Normal file
View File

@ -0,0 +1,43 @@
-- =====================================================
-- 举报表检查和测试数据脚本
-- =====================================================
-- 1. 检查表是否存在
SELECT '=== 检查举报表 ===' as step;
SHOW TABLES LIKE 'eb_report';
-- 2. 查看表结构
SELECT '=== 表结构 ===' as step;
DESCRIBE eb_report;
-- 3. 查看现有数据
SELECT '=== 现有数据 ===' as step;
SELECT COUNT(*) as total FROM eb_report;
-- 4. 查看详细数据
SELECT * FROM eb_report ORDER BY id DESC LIMIT 10;
-- 5. 如果没有数据,插入测试数据
INSERT IGNORE INTO `eb_report`
(`uid`, `nickname`, `target_type`, `target_id`, `target_name`, `reason_type`, `reason`, `images`, `status`, `create_time`)
VALUES
(121, '测试用户1', 1, 122, '被举报用户A', 1, '该用户发布色情低俗内容', '[]', 0, NOW()),
(121, '测试用户1', 2, 1, '违规直播间', 2, '直播间存在广告骚扰行为', '[]', 0, NOW()),
(122, '测试用户2', 3, 1, '违规动态', 5, '动态内容存在人身攻击', '[]', 0, DATE_SUB(NOW(), INTERVAL 1 DAY)),
(123, '测试用户3', 1, 124, '被举报用户B', 6, '该用户涉嫌欺诈骗钱', '[]', 2, DATE_SUB(NOW(), INTERVAL 2 DAY));
-- 6. 再次查看数据
SELECT '=== 插入后数据 ===' as step;
SELECT r.*,
CASE r.target_type WHEN 1 THEN '用户' WHEN 2 THEN '房间' WHEN 3 THEN '动态' WHEN 4 THEN '评论' ELSE '其他' END as type_text,
CASE r.status WHEN 0 THEN '待处理' WHEN 1 THEN '处理中' WHEN 2 THEN '已处理' WHEN 3 THEN '已忽略' ELSE '未知' END as status_text
FROM eb_report r
ORDER BY r.id DESC;
-- 7. 统计
SELECT '=== 统计 ===' as step;
SELECT
COUNT(*) as total,
SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as processed
FROM eb_report;

56
check_sensitive_word.sql Normal file
View File

@ -0,0 +1,56 @@
-- =====================================================
-- 敏感词表检查和初始化脚本
-- 请在 Navicat 中逐步执行以下SQL
-- =====================================================
-- ========== 第1步检查表是否存在 ==========
SELECT '=== 检查表是否存在 ===' as step;
SHOW TABLES LIKE 'eb_sensitive_word';
-- ========== 第2步如果表不存在创建表 ==========
CREATE TABLE IF NOT EXISTS `eb_sensitive_word` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`word` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '敏感词',
`category` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT '分类',
`level` tinyint NOT NULL DEFAULT 1 COMMENT '级别 1=轻度 2=中度 3=重度',
`action` tinyint NOT NULL DEFAULT 1 COMMENT '处理方式 1=替换 2=拦截 3=警告',
`replace_text` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '***' COMMENT '替换文本',
`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态 0=禁用 1=启用',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_word` (`word`) USING BTREE,
KEY `idx_category` (`category`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '敏感词表' ROW_FORMAT = DYNAMIC;
-- ========== 第3步查看表结构 ==========
SELECT '=== 表结构 ===' as step;
DESCRIBE eb_sensitive_word;
-- ========== 第4步查看现有数据数量 ==========
SELECT '=== 现有数据数量 ===' as step;
SELECT COUNT(*) as total FROM eb_sensitive_word;
-- ========== 第5步插入测试数据如果没有数据 ==========
-- 使用 INSERT IGNORE 避免重复插入
INSERT IGNORE INTO `eb_sensitive_word` (`word`, `category`, `level`, `action`, `replace_text`, `status`, `create_time`) VALUES
('测试敏感词1', 'default', 1, 1, '***', 1, NOW()),
('测试敏感词2', 'default', 2, 1, '***', 1, NOW()),
('广告', 'spam', 1, 1, '**', 1, NOW()),
('推广', 'spam', 1, 1, '**', 1, NOW()),
('加微信', 'spam', 2, 2, '***', 1, NOW()),
('加QQ', 'spam', 2, 2, '***', 1, NOW()),
('色情', 'illegal', 3, 2, '***', 1, NOW()),
('赌博', 'illegal', 3, 2, '***', 1, NOW()),
('诈骗', 'illegal', 3, 2, '***', 1, NOW()),
('违法', 'illegal', 2, 2, '***', 1, NOW());
-- ========== 第6步查看所有数据 ==========
SELECT '=== 敏感词数据列表 ===' as step;
SELECT * FROM eb_sensitive_word ORDER BY id DESC;
-- ========== 第7步最终统计 ==========
SELECT '=== 最终统计 ===' as step;
SELECT COUNT(*) as total_count FROM eb_sensitive_word;
SELECT COUNT(*) as enabled_count FROM eb_sensitive_word WHERE status = 1;
SELECT COUNT(*) as disabled_count FROM eb_sensitive_word WHERE status = 0;

View File

@ -0,0 +1,77 @@
-- ============================================
-- 完善粉丝团系统数据库
-- ============================================
-- 1. 查看粉丝团表结构
DESC eb_fan_group;
-- 2. 查看粉丝团成员表结构
DESC eb_fan_group_member;
-- 3. 查看当前粉丝团数据
SELECT * FROM eb_fan_group;
-- 4. 插入粉丝团成员测试数据
-- 为粉丝团1星光粉丝团主播121添加成员
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 1, 122, '用户小红', 3, 2500, 1, DATE_SUB(NOW(), INTERVAL 10 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 1 AND uid = 122);
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 1, 123, '用户小华', 2, 1200, 1, DATE_SUB(NOW(), INTERVAL 7 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 1 AND uid = 123);
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 1, 124, '用户阿杰', 1, 500, 1, DATE_SUB(NOW(), INTERVAL 3 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 1 AND uid = 124);
-- 为粉丝团2梦想家族主播122添加成员
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 2, 121, '主播小明', 5, 8000, 1, DATE_SUB(NOW(), INTERVAL 20 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 2 AND uid = 121);
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 2, 125, '用户小美', 4, 5500, 1, DATE_SUB(NOW(), INTERVAL 15 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 2 AND uid = 125);
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 2, 126, '用户大壮', 2, 1800, 1, DATE_SUB(NOW(), INTERVAL 5 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 2 AND uid = 126);
-- 为粉丝团3快乐大本营主播123添加成员
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 3, 127, '快乐粉丝A', 3, 3200, 1, DATE_SUB(NOW(), INTERVAL 12 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 3 AND uid = 127);
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 3, 128, '快乐粉丝B', 2, 1500, 1, DATE_SUB(NOW(), INTERVAL 8 DAY)
WHERE NOT EXISTS (SELECT 1 FROM eb_fan_group_member WHERE group_id = 3 AND uid = 128);
-- 5. 更新粉丝团成员数量
UPDATE eb_fan_group fg SET member_count = (
SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1
);
-- 6. 验证数据
SELECT '粉丝团列表' as info;
SELECT id, name, anchor_name, member_count, badge, status FROM eb_fan_group;
SELECT '粉丝团成员' as info;
SELECT m.*, fg.name as group_name
FROM eb_fan_group_member m
LEFT JOIN eb_fan_group fg ON m.group_id = fg.id
ORDER BY m.group_id, m.level DESC;
-- 7. 创建群组消息表(如果不存在)
CREATE TABLE IF NOT EXISTS eb_group_message (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
group_id BIGINT UNSIGNED NOT NULL COMMENT '群组ID',
sender_id INT UNSIGNED NOT NULL COMMENT '发送者ID',
content TEXT COMMENT '消息内容',
message_type VARCHAR(20) DEFAULT 'text' COMMENT '消息类型',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_group_id (group_id),
KEY idx_sender_id (sender_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群组消息表';

View File

@ -0,0 +1,80 @@
-- ============================================
-- 粉丝团系统完整设置脚本
-- 执行此脚本以完善粉丝团功能
-- ============================================
-- 1. 查看当前粉丝团数据
SELECT '=== 当前粉丝团 ===' as info;
SELECT id, name, anchor_id, anchor_name, member_count, badge, status FROM eb_fan_group;
-- 2. 查看粉丝团成员表结构
SELECT '=== 成员表结构 ===' as info;
DESC eb_fan_group_member;
-- 3. 插入粉丝团成员测试数据(如果不存在)
-- 粉丝团1的成员
INSERT IGNORE INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
VALUES
(1, 122, '用户小红', 3, 2500, 1, DATE_SUB(NOW(), INTERVAL 10 DAY)),
(1, 123, '用户小华', 2, 1200, 1, DATE_SUB(NOW(), INTERVAL 7 DAY)),
(1, 124, '用户阿杰', 1, 500, 1, DATE_SUB(NOW(), INTERVAL 3 DAY));
-- 粉丝团2的成员
INSERT IGNORE INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
VALUES
(2, 121, '主播小明', 5, 8000, 1, DATE_SUB(NOW(), INTERVAL 20 DAY)),
(2, 125, '用户小美', 4, 5500, 1, DATE_SUB(NOW(), INTERVAL 15 DAY)),
(2, 126, '用户大壮', 2, 1800, 1, DATE_SUB(NOW(), INTERVAL 5 DAY));
-- 粉丝团3的成员
INSERT IGNORE INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
VALUES
(3, 127, '快乐粉丝A', 3, 3200, 1, DATE_SUB(NOW(), INTERVAL 12 DAY)),
(3, 128, '快乐粉丝B', 2, 1500, 1, DATE_SUB(NOW(), INTERVAL 8 DAY));
-- 4. 更新粉丝团成员数量
UPDATE eb_fan_group fg SET member_count = (
SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1
);
-- 5. 创建群组消息表(如果不存在)
CREATE TABLE IF NOT EXISTS eb_group_message (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
group_id BIGINT UNSIGNED NOT NULL COMMENT '群组ID',
sender_id INT UNSIGNED NOT NULL COMMENT '发送者ID',
content TEXT COMMENT '消息内容',
message_type VARCHAR(20) DEFAULT 'text' COMMENT '消息类型',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_group_id (group_id),
KEY idx_sender_id (sender_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群组消息表';
-- 6. 插入一些测试聊天消息
INSERT IGNORE INTO eb_group_message (id, group_id, sender_id, content, message_type, create_time)
VALUES
(1, 1, 122, '大家好,我是新来的粉丝!', 'text', DATE_SUB(NOW(), INTERVAL 2 DAY)),
(2, 1, 123, '欢迎欢迎!', 'text', DATE_SUB(NOW(), INTERVAL 2 DAY)),
(3, 1, 121, '感谢大家的支持!', 'text', DATE_SUB(NOW(), INTERVAL 1 DAY)),
(4, 2, 125, '今晚直播太精彩了!', 'text', DATE_SUB(NOW(), INTERVAL 1 DAY)),
(5, 2, 126, '同意!期待下次直播', 'text', DATE_SUB(NOW(), INTERVAL 1 DAY));
-- 7. 验证最终数据
SELECT '=== 粉丝团列表 ===' as info;
SELECT id, name, anchor_name, member_count, badge, badge_color, status, create_time FROM eb_fan_group;
SELECT '=== 粉丝团成员 ===' as info;
SELECT m.id, m.group_id, fg.name as group_name, m.uid, m.nickname, m.level, m.intimacy, m.status, m.join_time
FROM eb_fan_group_member m
LEFT JOIN eb_fan_group fg ON m.group_id = fg.id
ORDER BY m.group_id, m.level DESC;
SELECT '=== 聊天消息 ===' as info;
SELECT gm.id, gm.group_id, fg.name as group_name, gm.sender_id, gm.content, gm.create_time
FROM eb_group_message gm
LEFT JOIN eb_fan_group fg ON gm.group_id = fg.id
WHERE gm.is_deleted = 0
ORDER BY gm.create_time DESC;
SELECT '=== 设置完成 ===' as info;

120
fan_group_real_data.sql Normal file
View File

@ -0,0 +1,120 @@
-- ============================================
-- 粉丝团真实数据设置
-- 确保成员来自真实用户,消息与成员对应
-- ============================================
-- 1. 先查看系统中的真实用户
SELECT '=== 系统真实用户 ===' as info;
SELECT uid, nickname, avatar, phone FROM eb_user WHERE is_del = 0 LIMIT 20;
-- 2. 查看当前粉丝团
SELECT '=== 当前粉丝团 ===' as info;
SELECT id, name, anchor_id, anchor_name, member_count FROM eb_fan_group;
-- 3. 清空测试数据,重新基于真实用户创建
DELETE FROM eb_fan_group_member;
DELETE FROM eb_group_message;
-- 4. 为每个粉丝团添加真实用户作为成员
-- 粉丝团1 (anchor_id=121) 的成员 - 从真实用户中选取(排除主播自己)
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 1, u.uid, u.nickname,
FLOOR(1 + RAND() * 5) as level, -- 随机等级1-5
FLOOR(100 + RAND() * 5000) as intimacy, -- 随机亲密度
1,
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY)
FROM eb_user u
WHERE u.uid != 121 AND u.is_del = 0 AND u.nickname IS NOT NULL AND u.nickname != ''
ORDER BY RAND()
LIMIT 5;
-- 粉丝团2 (anchor_id=122) 的成员
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 2, u.uid, u.nickname,
FLOOR(1 + RAND() * 5) as level,
FLOOR(100 + RAND() * 5000) as intimacy,
1,
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY)
FROM eb_user u
WHERE u.uid != 122 AND u.is_del = 0 AND u.nickname IS NOT NULL AND u.nickname != ''
AND u.uid NOT IN (SELECT uid FROM eb_fan_group_member WHERE group_id = 1)
ORDER BY RAND()
LIMIT 5;
-- 粉丝团3 (anchor_id=123) 的成员
INSERT INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT 3, u.uid, u.nickname,
FLOOR(1 + RAND() * 5) as level,
FLOOR(100 + RAND() * 5000) as intimacy,
1,
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY)
FROM eb_user u
WHERE u.uid != 123 AND u.is_del = 0 AND u.nickname IS NOT NULL AND u.nickname != ''
AND u.uid NOT IN (SELECT uid FROM eb_fan_group_member WHERE group_id IN (1,2))
ORDER BY RAND()
LIMIT 3;
-- 5. 更新粉丝团成员数量
UPDATE eb_fan_group fg SET member_count = (
SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1
);
-- 6. 为粉丝团添加真实成员的聊天消息
-- 粉丝团1的消息由该粉丝团的真实成员发送
INSERT INTO eb_group_message (group_id, sender_id, content, message_type, create_time)
SELECT 1, m.uid,
CASE FLOOR(RAND() * 5)
WHEN 0 THEN '大家好,很高兴加入粉丝团!'
WHEN 1 THEN '主播今天直播太精彩了!'
WHEN 2 THEN '支持主播,冲冲冲!'
WHEN 3 THEN '期待下次直播~'
ELSE '粉丝团的氛围真好!'
END,
'text',
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 7) DAY)
FROM eb_fan_group_member m
WHERE m.group_id = 1 AND m.status = 1
LIMIT 3;
-- 粉丝团2的消息
INSERT INTO eb_group_message (group_id, sender_id, content, message_type, create_time)
SELECT 2, m.uid,
CASE FLOOR(RAND() * 5)
WHEN 0 THEN '新人报到!'
WHEN 1 THEN '主播唱歌太好听了!'
WHEN 2 THEN '今天的游戏直播太刺激了'
WHEN 3 THEN '大家晚上好~'
ELSE '感谢主播的精彩表演!'
END,
'text',
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 7) DAY)
FROM eb_fan_group_member m
WHERE m.group_id = 2 AND m.status = 1
LIMIT 3;
-- 7. 验证数据完整性
SELECT '=== 粉丝团成员(关联真实用户)===' as info;
SELECT m.id, m.group_id, fg.name as fan_group_name,
m.uid, m.nickname, u.nickname as real_nickname, u.avatar,
m.level, m.intimacy, m.join_time
FROM eb_fan_group_member m
LEFT JOIN eb_fan_group fg ON m.group_id = fg.id
LEFT JOIN eb_user u ON m.uid = u.uid
WHERE m.status = 1
ORDER BY m.group_id, m.level DESC;
SELECT '=== 聊天消息(关联真实发送者)===' as info;
SELECT gm.id, gm.group_id, fg.name as fan_group_name,
gm.sender_id, u.nickname as sender_name, u.avatar as sender_avatar,
gm.content, gm.create_time
FROM eb_group_message gm
LEFT JOIN eb_fan_group fg ON gm.group_id = fg.id
LEFT JOIN eb_user u ON gm.sender_id = u.uid
WHERE gm.is_deleted = 0
ORDER BY gm.group_id, gm.create_time DESC;
SELECT '=== 粉丝团统计 ===' as info;
SELECT fg.id, fg.name, fg.anchor_name, fg.member_count,
(SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1) as actual_members,
(SELECT COUNT(*) FROM eb_group_message gm WHERE gm.group_id = fg.id AND gm.is_deleted = 0) as message_count
FROM eb_fan_group fg;

View File

@ -0,0 +1,60 @@
-- ============================================
-- 粉丝团系统完整性检查脚本
-- ============================================
-- 1. 检查表结构
SELECT '=== 1. 检查表结构 ===' as info;
-- 检查eb_fan_group表
SELECT 'eb_fan_group表字段:' as info;
SHOW COLUMNS FROM eb_fan_group;
-- 检查eb_fan_group_member表
SELECT 'eb_fan_group_member表字段:' as info;
SHOW COLUMNS FROM eb_fan_group_member;
-- 检查eb_group_message表
SELECT 'eb_group_message表字段:' as info;
SHOW COLUMNS FROM eb_group_message;
-- 2. 检查数据完整性
SELECT '=== 2. 数据统计 ===' as info;
SELECT
(SELECT COUNT(*) FROM eb_fan_group) as total_fan_groups,
(SELECT COUNT(*) FROM eb_fan_group WHERE status = 1) as active_fan_groups,
(SELECT COUNT(*) FROM eb_fan_group_member WHERE status = 1) as total_members,
(SELECT COUNT(*) FROM eb_group_message WHERE is_deleted = 0) as total_messages;
-- 3. 检查粉丝团列表
SELECT '=== 3. 粉丝团列表 ===' as info;
SELECT
fg.id,
fg.name,
fg.anchor_id,
fg.anchor_name,
fg.badge,
fg.member_count,
(SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1) as actual_members,
(SELECT COUNT(*) FROM eb_group_message gm WHERE gm.group_id = fg.id AND gm.is_deleted = 0) as message_count,
fg.status,
fg.create_time
FROM eb_fan_group fg
ORDER BY fg.id;
-- 4. 修复成员数量不一致
SELECT '=== 4. 修复成员数量 ===' as info;
UPDATE eb_fan_group fg SET member_count = (
SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1
);
-- 5. 检查管理端菜单
SELECT '=== 5. 检查管理端菜单 ===' as info;
SELECT id, pid, name, path, component, is_show
FROM eb_system_menu
WHERE name LIKE '%粉丝团%' OR path LIKE '%fanGroup%' OR path LIKE '%fan%group%';
-- 6. 如果菜单不存在,查找合适的父菜单
SELECT '=== 6. 可用的父菜单 ===' as info;
SELECT id, name, path FROM eb_system_menu WHERE pid = 0 AND is_show = 1 ORDER BY sort;
SELECT '=== 检查完成 ===' as info;

View File

@ -0,0 +1,45 @@
-- 修复客服联系方式分组表
-- 检查表是否存在,如果不存在则创建
-- 先检查表是否存在
SELECT COUNT(*) as table_exists FROM information_schema.tables
WHERE table_schema = DATABASE() AND table_name = 'eb_customer_service_group';
-- 如果表存在但字段名不对,需要修改
-- 检查是否有 name 字段
SET @has_name := (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'eb_customer_service_group'
AND column_name = 'name');
-- 检查是否有 group_name 字段
SET @has_group_name := (SELECT COUNT(*) FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'eb_customer_service_group'
AND column_name = 'group_name');
-- 如果有 name 但没有 group_name重命名字段
-- ALTER TABLE eb_customer_service_group CHANGE COLUMN `name` `group_name` varchar(64) NOT NULL DEFAULT '' COMMENT '用户组名称';
-- 或者直接重建表(更安全)
DROP TABLE IF EXISTS `eb_customer_service_group`;
CREATE TABLE `eb_customer_service_group` (
`id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户组名称',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户头像',
`contact` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '联系电话或微信',
`contact_type` tinyint NOT NULL DEFAULT 1 COMMENT '联系类型 1=电话 2=微信',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '客服联系方式分组表' ROW_FORMAT = DYNAMIC;
-- 插入测试数据
INSERT INTO `eb_customer_service_group` (`group_name`, `avatar`, `contact`, `contact_type`, `create_time`) VALUES
('官方客服', 'https://img.zcool.cn/community/01a9a65d143edaa8012187f447cfef.jpg', '400-888-8888', 1, NOW()),
('技术支持', 'https://img.zcool.cn/community/01b72057a7e0790000018c1bf4fce0.png', 'tech_support_wx', 2, NOW()),
('商务合作', '', '13800138000', 1, NOW()),
('投诉建议', '', 'complaint_wx', 2, NOW());
-- 查看数据
SELECT * FROM eb_customer_service_group;

11
insert_fan_group_data.sql Normal file
View File

@ -0,0 +1,11 @@
-- 插入粉丝团测试数据
INSERT INTO eb_fan_group (anchor_id, anchor_name, name, badge, badge_color, member_count, status, create_time, update_time)
VALUES
(121, '主播小明', '星光粉丝团', '星光', '#FF6B6B', 156, 1, NOW(), NOW()),
(122, '主播小红', '梦想家族', '梦想', '#4ECDC4', 328, 1, NOW(), NOW()),
(123, '主播小华', '快乐大本营', '快乐', '#45B7D1', 89, 1, NOW(), NOW()),
(124, '主播阿杰', '王者军团', '王者', '#96CEB4', 512, 1, NOW(), NOW()),
(125, '主播小美', '甜蜜蜜', '甜蜜', '#FFEAA7', 245, 1, NOW(), NOW());
-- 验证插入结果
SELECT * FROM eb_fan_group;

13
remove_fangroup_menu.sql Normal file
View File

@ -0,0 +1,13 @@
-- 移除"粉丝团管理"一级导航菜单
-- 先查看当前菜单
SELECT id, name, pid, sort, is_show FROM eb_system_menu WHERE name = '粉丝团管理';
-- 方法1隐藏菜单推荐可恢复
UPDATE eb_system_menu SET is_show = 0 WHERE name = '粉丝团管理' AND pid = 0;
-- 方法2如果要彻底删除先删除子菜单再删除父菜单
-- DELETE FROM eb_system_menu WHERE pid = (SELECT id FROM (SELECT id FROM eb_system_menu WHERE name = '粉丝团管理' AND pid = 0) AS t);
-- DELETE FROM eb_system_menu WHERE name = '粉丝团管理' AND pid = 0;
-- 验证结果
SELECT id, name, pid, is_show FROM eb_system_menu WHERE name = '粉丝团管理';

View File

@ -0,0 +1,5 @@
-- 删除遗留的子菜单
DELETE FROM eb_system_menu WHERE id = 664;
-- 验证删除结果 - 只应该剩下直播管理下的粉丝团管理(id=707)
SELECT * FROM eb_system_menu WHERE name LIKE '%粉丝团%';

200
setup_fan_group_data.sql Normal file
View File

@ -0,0 +1,200 @@
-- ============================================
-- 粉丝团测试数据设置脚本
-- 基于真实用户创建粉丝团测试数据
-- ============================================
-- ==================== 第一部分:创建表(如果不存在)====================
-- 1. 创建粉丝团表
CREATE TABLE IF NOT EXISTS eb_fan_group (
id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '粉丝团ID',
anchor_id INT UNSIGNED NOT NULL COMMENT '主播ID',
anchor_name VARCHAR(100) DEFAULT NULL COMMENT '主播名称',
group_id BIGINT UNSIGNED DEFAULT NULL COMMENT '关联群组ID',
name VARCHAR(100) NOT NULL COMMENT '粉丝团名称',
badge VARCHAR(50) DEFAULT '粉丝' COMMENT '粉丝徽章',
badge_color VARCHAR(20) DEFAULT '#FF6B9D' COMMENT '徽章颜色',
member_count INT DEFAULT 0 COMMENT '成员数量',
status TINYINT DEFAULT 1 COMMENT '状态 1-正常 0-禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_anchor_id (anchor_id),
KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='粉丝团表';
-- 2. 创建粉丝团成员表
CREATE TABLE IF NOT EXISTS eb_fan_group_member (
id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '成员ID',
group_id INT UNSIGNED NOT NULL COMMENT '粉丝团ID',
uid INT UNSIGNED NOT NULL COMMENT '用户ID',
nickname VARCHAR(100) DEFAULT NULL COMMENT '用户昵称',
level INT DEFAULT 1 COMMENT '粉丝等级 1-10',
intimacy INT DEFAULT 0 COMMENT '亲密度',
status TINYINT DEFAULT 1 COMMENT '状态 1-正常 0-退出',
join_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '加入时间',
PRIMARY KEY (id),
UNIQUE KEY uk_group_uid (group_id, uid),
KEY idx_uid (uid),
KEY idx_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='粉丝团成员表';
-- 3. 创建群组消息表
CREATE TABLE IF NOT EXISTS eb_group_message (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '消息ID',
group_id INT UNSIGNED NOT NULL COMMENT '粉丝团ID',
sender_id INT UNSIGNED NOT NULL COMMENT '发送者ID',
content TEXT COMMENT '消息内容',
message_type VARCHAR(20) DEFAULT 'text' COMMENT '消息类型 text/image/gift',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id),
KEY idx_group_id (group_id),
KEY idx_sender_id (sender_id),
KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='群组消息表';
-- ==================== 第二部分:查看可用用户 ====================
SELECT '=== 可用用户列表前20个===' as info;
SELECT uid, nickname, avatar, phone FROM eb_user WHERE status = 1 LIMIT 20;
-- ==================== 第三部分:创建粉丝团(如果不存在)====================
-- 为用户100创建粉丝团如果不存在
INSERT INTO eb_fan_group (anchor_id, anchor_name, name, badge, badge_color, member_count, status, create_time)
SELECT 100, u.nickname, CONCAT(u.nickname, '的粉丝团'), '星光', '#FF6B9D', 0, 1, NOW()
FROM eb_user u
WHERE u.uid = 100 AND u.status = 1
AND NOT EXISTS (SELECT 1 FROM eb_fan_group WHERE anchor_id = 100);
-- 为用户101创建粉丝团如果不存在
INSERT INTO eb_fan_group (anchor_id, anchor_name, name, badge, badge_color, member_count, status, create_time)
SELECT 101, u.nickname, CONCAT(u.nickname, '的粉丝团'), '梦想', '#6B9DFF', 0, 1, NOW()
FROM eb_user u
WHERE u.uid = 101 AND u.status = 1
AND NOT EXISTS (SELECT 1 FROM eb_fan_group WHERE anchor_id = 101);
-- 为用户102创建粉丝团如果不存在
INSERT INTO eb_fan_group (anchor_id, anchor_name, name, badge, badge_color, member_count, status, create_time)
SELECT 102, u.nickname, CONCAT(u.nickname, '的粉丝团'), '快乐', '#9DFF6B', 0, 1, NOW()
FROM eb_user u
WHERE u.uid = 102 AND u.status = 1
AND NOT EXISTS (SELECT 1 FROM eb_fan_group WHERE anchor_id = 102);
-- ==================== 第四部分:添加粉丝团成员 ====================
-- 获取粉丝团ID
SET @fg1 = (SELECT id FROM eb_fan_group WHERE anchor_id = 100 LIMIT 1);
SET @fg2 = (SELECT id FROM eb_fan_group WHERE anchor_id = 101 LIMIT 1);
SET @fg3 = (SELECT id FROM eb_fan_group WHERE anchor_id = 102 LIMIT 1);
-- 为粉丝团1添加成员从真实用户中选取
INSERT IGNORE INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT @fg1, u.uid, u.nickname,
FLOOR(1 + RAND() * 5) as level,
FLOOR(100 + RAND() * 5000) as intimacy,
1, DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY)
FROM eb_user u
WHERE u.uid NOT IN (100, 101, 102)
AND u.status = 1
AND u.nickname IS NOT NULL
AND u.nickname != ''
AND @fg1 IS NOT NULL
ORDER BY RAND()
LIMIT 5;
-- 为粉丝团2添加成员
INSERT IGNORE INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT @fg2, u.uid, u.nickname,
FLOOR(1 + RAND() * 5) as level,
FLOOR(100 + RAND() * 5000) as intimacy,
1, DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY)
FROM eb_user u
WHERE u.uid NOT IN (100, 101, 102)
AND u.status = 1
AND u.nickname IS NOT NULL
AND u.nickname != ''
AND u.uid NOT IN (SELECT uid FROM eb_fan_group_member WHERE group_id = @fg1)
AND @fg2 IS NOT NULL
ORDER BY RAND()
LIMIT 4;
-- 为粉丝团3添加成员
INSERT IGNORE INTO eb_fan_group_member (group_id, uid, nickname, level, intimacy, status, join_time)
SELECT @fg3, u.uid, u.nickname,
FLOOR(1 + RAND() * 5) as level,
FLOOR(100 + RAND() * 5000) as intimacy,
1, DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 30) DAY)
FROM eb_user u
WHERE u.uid NOT IN (100, 101, 102)
AND u.status = 1
AND u.nickname IS NOT NULL
AND u.nickname != ''
AND u.uid NOT IN (SELECT uid FROM eb_fan_group_member WHERE group_id IN (@fg1, @fg2))
AND @fg3 IS NOT NULL
ORDER BY RAND()
LIMIT 3;
-- ==================== 第五部分:添加聊天消息 ====================
-- 为粉丝团1添加消息
INSERT INTO eb_group_message (group_id, sender_id, content, message_type, create_time)
SELECT @fg1, m.uid,
CASE FLOOR(RAND() * 5)
WHEN 0 THEN '大家好,我是新来的粉丝!'
WHEN 1 THEN '今天的直播太精彩了!'
WHEN 2 THEN '支持主播!'
WHEN 3 THEN '期待下次直播~'
ELSE '主播加油!'
END,
'text',
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 7) DAY)
FROM eb_fan_group_member m
WHERE m.group_id = @fg1 AND m.status = 1 AND @fg1 IS NOT NULL
LIMIT 5;
-- 为粉丝团2添加消息
INSERT INTO eb_group_message (group_id, sender_id, content, message_type, create_time)
SELECT @fg2, m.uid,
CASE FLOOR(RAND() * 5)
WHEN 0 THEN '欢迎新成员!'
WHEN 1 THEN '直播间见!'
WHEN 2 THEN '主播唱歌真好听'
WHEN 3 THEN '什么时候开播?'
ELSE '粉丝团万岁!'
END,
'text',
DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 7) DAY)
FROM eb_fan_group_member m
WHERE m.group_id = @fg2 AND m.status = 1 AND @fg2 IS NOT NULL
LIMIT 4;
-- ==================== 第六部分:更新成员数量 ====================
UPDATE eb_fan_group fg SET member_count = (
SELECT COUNT(*) FROM eb_fan_group_member m WHERE m.group_id = fg.id AND m.status = 1
);
-- ==================== 第七部分:验证数据 ====================
SELECT '=== 粉丝团列表 ===' as info;
SELECT id, name, anchor_id, anchor_name, badge, badge_color, member_count, status, create_time
FROM eb_fan_group ORDER BY id;
SELECT '=== 粉丝团成员 ===' as info;
SELECT m.id, m.group_id, fg.name as fan_group_name, m.uid, m.nickname, m.level, m.intimacy, m.join_time
FROM eb_fan_group_member m
LEFT JOIN eb_fan_group fg ON m.group_id = fg.id
WHERE m.status = 1
ORDER BY m.group_id, m.level DESC;
SELECT '=== 聊天消息 ===' as info;
SELECT gm.id, gm.group_id, fg.name as fan_group_name, gm.sender_id, u.nickname as sender_name, gm.content, gm.create_time
FROM eb_group_message gm
LEFT JOIN eb_fan_group fg ON gm.group_id = fg.id
LEFT JOIN eb_user u ON gm.sender_id = u.uid
WHERE gm.is_deleted = 0
ORDER BY gm.group_id, gm.create_time DESC
LIMIT 20;
SELECT '=== 数据设置完成 ===' as info;

19
update_signin_data.sql Normal file
View File

@ -0,0 +1,19 @@
-- 更新签到记录测试数据
-- 确保 eb_user_signin_record 表数据正确
-- 更新现有记录的 continuous_days 字段随机1-7天
UPDATE eb_user_signin_record
SET continuous_days = FLOOR(1 + RAND() * 7)
WHERE continuous_days IS NULL OR continuous_days = 0;
-- 确保 reward_value 有值(根据连续天数计算)
UPDATE eb_user_signin_record
SET reward_value = continuous_days * 10
WHERE reward_value IS NULL OR reward_value = 0;
-- 查看更新后的数据
SELECT r.*, u.avatar as user_avatar, u.nickname as db_nickname
FROM eb_user_signin_record r
LEFT JOIN eb_user u ON r.uid = u.uid
ORDER BY r.id DESC
LIMIT 20;

188
封禁系统功能说明.md Normal file
View File

@ -0,0 +1,188 @@
# 封禁系统功能说明
## 功能概述
封禁系统提供用户封禁、房间封禁和黑名单管理功能,支持管理后台和移动端双端实时同步。
## 功能模块
### 1. 用户封禁(管理员功能)
管理员可以对违规用户进行封禁处理:
- **永久封禁**:用户账号永久禁用,无法登录
- **临时封禁**:设置封禁天数,到期自动解除
- **解除封禁**:手动解除用户封禁状态
- **封禁原因**:记录封禁原因,便于追溯
**封禁影响**
- 被封禁用户无法登录
- 被封禁用户无法发送弹幕
- 被封禁用户无法赠送礼物
- 被封禁用户无法发送私信
**API接口**
- `GET /api/admin/ban/user/list` - 获取用户封禁列表
- `POST /api/admin/ban/user/add` - 封禁用户
- `POST /api/admin/ban/user/unban/{id}` - 解除封禁
- `POST /api/admin/ban/user/delete/{id}` - 删除记录
- `GET /api/admin/ban/user/check/{userId}` - 检查封禁状态
### 2. 房间封禁(管理员功能)
管理员可以对违规直播间进行封禁处理:
- **永久封禁**:直播间永久关闭
- **临时封禁**:设置封禁天数,到期自动解除
- **解除封禁**:手动解除房间封禁状态
- **封禁原因**:记录封禁原因
**封禁影响**
- 被封禁房间无法进入
- 进入时显示封禁提示
**API接口**
- `GET /api/admin/ban/room/list` - 获取房间封禁列表
- `POST /api/admin/ban/room/add` - 封禁房间
- `POST /api/admin/ban/room/unban/{id}` - 解除封禁
- `POST /api/admin/ban/room/delete/{id}` - 删除记录
- `GET /api/admin/ban/room/check/{roomId}` - 检查封禁状态
### 3. 用户黑名单(用户功能)
用户可以将其他用户加入黑名单:
- **添加黑名单**:屏蔽指定用户
- **移除黑名单**:解除屏蔽
- **黑名单列表**:查看已屏蔽的用户
- **互动限制**:被拉黑用户无法发送消息、评论等
**黑名单影响**
- 双方无法发送私信
- 双方无法创建会话
- 拉黑时自动删除好友关系
**移动端API接口**
- `GET /api/front/ban/check/me` - 检查当前用户封禁状态
- `GET /api/front/ban/check/user/{userId}` - 检查指定用户封禁状态
- `GET /api/front/ban/check/room/{roomId}` - 检查房间封禁状态
- `GET /api/front/ban/blacklist/list` - 获取我的黑名单
- `POST /api/front/ban/blacklist/add` - 添加到黑名单
- `POST /api/front/ban/blacklist/remove` - 从黑名单移除
- `GET /api/front/ban/blacklist/check/{targetUserId}` - 检查黑名单状态
## 数据库表结构
### eb_user_ban用户封禁表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 主键 |
| user_id | int | 被封禁用户ID |
| ban_type | varchar(20) | 封禁类型permanent/temporary |
| reason | varchar(500) | 封禁原因 |
| duration_days | int | 封禁天数 |
| expire_time | datetime | 到期时间 |
| operator_id | int | 操作人ID |
| status | tinyint | 状态1-生效0-已解除 |
### eb_room_ban房间封禁表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 主键 |
| room_id | int | 被封禁房间ID |
| ban_type | varchar(20) | 封禁类型 |
| reason | varchar(500) | 封禁原因 |
| duration_days | int | 封禁天数 |
| expire_time | datetime | 到期时间 |
| operator_id | int | 操作人ID |
| status | tinyint | 状态 |
### eb_user_blacklist用户黑名单表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 主键 |
| user_id | int | 用户ID发起拉黑 |
| blocked_user_id | int | 被拉黑用户ID |
| blocker_nickname | varchar(100) | 拉黑者昵称 |
| blocked_nickname | varchar(100) | 被拉黑者昵称 |
| create_time | datetime | 创建时间 |
### eb_room_blacklist房间黑名单表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 主键 |
| room_id | int | 房间ID |
| room_name | varchar(200) | 房间名称 |
| blocked_user_id | int | 被拉黑用户ID |
| blocked_user_nickname | varchar(100) | 被拉黑用户昵称 |
| reason | varchar(500) | 拉黑原因 |
| create_time | datetime | 创建时间 |
## 封禁检查点
### 后端检查点
1. **登录时** - LoginServiceImpl.login() / phoneLogin()
2. **发送弹幕时** - LiveRoomController.sendMessage()
3. **赠送礼物时** - LiveRoomController.sendGift()
4. **发送私信时** - ConversationServiceImpl.sendMessage()
5. **创建会话时** - ConversationServiceImpl.getOrCreateConversation()
6. **开播时** - LiveRoomController.create()
### 移动端检查点
1. **进入直播间时** - RoomDetailActivity.checkRoomBanStatus()
2. **用户主页拉黑** - UserProfileActivity.addToBlacklist()
3. **设置页黑名单管理** - BlacklistActivity
## 双端同步机制
1. **管理后台封禁用户** → 用户状态更新 → 移动端登录时检测到封禁状态 → 显示封禁提示,阻止登录
2. **管理后台封禁房间** → 房间状态更新 → 移动端进入房间时检测到封禁状态 → 显示封禁提示,退出房间
3. **移动端添加黑名单** → 数据库更新 → 管理后台可查看黑名单记录
4. **管理后台解除封禁** → 用户/房间状态恢复 → 移动端可正常使用
## 文件清单
### 后端文件
- `Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/BanController.java` - 管理后台封禁控制器
- `Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/BanFrontController.java` - 移动端封禁控制器
- `Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/BlacklistController.java` - 管理后台黑名单控制器
- `Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/service/impl/LoginServiceImpl.java` - 登录服务(含封禁检查)
- `Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/ConversationServiceImpl.java` - 会话服务(含黑名单检查)
- `Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/LiveRoomController.java` - 直播间控制器(含封禁检查)
### 管理后台前端文件
- `Zhibo/admin/src/views/ban/userBan.vue` - 用户封禁管理页面
- `Zhibo/admin/src/views/ban/roomBan.vue` - 房间封禁管理页面
- `Zhibo/admin/src/views/blacklist/user.vue` - 用户黑名单页面
- `Zhibo/admin/src/views/blacklist/room.vue` - 房间黑名单页面
- `Zhibo/admin/src/api/ban.js` - 封禁API
- `Zhibo/admin/src/api/blacklist.js` - 黑名单API
### Android文件
- `android-app/app/src/main/java/com/example/livestreaming/BlacklistActivity.java` - 黑名单管理页面
- `android-app/app/src/main/java/com/example/livestreaming/UserProfileActivity.java` - 用户主页(含拉黑功能)
- `android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java` - 直播间详情(含房间封禁检查)
- `android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java` - API接口定义
### SQL脚本
- `ban_system_tables.sql` - 创建数据库表
- `add_ban_menus.sql` - 添加菜单配置
## 部署步骤
1. **执行数据库脚本**
```bash
mysql -u root -p zhibo < ban_system_tables.sql
mysql -u root -p zhibo < add_ban_menus.sql
```
2. **部署后端**
```bash
deploy_ban_system.bat
```
3. **部署管理后台前端**
- 重新编译部署前端
4. **编译Android App**
- 重新编译APK

View File

@ -0,0 +1,592 @@
# 直播平台功能交互逻辑报告
## 概述
本报告详细描述直播平台三大核心模块的功能交互逻辑:
1. **敏感词管理** - 内容安全过滤系统
2. **消息/粉丝团/群组** - 社交通讯系统
3. **封禁系统** - 用户与房间管理
---
## 一、敏感词管理模块
### 1.1 功能概述
敏感词管理用于过滤平台内的违规内容,保障平台内容安全。
### 1.2 数据表结构
**表名**: `eb_sensitive_word`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 主键ID |
| word | varchar(128) | 敏感词内容 |
| category | varchar(32) | 分类(default/spam/illegal) |
| level | tinyint | 级别(1轻度/2中度/3重度) |
| action | tinyint | 处理方式(1替换/2拦截/3警告) |
| replace_text | varchar(32) | 替换文本(默认***) |
| status | tinyint | 状态(0禁用/1启用) |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
### 1.3 管理端功能
**API路径**: `/api/admin/sensitive/word`
| 接口 | 方法 | 功能 |
|------|------|------|
| /list | GET | 敏感词列表(支持分页、关键词搜索、时间筛选) |
| /add | POST | 添加敏感词 |
| /update | POST | 更新敏感词 |
| /delete/{id} | POST | 删除敏感词 |
| /status/{id} | POST | 切换启用/禁用状态 |
### 1.4 交互流程图
```
┌─────────────────────────────────────────────────────────────────┐
│ 敏感词管理流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 管理员 │───▶│ 添加敏感词│───▶│ 存入数据库│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 用户发送 │───▶│ 内容检测 │───▶│ 匹配敏感词│───▶│ 执行处理 │ │
│ │ 消息/弹幕│ │ │ │ │ │ (替换/拦截)│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.5 应用场景
敏感词过滤应用于以下场景:
- 直播间弹幕消息
- 私聊消息内容
- 粉丝团群聊消息
- 动态发布内容
- 用户昵称/签名
---
## 二、消息/粉丝团/群组模块
### 2.1 私聊消息系统
#### 2.1.1 数据表结构
**会话表**: `eb_conversation`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 会话ID |
| user1_id | int | 用户1 ID |
| user2_id | int | 用户2 ID |
| last_message | varchar(255) | 最后一条消息预览 |
| last_message_time | datetime | 最后消息时间 |
| user1_unread_count | int | 用户1未读数 |
| user2_unread_count | int | 用户2未读数 |
| user1_deleted | tinyint | 用户1是否删除 |
| user2_deleted | tinyint | 用户2是否删除 |
**私聊消息表**: `eb_private_message`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 消息ID |
| conversation_id | bigint | 会话ID |
| sender_id | int | 发送者ID |
| receiver_id | int | 接收者ID |
| content | text | 消息内容 |
| message_type | varchar(20) | 消息类型(text/image/voice) |
| media_url | varchar(500) | 媒体URL |
| status | varchar(20) | 状态(sent/read) |
| is_recalled | tinyint | 是否撤回 |
#### 2.1.2 移动端API
**路径**: `/api/front/chat`
| 接口 | 方法 | 功能 |
|------|------|------|
| /conversations | GET | 获取会话列表 |
| /conversations/{id} | GET | 获取会话详情 |
| /conversations/{id}/messages | GET | 获取消息列表 |
| /conversations/{id}/messages | POST | 发送消息 |
| /conversations/{id}/read | POST | 标记已读 |
| /messages/{id}/recall | POST | 撤回消息(2分钟内) |
#### 2.1.3 管理端API
**路径**: `/api/admin/chat`
| 接口 | 方法 | 功能 |
|------|------|------|
| /conversations | GET | 会话列表(支持搜索) |
| /conversations/{id}/messages | GET | 查看会话消息 |
| /conversations/{id} | DELETE | 删除会话 |
| /messages/{id} | DELETE | 删除单条消息 |
| /statistics | GET | 私聊统计数据 |
#### 2.1.4 私聊交互流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 私聊消息发送流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 用户A │──▶│ 检查黑名单 │──▶│ 敏感词过滤 │──▶│ 保存消息 │ │
│ │ 发消息 │ │ │ │ │ │ │ │
│ └────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ │
│ │ 黑名单拦截 │ │ 更新会话 │ │
│ │ 返回错误 │ │ 推送通知 │ │
│ └────────────┘ └────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────┐ │
│ │ 用户B收到 │ │
│ │ 消息通知 │ │
│ └────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
### 2.2 粉丝团系统
#### 2.2.1 数据表结构
**粉丝团表**: `eb_fan_group`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 粉丝团ID |
| anchor_id | int | 主播ID |
| anchor_name | varchar(64) | 主播昵称 |
| name | varchar(64) | 粉丝团名称 |
| badge | varchar(32) | 粉丝团徽章 |
| badge_color | varchar(16) | 徽章颜色 |
| member_count | int | 成员数量 |
| level | int | 粉丝团等级 |
| status | tinyint | 状态(0解散/1正常) |
**粉丝团成员表**: `eb_fan_group_member`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 成员记录ID |
| group_id | int | 粉丝团ID |
| uid | int | 用户ID |
| nickname | varchar(64) | 成员昵称 |
| level | int | 成员等级(1-10) |
| intimacy | int | 亲密度 |
| status | tinyint | 状态 |
| join_time | datetime | 加入时间 |
**群聊消息表**: `eb_group_message`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 消息ID |
| group_id | int | 粉丝团ID |
| sender_id | int | 发送者ID |
| content | text | 消息内容 |
| message_type | varchar(20) | 消息类型 |
| is_deleted | tinyint | 是否删除 |
#### 2.2.2 管理端API
**路径**: `/api/admin/fan/group`
| 接口 | 方法 | 功能 |
|------|------|------|
| /list | GET | 粉丝团列表 |
| /detail/{id} | GET | 粉丝团详情 |
| /delete/{id} | POST | 删除粉丝团 |
| /batch-delete | POST | 批量删除 |
| /status/{id} | POST | 修改状态(解散/恢复) |
| /update/{id} | POST | 修改粉丝团信息 |
| /member/list | GET | 成员列表 |
| /member/delete/{id} | POST | 删除成员 |
| /member/update-level/{id} | POST | 修改成员等级 |
| /message/list | GET | 聊天记录列表 |
| /message/delete/{id} | POST | 删除聊天记录 |
| /statistics | GET | 统计数据 |
#### 2.2.3 粉丝团交互流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 粉丝团系统流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 【创建粉丝团】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 主播 │──▶│ 设置名称 │──▶│ 创建粉丝团 │ │
│ │ 开播 │ │ 徽章/颜色 │ │ │ │
│ └────────┘ └────────────┘ └────────────┘ │
│ │
│ 【加入粉丝团】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 用户 │──▶│ 查看粉丝团 │──▶│ 支付加入费 │──▶│ 成为成员 │ │
│ │ │ │ 信息 │ │ (可选) │ │ 等级=1 │ │
│ └────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 【等级提升】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 成员 │──▶│ 送礼/互动 │──▶│ 增加亲密度 │──▶│ 等级提升 │ │
│ │ │ │ │ │ │ │ (1-10级) │ │
│ └────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 【群聊功能】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 成员 │──▶│ 发送消息 │──▶│ 敏感词过滤 │──▶│ 群内广播 │ │
│ │ │ │ │ │ │ │ │ │
│ └────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 三、封禁系统模块
### 3.1 系统概述
封禁系统分为两大类:
1. **平台封禁** - 管理员对用户/房间的封禁eb_user_ban / eb_room_ban
2. **用户黑名单** - 用户间的互相拉黑eb_user_blacklist / eb_room_blacklist
### 3.2 数据表结构
#### 3.2.1 用户封禁表 `eb_user_ban`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 记录ID |
| user_id | int | 被封禁用户ID |
| ban_type | varchar(20) | 封禁类型(permanent永久/temporary临时) |
| reason | varchar(500) | 封禁原因 |
| duration_days | int | 封禁天数(临时封禁) |
| expire_time | datetime | 解封时间 |
| operator_id | int | 操作员ID |
| status | tinyint | 状态(0已解封/1封禁中) |
#### 3.2.2 房间封禁表 `eb_room_ban`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigint | 记录ID |
| room_id | int | 被封禁房间ID |
| ban_type | varchar(20) | 封禁类型 |
| reason | varchar(500) | 封禁原因 |
| duration_days | int | 封禁天数 |
| expire_time | datetime | 解封时间 |
| operator_id | int | 操作员ID |
| status | tinyint | 状态 |
#### 3.2.3 用户黑名单表 `eb_user_blacklist`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 记录ID |
| user_id | int | 拉黑发起者ID |
| blocked_user_id | int | 被拉黑用户ID |
| blocker_nickname | varchar(64) | 发起者昵称 |
| blocked_nickname | varchar(64) | 被拉黑者昵称 |
| create_time | datetime | 创建时间 |
#### 3.2.4 房间黑名单表 `eb_room_blacklist`
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 记录ID |
| room_id | int | 房间ID |
| room_name | varchar(128) | 房间名称 |
| blocked_user_id | int | 被拉黑用户ID |
| blocked_user_nickname | varchar(64) | 被拉黑用户昵称 |
| operator_id | int | 操作者ID(主播) |
| create_time | datetime | 创建时间 |
### 3.3 管理端API
#### 3.3.1 封禁管理 `/api/admin/ban`
| 接口 | 方法 | 功能 |
|------|------|------|
| /user/list | GET | 用户封禁列表 |
| /user/add | POST | 封禁用户 |
| /user/unban/{id} | POST | 解除用户封禁 |
| /user/delete/{id} | POST | 删除封禁记录 |
| /user/batch-delete | POST | 批量删除 |
| /user/check/{userId} | GET | 检查用户封禁状态 |
| /room/list | GET | 房间封禁列表 |
| /room/add | POST | 封禁房间 |
| /room/unban/{id} | POST | 解除房间封禁 |
| /room/delete/{id} | POST | 删除封禁记录 |
| /room/batch-delete | POST | 批量删除 |
| /room/check/{roomId} | GET | 检查房间封禁状态 |
#### 3.3.2 黑名单管理 `/api/admin/blacklist`
| 接口 | 方法 | 功能 |
|------|------|------|
| /user/list | GET | 用户黑名单列表 |
| /user/delete/{id} | POST | 删除用户黑名单记录 |
| /user/batch-delete | POST | 批量删除 |
| /room/list | GET | 房间黑名单列表 |
| /room/delete/{id} | POST | 删除房间黑名单记录 |
| /room/batch-delete | POST | 批量删除 |
### 3.4 移动端API
**路径**: `/api/front/ban`
| 接口 | 方法 | 功能 |
|------|------|------|
| /check/me | GET | 检查当前用户封禁状态 |
| /check/user/{userId} | GET | 检查指定用户封禁状态 |
| /check/room/{roomId} | GET | 检查房间封禁状态 |
| /blacklist/list | GET | 获取我的黑名单列表 |
| /blacklist/add | POST | 添加用户到黑名单 |
| /blacklist/remove | POST | 从黑名单移除用户 |
| /blacklist/check/{targetUserId} | GET | 检查黑名单状态 |
### 3.5 封禁检查触发点
封禁状态检查在以下场景自动触发:
| 场景 | 检查内容 | 实现位置 |
|------|----------|----------|
| 用户登录 | 检查用户是否被封禁 | LoginServiceImpl.checkUserBanStatus() |
| 发送私聊消息 | 检查双方黑名单状态 | ConversationServiceImpl.checkBlacklistStatus() |
| 发送直播间弹幕 | 检查用户封禁状态 | LiveRoomController.checkUserBanStatus() |
| 赠送礼物 | 检查用户封禁状态 | LiveRoomController.sendGift() |
| 进入直播间 | 检查房间封禁状态 | Android RoomDetailActivity |
### 3.6 封禁系统交互流程
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 封禁系统完整流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 【管理员封禁用户】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 管理员 │──▶│ 选择用户 │──▶│ 设置封禁 │──▶│ 更新用户 │ │
│ │ │ │ 填写原因 │ │ 类型/时长 │ │ status=0 │ │
│ └────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 【用户登录检查】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 用户 │──▶│ 提交登录 │──▶│ 检查封禁表 │──▶│ 封禁中? │ │
│ │ 登录 │ │ │ │ │ │ │ │
│ └────────┘ └────────────┘ └────────────┘ └─────┬──────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ │ │
│ ┌──────────┐ ┌──────────┐ │ │
│ │ 是:拒绝 │ │ 否:允许 │ │ │
│ │ 返回原因 │ │ 登录成功 │ │ │
│ └──────────┘ └──────────┘ │ │
│ │
│ 【用户拉黑用户】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 用户A │──▶│ 点击拉黑 │──▶│ 添加黑名单 │──▶│ 删除好友 │ │
│ │ │ │ 用户B │ │ 记录 │ │ 关系 │ │
│ └────────┘ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ 【私聊黑名单检查】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 用户A │──▶│ 发送消息 │──▶│ 检查黑名单 │ │
│ │ 发消息 │ │ 给用户B │ │ │ │
│ └────────┘ └────────────┘ └─────┬──────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ A拉黑了B │ │ B拉黑了A │ │ 无黑名单 │ │
│ │ 提示:您已│ │ 提示:对方│ │ 正常发送 │ │
│ │ 拉黑对方 │ │ 已拉黑您 │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 【房间封禁检查】 │
│ ┌────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ 用户 │──▶│ 进入直播间 │──▶│ 检查房间 │──▶│ 封禁中? │ │
│ │ │ │ │ │ 封禁状态 │ │ │ │
│ └────────┘ └────────────┘ └────────────┘ └─────┬──────┘ │
│ │ │
│ ┌──────────────────┼──────────────────┐ │
│ ▼ ▼ │ │
│ ┌──────────┐ ┌──────────┐ │ │
│ │ 是:禁止 │ │ 否:允许 │ │ │
│ │ 进入房间 │ │ 进入观看 │ │ │
│ └──────────┘ └──────────┘ │ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 四、三大模块联动关系
### 4.1 模块交互图
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 三大模块联动关系 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ 敏感词管理 │ │
│ │ │ │
│ │ • 内容过滤规则 │ │
│ │ • 违规词库维护 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────────┼────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 私聊消息 │ │ 粉丝团群聊 │ │ 直播间弹幕 │ │
│ │ │ │ │ │ │ │
│ │ • 发送前过滤敏感词 │ │ • 发送前过滤敏感词 │ │ • 发送前过滤敏感词 │ │
│ │ • 检查黑名单状态 │ │ • 检查成员状态 │ │ • 检查用户封禁 │ │
│ │ • 检查用户封禁 │ │ • 检查用户封禁 │ │ • 检查房间封禁 │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ └────────────────────────┼────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 封禁系统 │ │
│ │ │ │
│ │ • 用户封禁管理 │ │
│ │ • 房间封禁管理 │ │
│ │ • 用户黑名单 │ │
│ │ • 房间黑名单 │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 4.2 完整消息发送流程
```
用户发送消息
┌────────────────┐
│ 1. 用户登录检查 │ ──── 检查 eb_user_ban 表
└───────┬────────┘
│ 通过
┌────────────────┐
│ 2. 封禁状态检查 │ ──── 检查用户是否被平台封禁
└───────┬────────┘
│ 通过
┌────────────────┐
│ 3. 黑名单检查 │ ──── 检查 eb_user_blacklist 表
└───────┬────────┘ (私聊场景)
│ 通过
┌────────────────┐
│ 4. 敏感词过滤 │ ──── 检查 eb_sensitive_word 表
└───────┬────────┘
│ 通过/替换
┌────────────────┐
│ 5. 消息入库 │ ──── 保存到对应消息表
└───────┬────────┘
┌────────────────┐
│ 6. 推送通知 │ ──── WebSocket/推送服务
└────────────────┘
```
---
## 五、代码实现位置汇总
### 5.1 后端代码
| 模块 | 文件路径 | 说明 |
|------|----------|------|
| 敏感词管理 | `crmeb-admin/.../SensitiveWordController.java` | 管理端敏感词CRUD |
| 私聊管理 | `crmeb-admin/.../ChatManagementController.java` | 管理端私聊管理 |
| 私聊服务 | `crmeb-service/.../ConversationServiceImpl.java` | 私聊业务逻辑+黑名单检查 |
| 粉丝团管理 | `crmeb-admin/.../FanGroupController.java` | 管理端粉丝团管理 |
| 封禁管理 | `crmeb-admin/.../BanController.java` | 管理端封禁管理 |
| 黑名单管理 | `crmeb-admin/.../BlacklistController.java` | 管理端黑名单管理 |
| 移动端封禁 | `crmeb-front/.../BanFrontController.java` | 移动端封禁API |
| 登录服务 | `crmeb-front/.../LoginServiceImpl.java` | 登录时封禁检查 |
| 直播间控制 | `crmeb-front/.../LiveRoomController.java` | 弹幕/礼物封禁检查 |
### 5.2 前端代码
| 模块 | 文件路径 | 说明 |
|------|----------|------|
| 敏感词页面 | `admin/src/views/sensitiveWord/list/index.vue` | 敏感词管理页面 |
| 敏感词API | `admin/src/api/sensitiveWord.js` | 敏感词API接口 |
| 粉丝团页面 | `admin/src/views/fanGroup/list/index.vue` | 粉丝团管理页面 |
| 粉丝团API | `admin/src/api/fanGroup.js` | 粉丝团API接口 |
| 封禁页面 | `admin/src/views/ban/` | 封禁管理页面 |
| 黑名单页面 | `admin/src/views/blacklist/` | 黑名单管理页面 |
### 5.3 Android代码
| 模块 | 文件路径 | 说明 |
|------|----------|------|
| API接口 | `ApiService.java` | 封禁/黑名单API定义 |
| 直播间 | `RoomDetailActivity.java` | 进入房间封禁检查 |
| 粉丝团聊天 | `GroupChatActivity.java` | 粉丝团群聊功能 |
| 消息列表 | `MessagesActivity.java` | 消息列表页面 |
---
## 六、待完善功能建议
### 6.1 敏感词模块
1. **实时过滤服务** - 目前敏感词表已建立,建议实现统一的敏感词过滤服务类
2. **分类管理** - 支持按分类(spam/illegal/abuse)管理敏感词
3. **批量导入** - 支持Excel/TXT批量导入敏感词
4. **过滤日志** - 记录敏感词触发日志,便于分析
### 6.2 消息模块
1. **消息撤回通知** - 撤回消息时通知对方
2. **消息已读回执** - 显示消息已读状态
3. **群聊@功能** - 粉丝团群聊支持@成员
4. **消息搜索** - 支持历史消息搜索
### 6.3 封禁模块
1. **封禁申诉** - 用户可提交封禁申诉
2. **自动解封** - 临时封禁到期自动解封定时任务
3. **封禁通知** - 封禁/解封时推送通知给用户
4. **封禁统计** - 封禁数据统计报表
---
## 七、总结
本报告详细描述了直播平台三大核心模块的功能设计和交互逻辑:
1. **敏感词管理** - 提供内容安全过滤能力,保障平台内容合规
2. **消息/粉丝团/群组** - 构建完整的社交通讯体系,增强用户粘性
3. **封禁系统** - 提供多层次的用户管理能力,维护平台秩序
三大模块相互配合,形成完整的平台安全和社交体系。
---
*报告生成时间: 2026-01-05*