feat: 实现管理端与移动端历史记录功能互通
This commit is contained in:
parent
4890c98f85
commit
87674d736e
65
Zhibo/admin/src/api/userActivity.js
Normal file
65
Zhibo/admin/src/api/userActivity.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* 用户活动记录 API
|
||||||
|
* 包括观看历史、点赞记录、关注记录、收藏记录等
|
||||||
|
*/
|
||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户关注记录
|
||||||
|
* @param {Object} params - { userId, page, limit }
|
||||||
|
*/
|
||||||
|
export function getFollowRecords(params) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/user/follow/records',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户点赞记录
|
||||||
|
* @param {Object} params - { userId, targetType, page, limit }
|
||||||
|
*/
|
||||||
|
export function getLikeRecords(params) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/user/like/records',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户查看历史
|
||||||
|
* @param {Object} params - { userId, targetType, page, limit }
|
||||||
|
*/
|
||||||
|
export function getViewHistory(params) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/user/view/history',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的作品
|
||||||
|
* @param {Object} params - { userId, page, limit }
|
||||||
|
*/
|
||||||
|
export function getCollectedWorks(params) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/user/collect/works',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户活动统计
|
||||||
|
* @param {Object} params - { userId }
|
||||||
|
*/
|
||||||
|
export function getUserActivityStats(params) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/user/activity/stats',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -278,6 +278,17 @@
|
||||||
<el-table-column prop="createTime" label="查看时间" width="180" />
|
<el-table-column prop="createTime" label="查看时间" width="180" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<!-- 收藏记录 -->
|
||||||
|
<el-tab-pane name="10" label="收藏记录">
|
||||||
|
<el-table :data="tableData" size="small" class="mt20">
|
||||||
|
<el-table-column prop="workId" label="作品ID" width="100" />
|
||||||
|
<el-table-column prop="title" label="作品标题" min-width="200" />
|
||||||
|
<el-table-column prop="authorName" label="作者" width="120" />
|
||||||
|
<el-table-column prop="likeCount" label="点赞数" width="100" />
|
||||||
|
<el-table-column prop="collectCount" label="收藏数" width="100" />
|
||||||
|
<el-table-column prop="collectTime" label="收藏时间" width="180" />
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<div class="block" v-if="tabsVal != '0'">
|
<div class="block" v-if="tabsVal != '0'">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
|
|
@ -309,6 +320,7 @@
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
import { infobyconditionApi, topdetailApi } from '@/api/user';
|
import { infobyconditionApi, topdetailApi } from '@/api/user';
|
||||||
import { integralListApi } from '@/api/marketing';
|
import { integralListApi } from '@/api/marketing';
|
||||||
|
import { getFollowRecords as fetchFollowRecords, getLikeRecords as fetchLikeRecords, getViewHistory as fetchViewHistory, getCollectedWorks as fetchCollectedWorks } from '@/api/userActivity';
|
||||||
export default {
|
export default {
|
||||||
name: 'detailUser',
|
name: 'detailUser',
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -349,6 +361,9 @@ export default {
|
||||||
} else if (val == '9') {
|
} else if (val == '9') {
|
||||||
// 查看历史
|
// 查看历史
|
||||||
this.getViewHistory();
|
this.getViewHistory();
|
||||||
|
} else if (val == '10') {
|
||||||
|
// 收藏记录
|
||||||
|
this.getCollectedWorks();
|
||||||
} else {
|
} else {
|
||||||
this.getListData();
|
this.getListData();
|
||||||
}
|
}
|
||||||
|
|
@ -365,6 +380,8 @@ export default {
|
||||||
this.getLikeRecords();
|
this.getLikeRecords();
|
||||||
} else if (this.tabsVal == '9') {
|
} else if (this.tabsVal == '9') {
|
||||||
this.getViewHistory();
|
this.getViewHistory();
|
||||||
|
} else if (this.tabsVal == '10') {
|
||||||
|
this.getCollectedWorks();
|
||||||
} else {
|
} else {
|
||||||
this.getListData();
|
this.getListData();
|
||||||
}
|
}
|
||||||
|
|
@ -403,17 +420,13 @@ export default {
|
||||||
},
|
},
|
||||||
// 获取关注记录
|
// 获取关注记录
|
||||||
getFollowRecords() {
|
getFollowRecords() {
|
||||||
this.$http({
|
fetchFollowRecords({
|
||||||
url: '/admin/user/follow/records',
|
userId: this.userNo,
|
||||||
method: 'get',
|
page: this.paginationData.page,
|
||||||
params: {
|
limit: this.paginationData.limit,
|
||||||
userId: this.userNo,
|
|
||||||
page: this.paginationData.page,
|
|
||||||
limit: this.paginationData.limit,
|
|
||||||
},
|
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
this.tableData = res.data.list || [];
|
this.tableData = res.list || [];
|
||||||
this.paginationData.total = res.data.total || 0;
|
this.paginationData.total = res.total || 0;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.paginationData.total = 0;
|
this.paginationData.total = 0;
|
||||||
|
|
@ -421,17 +434,13 @@ export default {
|
||||||
},
|
},
|
||||||
// 获取点赞记录
|
// 获取点赞记录
|
||||||
getLikeRecords() {
|
getLikeRecords() {
|
||||||
this.$http({
|
fetchLikeRecords({
|
||||||
url: '/admin/user/like/records',
|
userId: this.userNo,
|
||||||
method: 'get',
|
page: this.paginationData.page,
|
||||||
params: {
|
limit: this.paginationData.limit,
|
||||||
userId: this.userNo,
|
|
||||||
page: this.paginationData.page,
|
|
||||||
limit: this.paginationData.limit,
|
|
||||||
},
|
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
this.tableData = res.data.list || [];
|
this.tableData = res.list || [];
|
||||||
this.paginationData.total = res.data.total || 0;
|
this.paginationData.total = res.total || 0;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.paginationData.total = 0;
|
this.paginationData.total = 0;
|
||||||
|
|
@ -439,17 +448,27 @@ export default {
|
||||||
},
|
},
|
||||||
// 获取查看历史
|
// 获取查看历史
|
||||||
getViewHistory() {
|
getViewHistory() {
|
||||||
this.$http({
|
fetchViewHistory({
|
||||||
url: '/admin/user/view/history',
|
userId: this.userNo,
|
||||||
method: 'get',
|
page: this.paginationData.page,
|
||||||
params: {
|
limit: this.paginationData.limit,
|
||||||
userId: this.userNo,
|
|
||||||
page: this.paginationData.page,
|
|
||||||
limit: this.paginationData.limit,
|
|
||||||
},
|
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
this.tableData = res.data.list || [];
|
this.tableData = res.list || [];
|
||||||
this.paginationData.total = res.data.total || 0;
|
this.paginationData.total = res.total || 0;
|
||||||
|
}).catch(() => {
|
||||||
|
this.tableData = [];
|
||||||
|
this.paginationData.total = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 获取收藏记录
|
||||||
|
getCollectedWorks() {
|
||||||
|
fetchCollectedWorks({
|
||||||
|
userId: this.userNo,
|
||||||
|
page: this.paginationData.page,
|
||||||
|
limit: this.paginationData.limit,
|
||||||
|
}).then((res) => {
|
||||||
|
this.tableData = res.list || [];
|
||||||
|
this.paginationData.total = res.total || 0;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.tableData = [];
|
this.tableData = [];
|
||||||
this.paginationData.total = 0;
|
this.paginationData.total = 0;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package com.zbkj.admin.controller;
|
||||||
|
|
||||||
import com.zbkj.common.page.CommonPage;
|
import com.zbkj.common.page.CommonPage;
|
||||||
import com.zbkj.common.result.CommonResult;
|
import com.zbkj.common.result.CommonResult;
|
||||||
|
import com.zbkj.service.service.UserActivityRecordService;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import io.swagger.annotations.ApiParam;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
@ -15,7 +17,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户活动记录控制器
|
* 用户活动记录控制器(管理后台)
|
||||||
* 包括关注记录、点赞记录、查看历史等
|
* 包括关注记录、点赞记录、查看历史等
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|
@ -28,6 +30,9 @@ public class UserActivityController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private JdbcTemplate jdbcTemplate;
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserActivityRecordService userActivityRecordService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户的关注记录
|
* 获取用户的关注记录
|
||||||
*/
|
*/
|
||||||
|
|
@ -38,38 +43,7 @@ public class UserActivityController {
|
||||||
@RequestParam(defaultValue = "1") Integer page,
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
@RequestParam(defaultValue = "10") Integer limit) {
|
@RequestParam(defaultValue = "10") Integer limit) {
|
||||||
try {
|
try {
|
||||||
// 计算偏移量
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getFollowRecords(userId, page, limit);
|
||||||
int offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
// 查询总数
|
|
||||||
String countSql = "SELECT COUNT(*) FROM eb_follow_record WHERE follower_id = ?";
|
|
||||||
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
|
||||||
|
|
||||||
// 查询列表
|
|
||||||
String sql = "SELECT " +
|
|
||||||
"fr.id, " +
|
|
||||||
"fr.follower_id as followerId, " +
|
|
||||||
"fr.follower_nickname as followerNickname, " +
|
|
||||||
"fr.followed_id as followedId, " +
|
|
||||||
"fr.followed_nickname as followedNickname, " +
|
|
||||||
"fr.follow_status as followStatus, " +
|
|
||||||
"fr.is_deleted as isDeleted, " +
|
|
||||||
"fr.create_time as createTime " +
|
|
||||||
"FROM eb_follow_record fr " +
|
|
||||||
"WHERE fr.follower_id = ? " +
|
|
||||||
"ORDER BY fr.create_time DESC " +
|
|
||||||
"LIMIT ? OFFSET ?";
|
|
||||||
|
|
||||||
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, limit, offset);
|
|
||||||
|
|
||||||
// 封装分页结果
|
|
||||||
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);
|
return CommonResult.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取用户关注记录失败: userId={}", userId, e);
|
log.error("获取用户关注记录失败: userId={}", userId, e);
|
||||||
|
|
@ -84,76 +58,12 @@ public class UserActivityController {
|
||||||
@GetMapping("/like/records")
|
@GetMapping("/like/records")
|
||||||
public CommonResult<CommonPage<Map<String, Object>>> getLikeRecords(
|
public CommonResult<CommonPage<Map<String, Object>>> getLikeRecords(
|
||||||
@RequestParam Integer userId,
|
@RequestParam Integer userId,
|
||||||
|
@ApiParam(value = "目标类型:room-直播间, work-作品, wish-心愿")
|
||||||
|
@RequestParam(required = false) String targetType,
|
||||||
@RequestParam(defaultValue = "1") Integer page,
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
@RequestParam(defaultValue = "10") Integer limit) {
|
@RequestParam(defaultValue = "10") Integer limit) {
|
||||||
try {
|
try {
|
||||||
// 计算偏移量
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getLikeRecords(userId, targetType, page, limit);
|
||||||
int offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
// 查询总数(从多个表统计)
|
|
||||||
int totalRoomLikes = 0;
|
|
||||||
int totalWorkLikes = 0;
|
|
||||||
int totalWishLikes = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
String countRoomSql = "SELECT COUNT(*) FROM eb_live_room_like WHERE user_id = ?";
|
|
||||||
totalRoomLikes = jdbcTemplate.queryForObject(countRoomSql, Integer.class, userId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("eb_live_room_like表不存在或查询失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String countWorkSql = "SELECT COUNT(*) FROM eb_work_like WHERE user_id = ?";
|
|
||||||
totalWorkLikes = jdbcTemplate.queryForObject(countWorkSql, Integer.class, userId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("eb_work_like表不存在或查询失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String countWishSql = "SELECT COUNT(*) FROM eb_wish_like WHERE user_id = ?";
|
|
||||||
totalWishLikes = jdbcTemplate.queryForObject(countWishSql, Integer.class, userId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("eb_wish_like表不存在或查询失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
int total = totalRoomLikes + totalWorkLikes + totalWishLikes;
|
|
||||||
|
|
||||||
// 联合查询所有点赞记录
|
|
||||||
StringBuilder sql = new StringBuilder();
|
|
||||||
|
|
||||||
// 直播间点赞
|
|
||||||
sql.append("SELECT 'room' as targetType, lr.id as targetId, lr.title as targetTitle, rl.create_time as createTime ");
|
|
||||||
sql.append("FROM eb_live_room_like rl ");
|
|
||||||
sql.append("LEFT JOIN eb_live_room lr ON rl.room_id = lr.id ");
|
|
||||||
sql.append("WHERE rl.user_id = ? ");
|
|
||||||
|
|
||||||
// 作品点赞
|
|
||||||
sql.append("UNION ALL ");
|
|
||||||
sql.append("SELECT 'work' as targetType, w.id as targetId, w.title as targetTitle, wl.create_time as createTime ");
|
|
||||||
sql.append("FROM eb_work_like wl ");
|
|
||||||
sql.append("LEFT JOIN eb_works w ON wl.work_id = w.id ");
|
|
||||||
sql.append("WHERE wl.user_id = ? ");
|
|
||||||
|
|
||||||
// 心愿点赞
|
|
||||||
sql.append("UNION ALL ");
|
|
||||||
sql.append("SELECT 'wish' as targetType, w.id as targetId, w.content as targetTitle, wl.create_time as createTime ");
|
|
||||||
sql.append("FROM eb_wish_like wl ");
|
|
||||||
sql.append("LEFT JOIN eb_wish w ON wl.wish_id = w.id ");
|
|
||||||
sql.append("WHERE wl.user_id = ? ");
|
|
||||||
|
|
||||||
sql.append("ORDER BY createTime DESC ");
|
|
||||||
sql.append("LIMIT ? OFFSET ?");
|
|
||||||
|
|
||||||
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), userId, userId, userId, limit, offset);
|
|
||||||
|
|
||||||
// 封装分页结果
|
|
||||||
CommonPage<Map<String, Object>> result = new CommonPage<>();
|
|
||||||
result.setList(list);
|
|
||||||
result.setTotal((long) total);
|
|
||||||
result.setPage(page);
|
|
||||||
result.setLimit(limit);
|
|
||||||
result.setTotalPage((int) Math.ceil((double) total / limit));
|
|
||||||
|
|
||||||
return CommonResult.success(result);
|
return CommonResult.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取用户点赞记录失败: userId={}", userId, e);
|
log.error("获取用户点赞记录失败: userId={}", userId, e);
|
||||||
|
|
@ -168,56 +78,49 @@ public class UserActivityController {
|
||||||
@GetMapping("/view/history")
|
@GetMapping("/view/history")
|
||||||
public CommonResult<CommonPage<Map<String, Object>>> getViewHistory(
|
public CommonResult<CommonPage<Map<String, Object>>> getViewHistory(
|
||||||
@RequestParam Integer userId,
|
@RequestParam Integer userId,
|
||||||
|
@ApiParam(value = "目标类型:room-直播间, work-作品, profile-用户主页")
|
||||||
|
@RequestParam(required = false) String targetType,
|
||||||
@RequestParam(defaultValue = "1") Integer page,
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
@RequestParam(defaultValue = "10") Integer limit) {
|
@RequestParam(defaultValue = "10") Integer limit) {
|
||||||
try {
|
try {
|
||||||
// 计算偏移量
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getViewHistory(userId, targetType, page, limit);
|
||||||
int offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
// 查询总数
|
|
||||||
String countSql = "SELECT COUNT(*) FROM eb_view_history WHERE user_id = ?";
|
|
||||||
Integer total = 0;
|
|
||||||
try {
|
|
||||||
total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("eb_view_history表不存在,返回空数据");
|
|
||||||
CommonPage<Map<String, Object>> result = new CommonPage<>();
|
|
||||||
result.setList(new java.util.ArrayList<>());
|
|
||||||
result.setTotal(0L);
|
|
||||||
result.setPage(page);
|
|
||||||
result.setLimit(limit);
|
|
||||||
result.setTotalPage(0);
|
|
||||||
return CommonResult.success(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询列表
|
|
||||||
String sql = "SELECT " +
|
|
||||||
"vh.id, " +
|
|
||||||
"vh.user_id as userId, " +
|
|
||||||
"vh.target_type as targetType, " +
|
|
||||||
"vh.target_id as targetId, " +
|
|
||||||
"vh.target_title as targetTitle, " +
|
|
||||||
"vh.view_duration as viewDuration, " +
|
|
||||||
"vh.create_time as createTime " +
|
|
||||||
"FROM eb_view_history vh " +
|
|
||||||
"WHERE vh.user_id = ? " +
|
|
||||||
"ORDER BY vh.create_time DESC " +
|
|
||||||
"LIMIT ? OFFSET ?";
|
|
||||||
|
|
||||||
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, limit, offset);
|
|
||||||
|
|
||||||
// 封装分页结果
|
|
||||||
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);
|
return CommonResult.success(result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取用户查看历史失败: userId={}", userId, e);
|
log.error("获取用户查看历史失败: userId={}", userId, e);
|
||||||
return CommonResult.failed("获取查看历史失败");
|
return CommonResult.failed("获取查看历史失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的作品
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取用户收藏的作品")
|
||||||
|
@GetMapping("/collect/works")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getCollectedWorks(
|
||||||
|
@RequestParam Integer userId,
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "10") Integer limit) {
|
||||||
|
try {
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getCollectedWorks(userId, page, limit);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户收藏作品失败: userId={}", userId, e);
|
||||||
|
return CommonResult.failed("获取收藏作品失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户活动统计
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取用户活动统计")
|
||||||
|
@GetMapping("/activity/stats")
|
||||||
|
public CommonResult<Map<String, Object>> getUserActivityStats(@RequestParam Integer userId) {
|
||||||
|
try {
|
||||||
|
Map<String, Object> stats = userActivityRecordService.getUserActivityStats(userId);
|
||||||
|
return CommonResult.success(stats);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户活动统计失败: userId={}", userId, e);
|
||||||
|
return CommonResult.failed("获取统计失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,254 @@
|
||||||
|
package com.zbkj.front.controller;
|
||||||
|
|
||||||
|
import com.zbkj.common.page.CommonPage;
|
||||||
|
import com.zbkj.common.result.CommonResult;
|
||||||
|
import com.zbkj.service.service.UserActivityRecordService;
|
||||||
|
import com.zbkj.service.service.UserService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import io.swagger.annotations.ApiParam;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户活动记录控制器(前端API)
|
||||||
|
* 提供观看历史、点赞记录、关注记录、收藏记录等功能
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("api/front/activity")
|
||||||
|
@Api(tags = "用户活动记录")
|
||||||
|
@Validated
|
||||||
|
public class UserActivityRecordController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserActivityRecordService userActivityRecordService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
// ==================== 观看历史 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录观看历史
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "记录观看历史")
|
||||||
|
@PostMapping("/view/record")
|
||||||
|
public CommonResult<Map<String, Object>> recordViewHistory(@RequestBody Map<String, Object> body) {
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetType = body.get("targetType") != null ? body.get("targetType").toString() : null;
|
||||||
|
String targetId = body.get("targetId") != null ? body.get("targetId").toString() : null;
|
||||||
|
String targetTitle = body.get("targetTitle") != null ? body.get("targetTitle").toString() : null;
|
||||||
|
Integer duration = body.get("duration") != null ? Integer.valueOf(body.get("duration").toString()) : 0;
|
||||||
|
|
||||||
|
if (targetType == null || targetId == null) {
|
||||||
|
return CommonResult.failed("参数不完整");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = userActivityRecordService.recordViewHistory(userId, targetType, targetId, targetTitle, duration);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("message", "记录成功");
|
||||||
|
return CommonResult.success(result);
|
||||||
|
} else {
|
||||||
|
return CommonResult.failed("记录失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取观看历史列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取观看历史列表")
|
||||||
|
@GetMapping("/view/history")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getViewHistory(
|
||||||
|
@ApiParam(value = "目标类型:room-直播间, work-作品, profile-用户主页")
|
||||||
|
@RequestParam(required = false) String targetType,
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getViewHistory(userId, targetType, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单条观看历史
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "删除单条观看历史")
|
||||||
|
@DeleteMapping("/view/history/{historyId}")
|
||||||
|
public CommonResult<String> deleteViewHistory(@PathVariable Long historyId) {
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = userActivityRecordService.deleteViewHistory(userId, historyId);
|
||||||
|
return success ? CommonResult.success("删除成功") : CommonResult.failed("删除失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空观看历史
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "清空观看历史")
|
||||||
|
@DeleteMapping("/view/history")
|
||||||
|
public CommonResult<String> clearViewHistory(
|
||||||
|
@ApiParam(value = "目标类型(可选):room-直播间, work-作品, profile-用户主页")
|
||||||
|
@RequestParam(required = false) String targetType) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = userActivityRecordService.clearViewHistory(userId, targetType);
|
||||||
|
return success ? CommonResult.success("清空成功") : CommonResult.failed("清空失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 点赞记录 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞记录列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取点赞记录列表")
|
||||||
|
@GetMapping("/like/records")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getLikeRecords(
|
||||||
|
@ApiParam(value = "目标类型:room-直播间, work-作品, wish-心愿,不传则获取全部")
|
||||||
|
@RequestParam(required = false) String targetType,
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getLikeRecords(userId, targetType, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞的直播间列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取点赞的直播间列表")
|
||||||
|
@GetMapping("/like/rooms")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getLikedRooms(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getLikedRooms(userId, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞的作品列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取点赞的作品列表")
|
||||||
|
@GetMapping("/like/works")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getLikedWorks(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getLikedWorks(userId, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞的心愿列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取点赞的心愿列表")
|
||||||
|
@GetMapping("/like/wishes")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getLikedWishes(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getLikedWishes(userId, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 关注记录 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取关注记录列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取关注记录列表")
|
||||||
|
@GetMapping("/follow/records")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getFollowRecords(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getFollowRecords(userId, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 收藏记录 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取收藏的作品列表
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取收藏的作品列表")
|
||||||
|
@GetMapping("/collect/works")
|
||||||
|
public CommonResult<CommonPage<Map<String, Object>>> getCollectedWorks(
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "20") Integer pageSize) {
|
||||||
|
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
CommonPage<Map<String, Object>> result = userActivityRecordService.getCollectedWorks(userId, page, pageSize);
|
||||||
|
return CommonResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 统计信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户活动统计
|
||||||
|
*/
|
||||||
|
@ApiOperation(value = "获取用户活动统计")
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public CommonResult<Map<String, Object>> getUserActivityStats() {
|
||||||
|
Integer userId = userService.getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return CommonResult.failed("请先登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> stats = userActivityRecordService.getUserActivityStats(userId);
|
||||||
|
return CommonResult.success(stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.zbkj.service.service;
|
||||||
|
|
||||||
|
import com.zbkj.common.page.CommonPage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户活动记录服务接口
|
||||||
|
* 统一管理用户的各类历史记录:观看历史、点赞记录、搜索历史等
|
||||||
|
*/
|
||||||
|
public interface UserActivityRecordService {
|
||||||
|
|
||||||
|
// ==================== 观看历史 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录观看历史
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param targetType 目标类型:room-直播间, work-作品, profile-用户主页
|
||||||
|
* @param targetId 目标ID
|
||||||
|
* @param targetTitle 目标标题
|
||||||
|
* @param duration 观看时长(秒)
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
boolean recordViewHistory(Integer userId, String targetType, String targetId, String targetTitle, Integer duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户观看历史列表
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param targetType 目标类型(可选,null表示全部)
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 观看历史列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getViewHistory(Integer userId, String targetType, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单条观看历史
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param historyId 历史记录ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
boolean deleteViewHistory(Integer userId, Long historyId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空用户观看历史
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param targetType 目标类型(可选,null表示清空全部)
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
boolean clearViewHistory(Integer userId, String targetType);
|
||||||
|
|
||||||
|
// ==================== 点赞记录 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户点赞记录列表(包含直播间、作品、心愿等)
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param targetType 目标类型(可选):room-直播间, work-作品, wish-心愿
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 点赞记录列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getLikeRecords(Integer userId, String targetType, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户点赞的直播间列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getLikedRooms(Integer userId, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户点赞的作品列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getLikedWorks(Integer userId, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户点赞的心愿列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getLikedWishes(Integer userId, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
// ==================== 关注记录 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户关注记录列表
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param page 页码
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @return 关注记录列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getFollowRecords(Integer userId, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
// ==================== 收藏记录 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的作品列表
|
||||||
|
*/
|
||||||
|
CommonPage<Map<String, Object>> getCollectedWorks(Integer userId, Integer page, Integer pageSize);
|
||||||
|
|
||||||
|
// ==================== 统计信息 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户活动统计
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 统计信息(观看数、点赞数、关注数、收藏数等)
|
||||||
|
*/
|
||||||
|
Map<String, Object> getUserActivityStats(Integer userId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,488 @@
|
||||||
|
package com.zbkj.service.service.impl;
|
||||||
|
|
||||||
|
import com.zbkj.common.page.CommonPage;
|
||||||
|
import com.zbkj.service.service.UserActivityRecordService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户活动记录服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class UserActivityRecordServiceImpl implements UserActivityRecordService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
// ==================== 观看历史 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean recordViewHistory(Integer userId, String targetType, String targetId,
|
||||||
|
String targetTitle, Integer duration) {
|
||||||
|
if (userId == null || targetType == null || targetId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 检查是否已存在相同记录(同一用户、同一目标)
|
||||||
|
String checkSql = "SELECT id FROM eb_view_history WHERE user_id = ? AND target_type = ? AND target_id = ?";
|
||||||
|
List<Map<String, Object>> existing = jdbcTemplate.queryForList(checkSql, userId, targetType, targetId);
|
||||||
|
|
||||||
|
if (!existing.isEmpty()) {
|
||||||
|
// 更新已有记录
|
||||||
|
String updateSql = "UPDATE eb_view_history SET target_title = ?, view_duration = COALESCE(view_duration, 0) + ?, " +
|
||||||
|
"update_time = NOW() WHERE user_id = ? AND target_type = ? AND target_id = ?";
|
||||||
|
jdbcTemplate.update(updateSql, targetTitle, duration != null ? duration : 0, userId, targetType, targetId);
|
||||||
|
} else {
|
||||||
|
// 插入新记录
|
||||||
|
String insertSql = "INSERT INTO eb_view_history (user_id, target_type, target_id, target_title, view_duration, create_time, update_time) " +
|
||||||
|
"VALUES (?, ?, ?, ?, ?, NOW(), NOW())";
|
||||||
|
jdbcTemplate.update(insertSql, userId, targetType, targetId, targetTitle, duration != null ? duration : 0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("记录观看历史失败: userId={}, targetType={}, targetId={}", userId, targetType, targetId, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getViewHistory(Integer userId, String targetType,
|
||||||
|
Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_view_history WHERE user_id = ?");
|
||||||
|
StringBuilder querySql = new StringBuilder(
|
||||||
|
"SELECT id, user_id as userId, target_type as targetType, target_id as targetId, " +
|
||||||
|
"target_title as targetTitle, view_duration as viewDuration, create_time as createTime, " +
|
||||||
|
"update_time as updateTime FROM eb_view_history WHERE user_id = ?");
|
||||||
|
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
params.add(userId);
|
||||||
|
|
||||||
|
if (targetType != null && !targetType.isEmpty()) {
|
||||||
|
countSql.append(" AND target_type = ?");
|
||||||
|
querySql.append(" AND target_type = ?");
|
||||||
|
params.add(targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
querySql.append(" ORDER BY update_time DESC LIMIT ? OFFSET ?");
|
||||||
|
|
||||||
|
// 查询总数
|
||||||
|
Integer total = jdbcTemplate.queryForObject(countSql.toString(), Integer.class, params.toArray());
|
||||||
|
|
||||||
|
// 查询列表
|
||||||
|
params.add(pageSize);
|
||||||
|
params.add(offset);
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(querySql.toString(), params.toArray());
|
||||||
|
|
||||||
|
// 补充目标详情
|
||||||
|
enrichViewHistoryDetails(list);
|
||||||
|
|
||||||
|
return buildPage(list, total != null ? total : 0, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取观看历史失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 补充观看历史的目标详情
|
||||||
|
*/
|
||||||
|
private void enrichViewHistoryDetails(List<Map<String, Object>> list) {
|
||||||
|
for (Map<String, Object> item : list) {
|
||||||
|
String targetType = (String) item.get("targetType");
|
||||||
|
String targetId = String.valueOf(item.get("targetId"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ("room".equals(targetType)) {
|
||||||
|
// 查询直播间信息
|
||||||
|
String sql = "SELECT title, streamer_name as streamerName, is_live as isLive, cover_image as coverImage " +
|
||||||
|
"FROM eb_live_room WHERE id = ?";
|
||||||
|
List<Map<String, Object>> rooms = jdbcTemplate.queryForList(sql, Integer.parseInt(targetId));
|
||||||
|
if (!rooms.isEmpty()) {
|
||||||
|
item.putAll(rooms.get(0));
|
||||||
|
}
|
||||||
|
} else if ("work".equals(targetType)) {
|
||||||
|
// 查询作品信息
|
||||||
|
String sql = "SELECT title, cover_image as coverImage, user_id as authorId FROM eb_works WHERE id = ?";
|
||||||
|
List<Map<String, Object>> works = jdbcTemplate.queryForList(sql, Long.parseLong(targetId));
|
||||||
|
if (!works.isEmpty()) {
|
||||||
|
item.putAll(works.get(0));
|
||||||
|
}
|
||||||
|
} else if ("profile".equals(targetType)) {
|
||||||
|
// 查询用户信息
|
||||||
|
String sql = "SELECT nickname, avatar FROM eb_user WHERE uid = ?";
|
||||||
|
List<Map<String, Object>> users = jdbcTemplate.queryForList(sql, Integer.parseInt(targetId));
|
||||||
|
if (!users.isEmpty()) {
|
||||||
|
item.putAll(users.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("补充观看历史详情失败: targetType={}, targetId={}", targetType, targetId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean deleteViewHistory(Integer userId, Long historyId) {
|
||||||
|
if (userId == null || historyId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String sql = "DELETE FROM eb_view_history WHERE id = ? AND user_id = ?";
|
||||||
|
int rows = jdbcTemplate.update(sql, historyId, userId);
|
||||||
|
return rows > 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除观看历史失败: userId={}, historyId={}", userId, historyId, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public boolean clearViewHistory(Integer userId, String targetType) {
|
||||||
|
if (userId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
StringBuilder sql = new StringBuilder("DELETE FROM eb_view_history WHERE user_id = ?");
|
||||||
|
List<Object> params = new ArrayList<>();
|
||||||
|
params.add(userId);
|
||||||
|
|
||||||
|
if (targetType != null && !targetType.isEmpty()) {
|
||||||
|
sql.append(" AND target_type = ?");
|
||||||
|
params.add(targetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
jdbcTemplate.update(sql.toString(), params.toArray());
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("清空观看历史失败: userId={}", userId, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 点赞记录 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getLikeRecords(Integer userId, String targetType,
|
||||||
|
Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型调用不同方法
|
||||||
|
if ("room".equals(targetType)) {
|
||||||
|
return getLikedRooms(userId, page, pageSize);
|
||||||
|
} else if ("work".equals(targetType)) {
|
||||||
|
return getLikedWorks(userId, page, pageSize);
|
||||||
|
} else if ("wish".equals(targetType)) {
|
||||||
|
return getLikedWishes(userId, page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取全部点赞记录(联合查询)
|
||||||
|
return getAllLikeRecords(userId, page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全部点赞记录(联合查询)
|
||||||
|
*/
|
||||||
|
private CommonPage<Map<String, Object>> getAllLikeRecords(Integer userId, Integer page, Integer pageSize) {
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
// 统计各类点赞总数
|
||||||
|
int roomLikes = countTableRows("eb_live_room_like", "user_id", userId);
|
||||||
|
int workLikes = countTableRows("eb_works_relation", "uid", userId, "type", "like");
|
||||||
|
int wishLikes = countTableRows("eb_wish_like", "user_id", userId);
|
||||||
|
int total = roomLikes + workLikes + wishLikes;
|
||||||
|
|
||||||
|
// 联合查询
|
||||||
|
String sql = "(" +
|
||||||
|
"SELECT 'room' as targetType, CAST(room_id AS CHAR) as targetId, lr.title as targetTitle, " +
|
||||||
|
"lr.cover_image as coverImage, lr.streamer_name as streamerName, lr.is_live as isLive, " +
|
||||||
|
"rl.create_time as createTime " +
|
||||||
|
"FROM eb_live_room_like rl " +
|
||||||
|
"LEFT JOIN eb_live_room lr ON rl.room_id = lr.id " +
|
||||||
|
"WHERE rl.user_id = ?" +
|
||||||
|
") UNION ALL (" +
|
||||||
|
"SELECT 'work' as targetType, CAST(wr.works_id AS CHAR) as targetId, w.title as targetTitle, " +
|
||||||
|
"w.cover_image as coverImage, NULL as streamerName, NULL as isLive, " +
|
||||||
|
"wr.create_time as createTime " +
|
||||||
|
"FROM eb_works_relation wr " +
|
||||||
|
"LEFT JOIN eb_works w ON wr.works_id = w.id " +
|
||||||
|
"WHERE wr.uid = ? AND wr.type = 'like'" +
|
||||||
|
") UNION ALL (" +
|
||||||
|
"SELECT 'wish' as targetType, CAST(wl.wish_id AS CHAR) as targetId, ws.content as targetTitle, " +
|
||||||
|
"NULL as coverImage, NULL as streamerName, NULL as isLive, " +
|
||||||
|
"wl.create_time as createTime " +
|
||||||
|
"FROM eb_wish_like wl " +
|
||||||
|
"LEFT JOIN eb_wishtree_wish ws ON wl.wish_id = ws.id " +
|
||||||
|
"WHERE wl.user_id = ?" +
|
||||||
|
") ORDER BY createTime DESC LIMIT ? OFFSET ?";
|
||||||
|
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, userId, userId, pageSize, offset);
|
||||||
|
|
||||||
|
return buildPage(list, total, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取全部点赞记录失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getLikedRooms(Integer userId, Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
String countSql = "SELECT COUNT(*) FROM eb_live_room_like WHERE user_id = ?";
|
||||||
|
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
||||||
|
|
||||||
|
String sql = "SELECT rl.id, rl.room_id as roomId, lr.title as roomTitle, " +
|
||||||
|
"lr.streamer_name as streamerName, lr.uid as streamerId, lr.cover_image as coverImage, " +
|
||||||
|
"lr.is_live as isLive, lr.like_count as likeCount, lr.view_count as viewCount, " +
|
||||||
|
"rl.create_time as likeTime " +
|
||||||
|
"FROM eb_live_room_like rl " +
|
||||||
|
"LEFT JOIN eb_live_room lr ON rl.room_id = lr.id " +
|
||||||
|
"WHERE rl.user_id = ? " +
|
||||||
|
"ORDER BY rl.create_time DESC LIMIT ? OFFSET ?";
|
||||||
|
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, pageSize, offset);
|
||||||
|
|
||||||
|
return buildPage(list, total != null ? total : 0, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取点赞直播间列表失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getLikedWorks(Integer userId, Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
String countSql = "SELECT COUNT(*) FROM eb_works_relation WHERE uid = ? AND type = 'like'";
|
||||||
|
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
||||||
|
|
||||||
|
String sql = "SELECT wr.id, wr.works_id as workId, w.title, w.description, " +
|
||||||
|
"w.cover_image as coverImage, w.video_url as videoUrl, w.user_id as authorId, " +
|
||||||
|
"u.nickname as authorName, u.avatar as authorAvatar, " +
|
||||||
|
"w.like_count as likeCount, w.view_count as viewCount, w.comment_count as commentCount, " +
|
||||||
|
"wr.create_time as likeTime " +
|
||||||
|
"FROM eb_works_relation wr " +
|
||||||
|
"LEFT JOIN eb_works w ON wr.works_id = w.id " +
|
||||||
|
"LEFT JOIN eb_user u ON w.user_id = u.uid " +
|
||||||
|
"WHERE wr.uid = ? AND wr.type = 'like' " +
|
||||||
|
"ORDER BY wr.create_time DESC LIMIT ? OFFSET ?";
|
||||||
|
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, pageSize, offset);
|
||||||
|
|
||||||
|
return buildPage(list, total != null ? total : 0, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取点赞作品列表失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getLikedWishes(Integer userId, Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
String countSql = "SELECT COUNT(*) FROM eb_wish_like WHERE user_id = ?";
|
||||||
|
Integer total = 0;
|
||||||
|
try {
|
||||||
|
total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("eb_wish_like表可能不存在");
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
String sql = "SELECT wl.id, wl.wish_id as wishId, ws.content, ws.user_id as authorId, " +
|
||||||
|
"u.nickname as authorName, u.avatar as authorAvatar, " +
|
||||||
|
"ws.like_count as likeCount, wl.create_time as likeTime " +
|
||||||
|
"FROM eb_wish_like wl " +
|
||||||
|
"LEFT JOIN eb_wishtree_wish ws ON wl.wish_id = ws.id " +
|
||||||
|
"LEFT JOIN eb_user u ON ws.user_id = u.uid " +
|
||||||
|
"WHERE wl.user_id = ? " +
|
||||||
|
"ORDER BY wl.create_time DESC LIMIT ? OFFSET ?";
|
||||||
|
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, pageSize, offset);
|
||||||
|
|
||||||
|
return buildPage(list, total != null ? total : 0, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取点赞心愿列表失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 关注记录 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getFollowRecords(Integer userId, Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
String countSql = "SELECT COUNT(*) FROM eb_follow_record WHERE follower_id = ? AND follow_status = 1";
|
||||||
|
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
||||||
|
|
||||||
|
String sql = "SELECT fr.id, fr.followed_id as followedId, fr.followed_nickname as followedNickname, " +
|
||||||
|
"u.avatar as followedAvatar, u.phone, " +
|
||||||
|
"fr.create_time as followTime " +
|
||||||
|
"FROM eb_follow_record fr " +
|
||||||
|
"LEFT JOIN eb_user u ON fr.followed_id = u.uid " +
|
||||||
|
"WHERE fr.follower_id = ? AND fr.follow_status = 1 " +
|
||||||
|
"ORDER BY fr.create_time DESC LIMIT ? OFFSET ?";
|
||||||
|
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, pageSize, offset);
|
||||||
|
|
||||||
|
return buildPage(list, total != null ? total : 0, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取关注记录失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 收藏记录 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CommonPage<Map<String, Object>> getCollectedWorks(Integer userId, Integer page, Integer pageSize) {
|
||||||
|
if (userId == null) {
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int offset = (page - 1) * pageSize;
|
||||||
|
|
||||||
|
String countSql = "SELECT COUNT(*) FROM eb_works_relation WHERE uid = ? AND type = 'collect'";
|
||||||
|
Integer total = jdbcTemplate.queryForObject(countSql, Integer.class, userId);
|
||||||
|
|
||||||
|
String sql = "SELECT wr.id, wr.works_id as workId, w.title, w.description, " +
|
||||||
|
"w.cover_image as coverImage, w.video_url as videoUrl, w.user_id as authorId, " +
|
||||||
|
"u.nickname as authorName, u.avatar as authorAvatar, " +
|
||||||
|
"w.like_count as likeCount, w.collect_count as collectCount, " +
|
||||||
|
"wr.create_time as collectTime " +
|
||||||
|
"FROM eb_works_relation wr " +
|
||||||
|
"LEFT JOIN eb_works w ON wr.works_id = w.id " +
|
||||||
|
"LEFT JOIN eb_user u ON w.user_id = u.uid " +
|
||||||
|
"WHERE wr.uid = ? AND wr.type = 'collect' " +
|
||||||
|
"ORDER BY wr.create_time DESC LIMIT ? OFFSET ?";
|
||||||
|
|
||||||
|
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql, userId, pageSize, offset);
|
||||||
|
|
||||||
|
return buildPage(list, total != null ? total : 0, page, pageSize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取收藏作品列表失败: userId={}", userId, e);
|
||||||
|
return emptyPage(page, pageSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 统计信息 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getUserActivityStats(Integer userId) {
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
if (userId == null) {
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 观看历史数
|
||||||
|
stats.put("viewCount", countTableRows("eb_view_history", "user_id", userId));
|
||||||
|
|
||||||
|
// 点赞直播间数
|
||||||
|
stats.put("likedRoomCount", countTableRows("eb_live_room_like", "user_id", userId));
|
||||||
|
|
||||||
|
// 点赞作品数
|
||||||
|
stats.put("likedWorkCount", countTableRows("eb_works_relation", "uid", userId, "type", "like"));
|
||||||
|
|
||||||
|
// 收藏作品数
|
||||||
|
stats.put("collectedWorkCount", countTableRows("eb_works_relation", "uid", userId, "type", "collect"));
|
||||||
|
|
||||||
|
// 关注数
|
||||||
|
stats.put("followingCount", countTableRows("eb_follow_record", "follower_id", userId, "follow_status", 1));
|
||||||
|
|
||||||
|
// 粉丝数
|
||||||
|
stats.put("followerCount", countTableRows("eb_follow_record", "followed_id", userId, "follow_status", 1));
|
||||||
|
|
||||||
|
// 搜索历史数
|
||||||
|
stats.put("searchHistoryCount", countTableRows("eb_search_history", "user_id", userId, "is_deleted", 0));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户活动统计失败: userId={}", userId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 工具方法 ====================
|
||||||
|
|
||||||
|
private int countTableRows(String tableName, String column, Object value) {
|
||||||
|
try {
|
||||||
|
String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE " + column + " = ?";
|
||||||
|
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, value);
|
||||||
|
return count != null ? count : 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("统计表{}行数失败", tableName);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countTableRows(String tableName, String column1, Object value1, String column2, Object value2) {
|
||||||
|
try {
|
||||||
|
String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE " + column1 + " = ? AND " + column2 + " = ?";
|
||||||
|
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, value1, value2);
|
||||||
|
return count != null ? count : 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("统计表{}行数失败", tableName);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommonPage<Map<String, Object>> emptyPage(Integer page, Integer pageSize) {
|
||||||
|
CommonPage<Map<String, Object>> result = new CommonPage<>();
|
||||||
|
result.setList(new ArrayList<>());
|
||||||
|
result.setTotal(0L);
|
||||||
|
result.setPage(page);
|
||||||
|
result.setLimit(pageSize);
|
||||||
|
result.setTotalPage(0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommonPage<Map<String, Object>> buildPage(List<Map<String, Object>> list, int total,
|
||||||
|
Integer page, Integer pageSize) {
|
||||||
|
CommonPage<Map<String, Object>> result = new CommonPage<>();
|
||||||
|
result.setList(list);
|
||||||
|
result.setTotal((long) total);
|
||||||
|
result.setPage(page);
|
||||||
|
result.setLimit(pageSize);
|
||||||
|
result.setTotalPage((int) Math.ceil((double) total / pageSize));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
111
Zhibo/zhibo-h/sql/user_activity_records_update.sql
Normal file
111
Zhibo/zhibo-h/sql/user_activity_records_update.sql
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
-- 用户活动记录相关表更新脚本
|
||||||
|
-- 确保管理端与移动端的"历史记录"功能互通
|
||||||
|
|
||||||
|
-- 1. 查看历史记录表(如果不存在则创建)
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_view_history` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||||
|
`target_type` varchar(20) NOT NULL COMMENT '目标类型:room-直播间, work-作品, profile-用户主页',
|
||||||
|
`target_id` varchar(50) NOT NULL COMMENT '目标ID',
|
||||||
|
`target_title` varchar(255) DEFAULT NULL COMMENT '目标标题',
|
||||||
|
`view_duration` int(11) DEFAULT 0 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`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_target` (`target_type`, `target_id`),
|
||||||
|
KEY `idx_create_time` (`create_time`),
|
||||||
|
KEY `idx_update_time` (`update_time`),
|
||||||
|
UNIQUE KEY `uk_user_target` (`user_id`, `target_type`, `target_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='查看历史记录表';
|
||||||
|
|
||||||
|
-- 2. 直播间点赞记录表(如果不存在则创建)
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_live_room_like` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||||
|
`room_id` int(11) NOT NULL COMMENT '直播间ID',
|
||||||
|
`like_count` int(11) 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_user_room` (`user_id`, `room_id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_room_id` (`room_id`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='直播间点赞记录表';
|
||||||
|
|
||||||
|
-- 3. 心愿点赞记录表(如果不存在则创建)
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_wish_like` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||||
|
`wish_id` bigint(20) NOT NULL COMMENT '心愿ID',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '点赞时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_wish` (`user_id`, `wish_id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_wish_id` (`wish_id`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心愿点赞记录表';
|
||||||
|
|
||||||
|
-- 4. 搜索历史表(如果不存在则创建)
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_search_history` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||||
|
`keyword` varchar(200) NOT NULL COMMENT '搜索关键词',
|
||||||
|
`search_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '搜索类型:0-综合 1-用户 2-直播间 3-作品 4-消息',
|
||||||
|
`search_count` int(11) NOT NULL DEFAULT 1 COMMENT '搜索次数',
|
||||||
|
`is_deleted` tinyint(1) NOT NULL DEFAULT 0 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 '更新时间',
|
||||||
|
`ext_field1` varchar(200) DEFAULT NULL COMMENT '扩展字段1',
|
||||||
|
`ext_field2` varchar(200) DEFAULT NULL COMMENT '扩展字段2',
|
||||||
|
`ext_field3` varchar(200) DEFAULT NULL COMMENT '扩展字段3',
|
||||||
|
`ext_field4` varchar(200) DEFAULT NULL COMMENT '扩展字段4',
|
||||||
|
`ext_field5` varchar(200) DEFAULT NULL COMMENT '扩展字段5',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_user_id` (`user_id`),
|
||||||
|
KEY `idx_search_type` (`search_type`),
|
||||||
|
KEY `idx_is_deleted` (`is_deleted`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='搜索历史表';
|
||||||
|
|
||||||
|
-- 5. 热门搜索表(如果不存在则创建)
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_hot_search` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`keyword` varchar(200) NOT NULL COMMENT '搜索关键词',
|
||||||
|
`search_type` tinyint(4) NOT NULL DEFAULT 0 COMMENT '搜索类型:0-综合 1-用户 2-直播间 3-作品',
|
||||||
|
`hot_score` int(11) NOT NULL DEFAULT 0 COMMENT '热度分数',
|
||||||
|
`search_count` int(11) NOT NULL DEFAULT 0 COMMENT '搜索次数',
|
||||||
|
`sort_order` int(11) NOT NULL DEFAULT 0 COMMENT '排序权重',
|
||||||
|
`status` tinyint(1) 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`),
|
||||||
|
UNIQUE KEY `uk_keyword_type` (`keyword`, `search_type`),
|
||||||
|
KEY `idx_search_type` (`search_type`),
|
||||||
|
KEY `idx_hot_score` (`hot_score`),
|
||||||
|
KEY `idx_status` (`status`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='热门搜索表';
|
||||||
|
|
||||||
|
-- 6. 确保关注记录表有必要的字段
|
||||||
|
-- 如果表已存在,添加缺失的字段
|
||||||
|
-- ALTER TABLE `eb_follow_record` ADD COLUMN IF NOT EXISTS `follower_nickname` varchar(100) DEFAULT NULL COMMENT '关注者昵称';
|
||||||
|
-- ALTER TABLE `eb_follow_record` ADD COLUMN IF NOT EXISTS `followed_nickname` varchar(100) DEFAULT NULL COMMENT '被关注者昵称';
|
||||||
|
|
||||||
|
-- 7. 确保作品关系表存在(点赞、收藏)
|
||||||
|
CREATE TABLE IF NOT EXISTS `eb_works_relation` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
|
`uid` int(11) NOT NULL COMMENT '用户ID',
|
||||||
|
`works_id` bigint(20) NOT NULL COMMENT '作品ID',
|
||||||
|
`type` varchar(20) NOT NULL COMMENT '关系类型:like-点赞, collect-收藏',
|
||||||
|
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_user_works_type` (`uid`, `works_id`, `type`),
|
||||||
|
KEY `idx_uid` (`uid`),
|
||||||
|
KEY `idx_works_id` (`works_id`),
|
||||||
|
KEY `idx_type` (`type`),
|
||||||
|
KEY `idx_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='作品关系表(点赞、收藏)';
|
||||||
|
|
||||||
|
-- 完成提示
|
||||||
|
SELECT '用户活动记录表结构更新完成' AS message;
|
||||||
|
|
@ -67,6 +67,10 @@
|
||||||
android:name="com.example.livestreaming.WatchHistoryActivity"
|
android:name="com.example.livestreaming.WatchHistoryActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.example.livestreaming.MyRecordsActivity"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.example.livestreaming.MyFriendsActivity"
|
android:name="com.example.livestreaming.MyFriendsActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,474 @@
|
||||||
|
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.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.example.livestreaming.net.ApiClient;
|
||||||
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
|
import com.example.livestreaming.net.PageResponse;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 我的记录页面 - 整合观看历史、点赞记录、收藏记录等
|
||||||
|
*/
|
||||||
|
public class MyRecordsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private TabLayout tabLayout;
|
||||||
|
private ViewPager2 viewPager;
|
||||||
|
private ImageView backButton;
|
||||||
|
|
||||||
|
private final String[] tabTitles = {"观看历史", "点赞记录", "收藏记录", "关注记录"};
|
||||||
|
|
||||||
|
public static void start(Context context) {
|
||||||
|
Intent intent = new Intent(context, MyRecordsActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start(Context context, int tabIndex) {
|
||||||
|
Intent intent = new Intent(context, MyRecordsActivity.class);
|
||||||
|
intent.putExtra("tabIndex", tabIndex);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_my_records);
|
||||||
|
|
||||||
|
initViews();
|
||||||
|
setupViewPager();
|
||||||
|
|
||||||
|
// 处理跳转到指定Tab
|
||||||
|
int tabIndex = getIntent().getIntExtra("tabIndex", 0);
|
||||||
|
if (tabIndex >= 0 && tabIndex < tabTitles.length) {
|
||||||
|
viewPager.setCurrentItem(tabIndex, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews() {
|
||||||
|
backButton = findViewById(R.id.backButton);
|
||||||
|
tabLayout = findViewById(R.id.tabLayout);
|
||||||
|
viewPager = findViewById(R.id.viewPager);
|
||||||
|
|
||||||
|
backButton.setOnClickListener(v -> finish());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupViewPager() {
|
||||||
|
RecordsPagerAdapter adapter = new RecordsPagerAdapter(this);
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
|
||||||
|
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
|
||||||
|
tab.setText(tabTitles[position]);
|
||||||
|
}).attach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewPager适配器
|
||||||
|
*/
|
||||||
|
private static class RecordsPagerAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
|
public RecordsPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
|
||||||
|
super(fragmentActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(int position) {
|
||||||
|
switch (position) {
|
||||||
|
case 0:
|
||||||
|
return RecordListFragment.newInstance("view");
|
||||||
|
case 1:
|
||||||
|
return RecordListFragment.newInstance("like");
|
||||||
|
case 2:
|
||||||
|
return RecordListFragment.newInstance("collect");
|
||||||
|
case 3:
|
||||||
|
return RecordListFragment.newInstance("follow");
|
||||||
|
default:
|
||||||
|
return RecordListFragment.newInstance("view");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录列表Fragment
|
||||||
|
*/
|
||||||
|
public static class RecordListFragment extends Fragment {
|
||||||
|
|
||||||
|
private static final String ARG_TYPE = "type";
|
||||||
|
private String recordType;
|
||||||
|
|
||||||
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private View loadingView;
|
||||||
|
private View emptyView;
|
||||||
|
|
||||||
|
private RecordAdapter adapter;
|
||||||
|
private final List<Map<String, Object>> records = new ArrayList<>();
|
||||||
|
private int currentPage = 1;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
private boolean hasMore = true;
|
||||||
|
|
||||||
|
public static RecordListFragment newInstance(String type) {
|
||||||
|
RecordListFragment fragment = new RecordListFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(ARG_TYPE, type);
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (getArguments() != null) {
|
||||||
|
recordType = getArguments().getString(ARG_TYPE, "view");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_record_list, container, false);
|
||||||
|
|
||||||
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
|
||||||
|
recyclerView = view.findViewById(R.id.recyclerView);
|
||||||
|
loadingView = view.findViewById(R.id.loadingView);
|
||||||
|
emptyView = view.findViewById(R.id.emptyView);
|
||||||
|
|
||||||
|
setupRecyclerView();
|
||||||
|
setupSwipeRefresh();
|
||||||
|
loadRecords();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new RecordAdapter(recordType, item -> {
|
||||||
|
// 点击跳转
|
||||||
|
handleItemClick(item);
|
||||||
|
});
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy > 0 && !isLoading && hasMore) {
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int visible = lm.getChildCount();
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int first = lm.findFirstVisibleItemPosition();
|
||||||
|
if ((visible + first) >= total - 2) {
|
||||||
|
loadMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSwipeRefresh() {
|
||||||
|
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = true;
|
||||||
|
loadRecords();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRecords() {
|
||||||
|
if (isLoading || getContext() == null) return;
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
if (currentPage == 1) {
|
||||||
|
loadingView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
emptyView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> call;
|
||||||
|
switch (recordType) {
|
||||||
|
case "view":
|
||||||
|
call = ApiClient.getService(getContext()).getViewHistory(null, currentPage, 20);
|
||||||
|
break;
|
||||||
|
case "like":
|
||||||
|
call = ApiClient.getService(getContext()).getLikeRecords(null, currentPage, 20);
|
||||||
|
break;
|
||||||
|
case "collect":
|
||||||
|
call = ApiClient.getService(getContext()).getCollectedWorks(currentPage, 20);
|
||||||
|
break;
|
||||||
|
case "follow":
|
||||||
|
call = ApiClient.getService(getContext()).getFollowRecords(currentPage, 20);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
call = ApiClient.getService(getContext()).getViewHistory(null, currentPage, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
call.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) {
|
||||||
|
isLoading = false;
|
||||||
|
loadingView.setVisibility(View.GONE);
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<Map<String, Object>> data = response.body().getData();
|
||||||
|
if (data != null && data.getList() != null) {
|
||||||
|
if (currentPage == 1) {
|
||||||
|
records.clear();
|
||||||
|
}
|
||||||
|
records.addAll(data.getList());
|
||||||
|
adapter.setData(new ArrayList<>(records));
|
||||||
|
hasMore = data.getList().size() >= 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
loadingView.setVisibility(View.GONE);
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
updateEmptyState();
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "加载失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMore() {
|
||||||
|
currentPage++;
|
||||||
|
loadRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
if (records.isEmpty()) {
|
||||||
|
emptyView.setVisibility(View.VISIBLE);
|
||||||
|
recyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
emptyView.setVisibility(View.GONE);
|
||||||
|
recyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleItemClick(Map<String, Object> item) {
|
||||||
|
if (getContext() == null) return;
|
||||||
|
|
||||||
|
String targetType = (String) item.get("targetType");
|
||||||
|
if (targetType == null) {
|
||||||
|
// 根据记录类型判断
|
||||||
|
if ("follow".equals(recordType)) {
|
||||||
|
// 跳转到用户主页
|
||||||
|
Object followedId = item.get("followedId");
|
||||||
|
if (followedId != null) {
|
||||||
|
Intent intent = new Intent(getContext(), UserProfileReadOnlyActivity.class);
|
||||||
|
intent.putExtra("userId", ((Number) followedId).intValue());
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
} else if ("collect".equals(recordType) || "like".equals(recordType)) {
|
||||||
|
// 跳转到作品详情
|
||||||
|
Object workId = item.get("workId");
|
||||||
|
if (workId != null) {
|
||||||
|
Intent intent = new Intent(getContext(), WorkDetailActivity.class);
|
||||||
|
intent.putExtra("workId", ((Number) workId).longValue());
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetId = String.valueOf(item.get("targetId"));
|
||||||
|
switch (targetType) {
|
||||||
|
case "room":
|
||||||
|
Intent roomIntent = new Intent(getContext(), RoomDetailActivity.class);
|
||||||
|
roomIntent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, targetId);
|
||||||
|
startActivity(roomIntent);
|
||||||
|
break;
|
||||||
|
case "work":
|
||||||
|
Intent workIntent = new Intent(getContext(), WorkDetailActivity.class);
|
||||||
|
workIntent.putExtra("workId", Long.parseLong(targetId));
|
||||||
|
startActivity(workIntent);
|
||||||
|
break;
|
||||||
|
case "profile":
|
||||||
|
Intent profileIntent = new Intent(getContext(), UserProfileReadOnlyActivity.class);
|
||||||
|
profileIntent.putExtra("userId", Integer.parseInt(targetId));
|
||||||
|
startActivity(profileIntent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录列表适配器
|
||||||
|
*/
|
||||||
|
private static class RecordAdapter extends RecyclerView.Adapter<RecordAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private final String recordType;
|
||||||
|
private List<Map<String, Object>> data = new ArrayList<>();
|
||||||
|
private final OnItemClickListener listener;
|
||||||
|
|
||||||
|
interface OnItemClickListener {
|
||||||
|
void onItemClick(Map<String, Object> item);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordAdapter(String recordType, OnItemClickListener listener) {
|
||||||
|
this.recordType = recordType;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setData(List<Map<String, Object>> data) {
|
||||||
|
this.data = data;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_record, parent, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
Map<String, Object> item = data.get(position);
|
||||||
|
holder.bind(item, recordType);
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onItemClick(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
TextView tvTitle;
|
||||||
|
TextView tvSubtitle;
|
||||||
|
TextView tvTime;
|
||||||
|
ImageView ivIcon;
|
||||||
|
|
||||||
|
ViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
tvTitle = itemView.findViewById(R.id.tvTitle);
|
||||||
|
tvSubtitle = itemView.findViewById(R.id.tvSubtitle);
|
||||||
|
tvTime = itemView.findViewById(R.id.tvTime);
|
||||||
|
ivIcon = itemView.findViewById(R.id.ivIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(Map<String, Object> item, String recordType) {
|
||||||
|
String title = "";
|
||||||
|
String subtitle = "";
|
||||||
|
String time = "";
|
||||||
|
String imageUrl = "";
|
||||||
|
|
||||||
|
switch (recordType) {
|
||||||
|
case "view":
|
||||||
|
title = getStringValue(item, "targetTitle", "title");
|
||||||
|
String targetType = (String) item.get("targetType");
|
||||||
|
subtitle = "room".equals(targetType) ? "直播间" :
|
||||||
|
"work".equals(targetType) ? "作品" : "用户主页";
|
||||||
|
time = formatTime(item.get("updateTime"));
|
||||||
|
imageUrl = getStringValue(item, "coverImage", "avatar");
|
||||||
|
break;
|
||||||
|
case "like":
|
||||||
|
title = getStringValue(item, "targetTitle", "roomTitle", "title", "content");
|
||||||
|
String likeType = (String) item.get("targetType");
|
||||||
|
subtitle = "room".equals(likeType) ? "直播间" :
|
||||||
|
"work".equals(likeType) ? "作品" : "心愿";
|
||||||
|
time = formatTime(item.get("createTime"), item.get("likeTime"));
|
||||||
|
imageUrl = getStringValue(item, "coverImage");
|
||||||
|
break;
|
||||||
|
case "collect":
|
||||||
|
title = getStringValue(item, "title");
|
||||||
|
subtitle = "作品 · " + getStringValue(item, "authorName");
|
||||||
|
time = formatTime(item.get("collectTime"));
|
||||||
|
imageUrl = getStringValue(item, "coverImage");
|
||||||
|
break;
|
||||||
|
case "follow":
|
||||||
|
title = getStringValue(item, "followedNickname");
|
||||||
|
subtitle = "已关注";
|
||||||
|
time = formatTime(item.get("followTime"));
|
||||||
|
imageUrl = getStringValue(item, "followedAvatar");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tvTitle.setText(title.isEmpty() ? "未知" : title);
|
||||||
|
tvSubtitle.setText(subtitle);
|
||||||
|
tvTime.setText(time);
|
||||||
|
|
||||||
|
// 加载图片
|
||||||
|
if (!imageUrl.isEmpty()) {
|
||||||
|
Glide.with(itemView.getContext())
|
||||||
|
.load(imageUrl)
|
||||||
|
.placeholder(R.drawable.ic_history_24)
|
||||||
|
.error(R.drawable.ic_history_24)
|
||||||
|
.centerCrop()
|
||||||
|
.into(ivIcon);
|
||||||
|
} else {
|
||||||
|
ivIcon.setImageResource(R.drawable.ic_history_24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getStringValue(Map<String, Object> item, String... keys) {
|
||||||
|
for (String key : keys) {
|
||||||
|
Object value = item.get(key);
|
||||||
|
if (value != null && !value.toString().isEmpty()) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatTime(Object... times) {
|
||||||
|
for (Object time : times) {
|
||||||
|
if (time != null) {
|
||||||
|
String timeStr = time.toString();
|
||||||
|
if (timeStr.length() > 10) {
|
||||||
|
return timeStr.substring(0, 10);
|
||||||
|
}
|
||||||
|
return timeStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -419,6 +419,13 @@ public class ProfileActivity extends AppCompatActivity {
|
||||||
startActivity(new Intent(this, LikedRoomsActivity.class));
|
startActivity(new Intent(this, LikedRoomsActivity.class));
|
||||||
});
|
});
|
||||||
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
|
binding.action3.setOnClickListener(v -> startActivity(new Intent(this, MyFriendsActivity.class)));
|
||||||
|
binding.action4.setOnClickListener(v -> {
|
||||||
|
// 我的记录 - 跳转到统一记录页面
|
||||||
|
if (!AuthHelper.requireLogin(this, "查看记录需要登录")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MyRecordsActivity.start(this);
|
||||||
|
});
|
||||||
|
|
||||||
binding.editProfile.setOnClickListener(v -> {
|
binding.editProfile.setOnClickListener(v -> {
|
||||||
// 检查登录状态,编辑资料需要登录
|
// 检查登录状态,编辑资料需要登录
|
||||||
|
|
|
||||||
|
|
@ -934,22 +934,28 @@ public class RoomDetailActivity extends AppCompatActivity {
|
||||||
|
|
||||||
ApiService apiService = ApiClient.getService(getApplicationContext());
|
ApiService apiService = ApiClient.getService(getApplicationContext());
|
||||||
|
|
||||||
|
// 使用新的统一观看历史API
|
||||||
java.util.Map<String, Object> body = new java.util.HashMap<>();
|
java.util.Map<String, Object> body = new java.util.HashMap<>();
|
||||||
body.put("roomId", roomId);
|
body.put("targetType", "room");
|
||||||
body.put("watchTime", System.currentTimeMillis());
|
body.put("targetId", roomId);
|
||||||
|
body.put("targetTitle", roomTitle != null ? roomTitle : "直播间");
|
||||||
|
body.put("duration", 0); // 初始时长为0,后续可更新
|
||||||
|
|
||||||
Call<ApiResponse<java.util.Map<String, Object>>> call = apiService.recordWatchHistory(body);
|
Call<ApiResponse<java.util.Map<String, Object>>> call = apiService.recordViewHistoryNew(body);
|
||||||
|
|
||||||
call.enqueue(new Callback<ApiResponse<java.util.Map<String, Object>>>() {
|
call.enqueue(new Callback<ApiResponse<java.util.Map<String, Object>>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(Call<ApiResponse<java.util.Map<String, Object>>> call,
|
public void onResponse(Call<ApiResponse<java.util.Map<String, Object>>> call,
|
||||||
Response<ApiResponse<java.util.Map<String, Object>>> response) {
|
Response<ApiResponse<java.util.Map<String, Object>>> response) {
|
||||||
// 忽略结果,接口可能不存在
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
android.util.Log.d("RoomDetail", "观看历史记录成功");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(Call<ApiResponse<java.util.Map<String, Object>>> call, Throwable t) {
|
public void onFailure(Call<ApiResponse<java.util.Map<String, Object>>> call, Throwable t) {
|
||||||
// 忽略错误,接口可能不存在
|
// 忽略错误,不影响直播观看
|
||||||
|
android.util.Log.w("RoomDetail", "记录观看历史失败: " + t.getMessage());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,42 @@ package com.example.livestreaming;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
import com.example.livestreaming.databinding.ActivityWatchHistoryBinding;
|
import com.example.livestreaming.databinding.ActivityWatchHistoryBinding;
|
||||||
|
import com.example.livestreaming.net.ApiClient;
|
||||||
|
import com.example.livestreaming.net.ApiResponse;
|
||||||
|
import com.example.livestreaming.net.PageResponse;
|
||||||
import com.example.livestreaming.net.Room;
|
import com.example.livestreaming.net.Room;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 观看历史页面
|
||||||
|
*/
|
||||||
public class WatchHistoryActivity extends AppCompatActivity {
|
public class WatchHistoryActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ActivityWatchHistoryBinding binding;
|
private ActivityWatchHistoryBinding binding;
|
||||||
|
private RoomsAdapter adapter;
|
||||||
|
private final List<Room> historyRooms = new ArrayList<>();
|
||||||
|
|
||||||
|
private int currentPage = 1;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
private boolean hasMore = true;
|
||||||
|
|
||||||
public static void start(Context context) {
|
public static void start(Context context) {
|
||||||
Intent intent = new Intent(context, WatchHistoryActivity.class);
|
Intent intent = new Intent(context, WatchHistoryActivity.class);
|
||||||
|
|
@ -28,34 +51,202 @@ public class WatchHistoryActivity extends AppCompatActivity {
|
||||||
binding = ActivityWatchHistoryBinding.inflate(getLayoutInflater());
|
binding = ActivityWatchHistoryBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
binding.backButton.setOnClickListener(v -> finish());
|
initViews();
|
||||||
|
setupRecyclerView();
|
||||||
|
loadWatchHistory();
|
||||||
|
}
|
||||||
|
|
||||||
RoomsAdapter adapter = new RoomsAdapter(room -> {
|
private void initViews() {
|
||||||
|
binding.backButton.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
|
// 清空历史按钮
|
||||||
|
binding.clearButton.setOnClickListener(v -> showClearConfirmDialog());
|
||||||
|
|
||||||
|
// 下拉刷新
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||||
|
currentPage = 1;
|
||||||
|
hasMore = true;
|
||||||
|
loadWatchHistory();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new RoomsAdapter(room -> {
|
||||||
if (room == null) return;
|
if (room == null) return;
|
||||||
Intent intent = new Intent(WatchHistoryActivity.this, RoomDetailActivity.class);
|
Intent intent = new Intent(WatchHistoryActivity.this, RoomDetailActivity.class);
|
||||||
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
|
intent.putExtra(RoomDetailActivity.EXTRA_ROOM_ID, room.getId());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: 接入后端接口 - 获取观看历史
|
binding.roomsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||||
// 接口路径: GET /api/watch/history
|
|
||||||
// 请求参数:
|
|
||||||
// - userId: 当前用户ID(从token中获取)
|
|
||||||
// - page (可选): 页码
|
|
||||||
// - pageSize (可选): 每页数量
|
|
||||||
// 返回数据格式: ApiResponse<List<Room>>
|
|
||||||
// Room对象应包含: id, title, streamerName, type, isLive, coverUrl, watchTime(观看时间)等字段
|
|
||||||
// 列表应按观看时间倒序排列(最近观看的在前面)
|
|
||||||
StaggeredGridLayoutManager glm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
|
|
||||||
glm.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
|
|
||||||
binding.roomsRecyclerView.setLayoutManager(glm);
|
|
||||||
binding.roomsRecyclerView.setAdapter(adapter);
|
binding.roomsRecyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
adapter.submitList(buildDemoHistory(18));
|
// 滚动加载更多
|
||||||
|
binding.roomsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
|
if (dy > 0 && !isLoading && hasMore) {
|
||||||
|
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
|
||||||
|
if (layoutManager != null) {
|
||||||
|
int visibleItemCount = layoutManager.getChildCount();
|
||||||
|
int totalItemCount = layoutManager.getItemCount();
|
||||||
|
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
|
||||||
|
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount - 2) {
|
||||||
|
loadMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Room> buildDemoHistory(int count) {
|
private void loadWatchHistory() {
|
||||||
// 不再使用模拟数据,只从后端接口获取真实观看历史数据
|
if (isLoading) return;
|
||||||
return new ArrayList<>();
|
isLoading = true;
|
||||||
|
|
||||||
|
if (currentPage == 1) {
|
||||||
|
showLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API获取观看历史(只获取直播间类型)
|
||||||
|
ApiClient.getService(this)
|
||||||
|
.getViewHistory("room", currentPage, 20)
|
||||||
|
.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) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
binding.swipeRefreshLayout.setRefreshing(false);
|
||||||
|
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
PageResponse<Map<String, Object>> pageData = response.body().getData();
|
||||||
|
if (pageData != null && pageData.getList() != null) {
|
||||||
|
List<Room> rooms = convertToRooms(pageData.getList());
|
||||||
|
|
||||||
|
if (currentPage == 1) {
|
||||||
|
historyRooms.clear();
|
||||||
|
}
|
||||||
|
historyRooms.addAll(rooms);
|
||||||
|
adapter.submitList(new ArrayList<>(historyRooms));
|
||||||
|
|
||||||
|
hasMore = rooms.size() >= 20;
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentPage == 1) {
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
Toast.makeText(WatchHistoryActivity.this, "加载失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<PageResponse<Map<String, Object>>>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
hideLoading();
|
||||||
|
binding.swipeRefreshLayout.setRefreshing(false);
|
||||||
|
if (currentPage == 1) {
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
Toast.makeText(WatchHistoryActivity.this, "网络错误", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMore() {
|
||||||
|
currentPage++;
|
||||||
|
loadWatchHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将Map转换为Room对象
|
||||||
|
*/
|
||||||
|
private List<Room> convertToRooms(List<Map<String, Object>> maps) {
|
||||||
|
List<Room> rooms = new ArrayList<>();
|
||||||
|
for (Map<String, Object> map : maps) {
|
||||||
|
Room room = new Room();
|
||||||
|
|
||||||
|
// 从观看历史数据中提取信息
|
||||||
|
if (map.containsKey("targetId")) {
|
||||||
|
room.setId(map.get("targetId"));
|
||||||
|
}
|
||||||
|
if (map.containsKey("targetTitle")) {
|
||||||
|
room.setTitle((String) map.get("targetTitle"));
|
||||||
|
}
|
||||||
|
if (map.containsKey("title")) {
|
||||||
|
room.setTitle((String) map.get("title"));
|
||||||
|
}
|
||||||
|
if (map.containsKey("streamerName")) {
|
||||||
|
room.setStreamerName((String) map.get("streamerName"));
|
||||||
|
}
|
||||||
|
if (map.containsKey("coverImage")) {
|
||||||
|
room.setCoverImage((String) map.get("coverImage"));
|
||||||
|
}
|
||||||
|
if (map.containsKey("isLive")) {
|
||||||
|
Object isLive = map.get("isLive");
|
||||||
|
if (isLive instanceof Number) {
|
||||||
|
room.setLive(((Number) isLive).intValue() == 1);
|
||||||
|
} else if (isLive instanceof Boolean) {
|
||||||
|
room.setLive((Boolean) isLive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms.add(room);
|
||||||
|
}
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showClearConfirmDialog() {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("清空观看历史")
|
||||||
|
.setMessage("确定要清空所有观看历史吗?此操作不可恢复。")
|
||||||
|
.setPositiveButton("确定", (dialog, which) -> clearWatchHistory())
|
||||||
|
.setNegativeButton("取消", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearWatchHistory() {
|
||||||
|
ApiClient.getService(this)
|
||||||
|
.clearViewHistory("room")
|
||||||
|
.enqueue(new Callback<ApiResponse<String>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<ApiResponse<String>> call, Response<ApiResponse<String>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
||||||
|
historyRooms.clear();
|
||||||
|
adapter.submitList(new ArrayList<>());
|
||||||
|
updateEmptyState();
|
||||||
|
Toast.makeText(WatchHistoryActivity.this, "已清空观看历史", Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(WatchHistoryActivity.this, "清空失败", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<ApiResponse<String>> call, Throwable t) {
|
||||||
|
Toast.makeText(WatchHistoryActivity.this, "网络错误", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showLoading() {
|
||||||
|
binding.loadingView.setVisibility(View.VISIBLE);
|
||||||
|
binding.roomsRecyclerView.setVisibility(View.GONE);
|
||||||
|
binding.emptyView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideLoading() {
|
||||||
|
binding.loadingView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
if (historyRooms.isEmpty()) {
|
||||||
|
binding.emptyView.setVisibility(View.VISIBLE);
|
||||||
|
binding.roomsRecyclerView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
binding.emptyView.setVisibility(View.GONE);
|
||||||
|
binding.roomsRecyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -652,4 +652,108 @@ public interface ApiService {
|
||||||
Call<ApiResponse<List<CommunityResponse.MatchUser>>> getMatchList(
|
Call<ApiResponse<List<CommunityResponse.MatchUser>>> getMatchList(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("limit") int limit);
|
@Query("limit") int limit);
|
||||||
|
|
||||||
|
// ==================== 用户活动记录接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录观看历史
|
||||||
|
*/
|
||||||
|
@POST("api/front/activity/view/record")
|
||||||
|
Call<ApiResponse<Map<String, Object>>> recordViewHistoryNew(@Body Map<String, Object> body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取观看历史列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/view/history")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getViewHistory(
|
||||||
|
@Query("targetType") String targetType,
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单条观看历史
|
||||||
|
*/
|
||||||
|
@DELETE("api/front/activity/view/history/{historyId}")
|
||||||
|
Call<ApiResponse<String>> deleteViewHistory(@Path("historyId") long historyId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空观看历史
|
||||||
|
*/
|
||||||
|
@DELETE("api/front/activity/view/history")
|
||||||
|
Call<ApiResponse<String>> clearViewHistory(@Query("targetType") String targetType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞记录列表(全部类型)
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/like/records")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikeRecords(
|
||||||
|
@Query("targetType") String targetType,
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞的直播间列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/like/rooms")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikedRoomsNew(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞的作品列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/like/works")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikedWorks(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取点赞的心愿列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/like/wishes")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getLikedWishes(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取关注记录列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/follow/records")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getFollowRecords(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取收藏的作品列表
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/collect/works")
|
||||||
|
Call<ApiResponse<PageResponse<Map<String, Object>>>> getCollectedWorks(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("pageSize") int pageSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户活动统计
|
||||||
|
*/
|
||||||
|
@GET("api/front/activity/stats")
|
||||||
|
Call<ApiResponse<Map<String, Object>>> getUserActivityStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取搜索历史
|
||||||
|
*/
|
||||||
|
@GET("api/front/search/history")
|
||||||
|
Call<ApiResponse<List<SearchHistoryResponse>>> getSearchHistory(
|
||||||
|
@Query("searchType") Integer searchType,
|
||||||
|
@Query("limit") int limit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除搜索历史
|
||||||
|
*/
|
||||||
|
@DELETE("api/front/search/history")
|
||||||
|
Call<ApiResponse<String>> clearSearchHistory(@Query("searchType") Integer searchType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单条搜索历史
|
||||||
|
*/
|
||||||
|
@DELETE("api/front/search/history/{historyId}")
|
||||||
|
Call<ApiResponse<String>> deleteSearchHistoryItem(@Path("historyId") long historyId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
android-app/app/src/main/res/drawable/ic_history_24.xml
Normal file
10
android-app/app/src/main/res/drawable/ic_history_24.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#999999"
|
||||||
|
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
|
||||||
|
</vector>
|
||||||
65
android-app/app/src/main/res/layout/activity_my_records.xml
Normal file
65
android-app/app/src/main/res/layout/activity_my_records.xml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?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:background="@android:color/white"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- 顶部标题栏 -->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:contentDescription="返回"
|
||||||
|
android:src="@drawable/ic_arrow_back_24"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="我的记录"
|
||||||
|
android:textColor="#111111"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<!-- Tab栏 -->
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
app:tabGravity="fill"
|
||||||
|
app:tabIndicatorColor="#FF6B6B"
|
||||||
|
app:tabIndicatorHeight="3dp"
|
||||||
|
app:tabMode="fixed"
|
||||||
|
app:tabSelectedTextColor="#FF6B6B"
|
||||||
|
app:tabTextColor="#666666" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#EEEEEE" />
|
||||||
|
|
||||||
|
<!-- ViewPager -->
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/viewPager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -475,6 +475,7 @@
|
||||||
android:id="@+id/action3"
|
android:id="@+id/action3"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
|
@ -519,6 +520,54 @@
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/action4"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:background="@drawable/bg_gray_12">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:src="@drawable/ic_history_24"
|
||||||
|
android:tint="#2196F3" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="我的记录"
|
||||||
|
android:textColor="#111111"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recordsCount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="查看全部"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="11sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="观看历史"
|
android:text="观看历史"
|
||||||
|
|
@ -40,23 +41,102 @@
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/backButton"
|
app:layout_constraintBottom_toBottomOf="@id/backButton"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@id/clearButton"
|
||||||
app:layout_constraintStart_toEndOf="@id/backButton"
|
app:layout_constraintStart_toEndOf="@id/backButton"
|
||||||
app:layout_constraintTop_toTopOf="@id/backButton" />
|
app:layout_constraintTop_toTopOf="@id/backButton" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/clearButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:text="清空"
|
||||||
|
android:textColor="#FF6B6B"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/roomsRecyclerView"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
android:paddingStart="12dp"
|
|
||||||
android:paddingEnd="12dp"
|
<FrameLayout
|
||||||
android:paddingTop="8dp"
|
android:layout_width="match_parent"
|
||||||
android:paddingBottom="16dp"
|
android:layout_height="match_parent">
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/roomsRecyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="16dp" />
|
||||||
|
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loadingView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="加载中..."
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/emptyView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="120dp"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:alpha="0.5"
|
||||||
|
android:src="@drawable/ic_history_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="暂无观看历史"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="去看看精彩直播吧"
|
||||||
|
android:textColor="#BBBBBB"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
|
||||||
68
android-app/app/src/main/res/layout/fragment_record_list.xml
Normal file
68
android-app/app/src/main/res/layout/fragment_record_list.xml
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipeRefreshLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="16dp" />
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<!-- 加载中 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/loadingView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="加载中..."
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/emptyView"
|
||||||
|
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:alpha="0.4"
|
||||||
|
android:src="@drawable/ic_history_24" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="暂无记录"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
72
android-app/app/src/main/res/layout/item_record.xml
Normal file
72
android-app/app/src/main/res/layout/item_record.xml
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivIcon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="#F5F5F5"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="标题"
|
||||||
|
android:textColor="#333333"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/tvTime"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/ivIcon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/ivIcon" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSubtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="副标题"
|
||||||
|
android:textColor="#999999"
|
||||||
|
android:textSize="13sp"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/tvTime"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/ivIcon"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvTitle" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="时间"
|
||||||
|
android:textColor="#BBBBBB"
|
||||||
|
android:textSize="12sp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvTitle" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:background="#F0F0F0"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tvTitle" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
355
用户活动记录功能说明.md
355
用户活动记录功能说明.md
|
|
@ -2,297 +2,106 @@
|
||||||
|
|
||||||
## 功能概述
|
## 功能概述
|
||||||
|
|
||||||
在管理后台的用户详情页面添加了三个新的标签页,用于查看用户的活动记录:
|
实现了管理端与移动端的"历史记录"功能互通,包括:
|
||||||
1. **关注记录** - 查看用户关注了哪些人
|
- 观看历史(直播间、作品、用户主页)
|
||||||
2. **点赞记录** - 查看用户点赞了哪些内容(直播间、作品、心愿等)
|
- 点赞记录(直播间、作品、心愿)
|
||||||
3. **查看历史** - 查看用户的浏览历史记录
|
- 关注记录
|
||||||
|
- 收藏记录
|
||||||
|
|
||||||
## 实现内容
|
## 后端实现
|
||||||
|
|
||||||
### 1. 前端修改
|
### 1. 服务层
|
||||||
|
- `UserActivityRecordService.java` - 服务接口
|
||||||
|
- `UserActivityRecordServiceImpl.java` - 服务实现
|
||||||
|
|
||||||
**文件**: `Zhibo/admin/src/views/user/list/userDetails.vue`
|
### 2. API 控制器
|
||||||
|
- **前端 API**: `UserActivityRecordController.java`
|
||||||
|
- 路径前缀: `/api/front/activity/`
|
||||||
|
- 接口列表:
|
||||||
|
- `POST /view/record` - 记录观看历史
|
||||||
|
- `GET /view/history` - 获取观看历史
|
||||||
|
- `DELETE /view/history/{historyId}` - 删除单条观看历史
|
||||||
|
- `DELETE /view/history` - 清空观看历史
|
||||||
|
- `GET /like/records` - 获取点赞记录
|
||||||
|
- `GET /like/rooms` - 获取点赞的直播间
|
||||||
|
- `GET /like/works` - 获取点赞的作品
|
||||||
|
- `GET /like/wishes` - 获取点赞的心愿
|
||||||
|
- `GET /follow/records` - 获取关注记录
|
||||||
|
- `GET /collect/works` - 获取收藏的作品
|
||||||
|
- `GET /stats` - 获取用户活动统计
|
||||||
|
|
||||||
**新增标签页**:
|
- **管理端 API**: `UserActivityController.java`
|
||||||
- 标签页 7:关注记录
|
- 路径前缀: `/api/admin/user/`
|
||||||
- 标签页 8:点赞记录
|
- 接口列表:
|
||||||
- 标签页 9:查看历史
|
- `GET /follow/records` - 获取用户关注记录
|
||||||
|
- `GET /like/records` - 获取用户点赞记录
|
||||||
**功能特性**:
|
- `GET /view/history` - 获取用户查看历史
|
||||||
- 支持分页显示
|
- `GET /collect/works` - 获取用户收藏的作品
|
||||||
- 数据实时加载
|
- `GET /activity/stats` - 获取用户活动统计
|
||||||
- 状态标签显示(关注状态、内容类型等)
|
|
||||||
- 时间排序(最新的在前)
|
|
||||||
|
|
||||||
### 2. 后端接口
|
|
||||||
|
|
||||||
**文件**: `Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/UserActivityController.java`
|
|
||||||
|
|
||||||
**新增接口**:
|
|
||||||
|
|
||||||
#### 2.1 获取关注记录
|
|
||||||
```
|
|
||||||
GET /api/admin/user/follow/records
|
|
||||||
参数:
|
|
||||||
- userId: 用户ID
|
|
||||||
- page: 页码(默认1)
|
|
||||||
- limit: 每页数量(默认10)
|
|
||||||
```
|
|
||||||
|
|
||||||
返回数据:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"data": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"followedId": 41,
|
|
||||||
"followedNickname": "夏至已至",
|
|
||||||
"followStatus": "1",
|
|
||||||
"createTime": "2026-01-03 14:19:16"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 1,
|
|
||||||
"page": 1,
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.2 获取点赞记录
|
|
||||||
```
|
|
||||||
GET /api/admin/user/like/records
|
|
||||||
参数:
|
|
||||||
- userId: 用户ID
|
|
||||||
- page: 页码(默认1)
|
|
||||||
- limit: 每页数量(默认10)
|
|
||||||
```
|
|
||||||
|
|
||||||
返回数据:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"data": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"targetType": "room",
|
|
||||||
"targetId": "8",
|
|
||||||
"targetTitle": "火影忍者",
|
|
||||||
"createTime": "2026-01-03 14:30:00"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 1,
|
|
||||||
"page": 1,
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2.3 获取查看历史
|
|
||||||
```
|
|
||||||
GET /api/admin/user/view/history
|
|
||||||
参数:
|
|
||||||
- userId: 用户ID
|
|
||||||
- page: 页码(默认1)
|
|
||||||
- limit: 每页数量(默认10)
|
|
||||||
```
|
|
||||||
|
|
||||||
返回数据:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": 200,
|
|
||||||
"data": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"targetType": "room",
|
|
||||||
"targetId": "8",
|
|
||||||
"targetTitle": "火影忍者",
|
|
||||||
"viewDuration": 1200,
|
|
||||||
"createTime": "2026-01-03 14:25:00"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"total": 1,
|
|
||||||
"page": 1,
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 数据库表
|
### 3. 数据库表
|
||||||
|
执行 `sql/user_activity_records_update.sql` 创建以下表:
|
||||||
|
- `eb_view_history` - 查看历史记录表
|
||||||
|
- `eb_live_room_like` - 直播间点赞记录表
|
||||||
|
- `eb_wish_like` - 心愿点赞记录表
|
||||||
|
- `eb_search_history` - 搜索历史表
|
||||||
|
- `eb_hot_search` - 热门搜索表
|
||||||
|
- `eb_works_relation` - 作品关系表(点赞、收藏)
|
||||||
|
|
||||||
**文件**: `user_activity_tables.sql`
|
## Android 端实现
|
||||||
|
|
||||||
**新增表**:
|
### 1. API 接口
|
||||||
|
在 `ApiService.java` 中添加了以下接口:
|
||||||
|
- 观看历史相关接口
|
||||||
|
- 点赞记录相关接口
|
||||||
|
- 关注记录接口
|
||||||
|
- 收藏记录接口
|
||||||
|
- 用户活动统计接口
|
||||||
|
|
||||||
#### 3.1 直播间点赞记录表 (eb_live_room_like)
|
### 2. 页面
|
||||||
```sql
|
- `MyRecordsActivity.java` - 我的记录页面(整合所有记录类型)
|
||||||
CREATE TABLE `eb_live_room_like` (
|
- `WatchHistoryActivity.java` - 观看历史页面(已更新使用新 API)
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
||||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
|
||||||
`room_id` varchar(50) NOT NULL COMMENT '直播间ID',
|
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_user_room` (`user_id`, `room_id`)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2 作品点赞记录表 (eb_work_like)
|
### 3. 布局文件
|
||||||
```sql
|
- `activity_my_records.xml` - 我的记录页面布局
|
||||||
CREATE TABLE `eb_work_like` (
|
- `fragment_record_list.xml` - 记录列表 Fragment 布局
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
- `item_record.xml` - 记录列表项布局
|
||||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
- `activity_watch_history.xml` - 观看历史页面布局
|
||||||
`work_id` bigint(20) NOT NULL COMMENT '作品ID',
|
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_user_work` (`user_id`, `work_id`)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3 心愿点赞记录表 (eb_wish_like)
|
### 4. 入口
|
||||||
```sql
|
在 `ProfileActivity` 中添加了"我的记录"快捷入口(action4)
|
||||||
CREATE TABLE `eb_wish_like` (
|
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
||||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
|
||||||
`wish_id` bigint(20) NOT NULL COMMENT '心愿ID',
|
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_user_wish` (`user_id`, `wish_id`)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.4 查看历史记录表 (eb_view_history)
|
## 管理端前端实现
|
||||||
```sql
|
|
||||||
CREATE TABLE `eb_view_history` (
|
### 1. API 文件
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
- `src/api/userActivity.js` - 用户活动记录 API
|
||||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
|
||||||
`target_type` varchar(20) NOT NULL COMMENT '目标类型',
|
### 2. 页面更新
|
||||||
`target_id` varchar(50) NOT NULL COMMENT '目标ID',
|
- `src/views/user/list/userDetails.vue` - 用户详情页面
|
||||||
`target_title` varchar(255) DEFAULT NULL COMMENT '目标标题',
|
- 添加了"关注记录"标签页
|
||||||
`view_duration` int(11) DEFAULT 0 COMMENT '观看时长(秒)',
|
- 添加了"点赞记录"标签页
|
||||||
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
- 添加了"查看历史"标签页
|
||||||
PRIMARY KEY (`id`),
|
- 添加了"收藏记录"标签页
|
||||||
KEY `idx_user_id` (`user_id`)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 部署步骤
|
## 部署步骤
|
||||||
|
|
||||||
### 1. 创建数据库表
|
1. **数据库更新**
|
||||||
```bash
|
```sql
|
||||||
mysql -u root -p zhibo < user_activity_tables.sql
|
-- 执行 SQL 脚本
|
||||||
```
|
source sql/user_activity_records_update.sql
|
||||||
|
```
|
||||||
|
|
||||||
### 2. 编译后端代码
|
2. **后端部署**
|
||||||
```bash
|
- 重新编译并部署 Java 后端服务
|
||||||
cd Zhibo/zhibo-h
|
|
||||||
mvn clean package -DskipTests -pl crmeb-admin -am
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 重启后端服务
|
3. **管理端前端部署**
|
||||||
```bash
|
- 重新构建并部署管理端前端
|
||||||
cd /root/zhibo/Zhibo/zhibo-h/crmeb-admin
|
|
||||||
./restart.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 前端无需重新编译
|
4. **Android 端**
|
||||||
前端代码修改后,刷新浏览器即可看到新功能。
|
- 重新编译 APK 并安装
|
||||||
|
|
||||||
## 使用说明
|
|
||||||
|
|
||||||
### 1. 查看用户活动记录
|
|
||||||
|
|
||||||
1. 登录管理后台
|
|
||||||
2. 进入"用户管理" -> "用户列表"
|
|
||||||
3. 点击某个用户的"详情"按钮
|
|
||||||
4. 在弹出的抽屉中,可以看到新增的三个标签页:
|
|
||||||
- 关注记录
|
|
||||||
- 点赞记录
|
|
||||||
- 查看历史
|
|
||||||
|
|
||||||
### 2. 数据说明
|
|
||||||
|
|
||||||
**关注记录**:
|
|
||||||
- 显示用户关注了哪些人
|
|
||||||
- 包含关注状态(已关注/已取消)
|
|
||||||
- 按关注时间倒序排列
|
|
||||||
|
|
||||||
**点赞记录**:
|
|
||||||
- 显示用户点赞的所有内容
|
|
||||||
- 包含类型标签(直播间/作品/心愿)
|
|
||||||
- 按点赞时间倒序排列
|
|
||||||
|
|
||||||
**查看历史**:
|
|
||||||
- 显示用户的浏览记录
|
|
||||||
- 包含观看时长
|
|
||||||
- 按查看时间倒序排列
|
|
||||||
|
|
||||||
### 3. 数据收集
|
|
||||||
|
|
||||||
这些数据需要在应用中主动记录:
|
|
||||||
|
|
||||||
**点赞记录**:
|
|
||||||
- 用户点赞直播间时,插入 `eb_live_room_like` 表
|
|
||||||
- 用户点赞作品时,插入 `eb_work_like` 表
|
|
||||||
- 用户点赞心愿时,插入 `eb_wish_like` 表
|
|
||||||
|
|
||||||
**查看历史**:
|
|
||||||
- 用户进入直播间时,记录到 `eb_view_history` 表
|
|
||||||
- 用户查看作品时,记录到 `eb_view_history` 表
|
|
||||||
- 用户访问他人主页时,记录到 `eb_view_history` 表
|
|
||||||
|
|
||||||
## 测试数据
|
|
||||||
|
|
||||||
可以使用以下 SQL 插入测试数据:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 插入点赞记录
|
|
||||||
INSERT INTO eb_live_room_like (user_id, room_id, create_time)
|
|
||||||
VALUES (43, '8', '2026-01-03 14:30:00');
|
|
||||||
|
|
||||||
-- 插入查看历史
|
|
||||||
INSERT INTO eb_view_history (user_id, target_type, target_id, target_title, view_duration, create_time)
|
|
||||||
VALUES (43, 'room', '8', '火影忍者', 1200, '2026-01-03 14:25:00');
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **性能优化**:
|
1. 后端使用 JdbcTemplate 直接操作数据库,确保数据库表已创建
|
||||||
- 所有表都添加了索引,确保查询性能
|
2. Android 端需要登录后才能使用记录功能
|
||||||
- 使用分页查询,避免一次加载过多数据
|
3. 管理端可以查看任意用户的活动记录
|
||||||
|
|
||||||
2. **数据一致性**:
|
|
||||||
- 点赞表使用唯一索引,防止重复点赞
|
|
||||||
- 查看历史可以重复记录,用于统计观看次数
|
|
||||||
|
|
||||||
3. **扩展性**:
|
|
||||||
- 可以轻松添加更多类型的点赞记录
|
|
||||||
- 查看历史支持多种目标类型
|
|
||||||
|
|
||||||
4. **隐私保护**:
|
|
||||||
- 只有管理员可以查看用户的活动记录
|
|
||||||
- 前端用户无法查看其他用户的私密数据
|
|
||||||
|
|
||||||
## 后续优化建议
|
|
||||||
|
|
||||||
1. **数据统计**:
|
|
||||||
- 添加用户活跃度统计
|
|
||||||
- 生成用户行为分析报告
|
|
||||||
|
|
||||||
2. **数据导出**:
|
|
||||||
- 支持导出用户活动记录为 Excel
|
|
||||||
- 用于数据分析和备份
|
|
||||||
|
|
||||||
3. **实时更新**:
|
|
||||||
- 使用 WebSocket 实时推送新的活动记录
|
|
||||||
- 提升管理员的监控体验
|
|
||||||
|
|
||||||
4. **数据清理**:
|
|
||||||
- 定期清理过期的查看历史记录
|
|
||||||
- 避免数据库膨胀
|
|
||||||
|
|
||||||
## 相关文件
|
|
||||||
|
|
||||||
- 前端页面: `Zhibo/admin/src/views/user/list/userDetails.vue`
|
|
||||||
- 后端控制器: `Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/UserActivityController.java`
|
|
||||||
- 数据库脚本: `user_activity_tables.sql`
|
|
||||||
- 说明文档: `用户活动记录功能说明.md`
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user