From 5b0c786f1fcadf4c38466fa8ab2d9fceed0d7018 Mon Sep 17 00:00:00 2001 From: xiao12feng8 <16507319+xiao12feng8@user.noreply.gitee.com> Date: Tue, 30 Dec 2025 09:31:15 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dapp=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 0-待完成接入接口.md | 167 ++++++++++++++++++ android-app/app/release-key.jks | Bin 0 -> 2226 bytes .../livestreaming/FansListActivity.java | 6 +- .../livestreaming/FollowingListActivity.java | 6 +- .../livestreaming/ProfileActivity.java | 2 +- .../livestreaming/PublishWorkActivity.java | 12 +- .../livestreaming/RoomDetailActivity.java | 15 +- .../example/livestreaming/SearchActivity.java | 12 +- .../UserProfileReadOnlyActivity.java | 23 +-- .../livestreaming/WorkDetailActivity.java | 16 +- .../example/livestreaming/net/ApiService.java | 9 +- 11 files changed, 209 insertions(+), 59 deletions(-) create mode 100644 0-待完成接入接口.md create mode 100644 android-app/app/release-key.jks diff --git a/0-待完成接入接口.md b/0-待完成接入接口.md new file mode 100644 index 00000000..b14c635c --- /dev/null +++ b/0-待完成接入接口.md @@ -0,0 +1,167 @@ +# 待完成接入接口清单 + +> 更新时间: 2024-12-30 + +## 📊 总体情况 + +| 类别 | 后端接口数 | Android已接入 | 待接入 | +|------|-----------|--------------|--------| +| 总计 | 131 | ~85 | ~46 | + +--- + +## ✅ 已接入模块 (无需处理) + +| 模块 | 接口数 | 状态 | +|------|--------|------| +| 用户认证 | 4 | ✅ 全部接入 | +| 用户信息 | 2 | ✅ 全部接入 | +| 直播间 | 6 | ✅ 全部接入 | +| 直播弹幕 | 2 | ✅ 全部接入 | +| 礼物打赏 | 5 | ✅ 全部接入 | +| 私聊会话 | 8 | ✅ 全部接入 | +| 好友管理 | 9 | ✅ 全部接入 | +| 文件上传 | 2 | ✅ 全部接入 | +| 在线状态 | 5 | ✅ 全部接入 | +| 离线消息 | 3 | ✅ 全部接入 | +| 消息表情回应 | 4 | ✅ 全部接入 | +| 关注功能 | 7 | ✅ 全部接入 | +| 作品管理 | 13 | ✅ 全部接入 | +| 搜索功能 | 9 | ✅ 全部接入 | +| 支付集成 | 4 | ✅ 全部接入 | +| 通话功能 | 10 | ✅ 全部接入 | + +--- + +## ❌ 待接入模块 + +### 1. 群组管理 (10个接口) - 🔴 高优先级 + +后端已完成,Android端未定义接口。 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 创建群组 | `POST /api/front/groups/create` | 创建新群组 | +| 群组列表 | `GET /api/front/groups/list` | 获取我的群组 | +| 群组详情 | `GET /api/front/groups/{groupId}` | 获取群组信息 | +| 更新群组 | `PUT /api/front/groups/{groupId}` | 修改群组信息 | +| 解散群组 | `DELETE /api/front/groups/{groupId}` | 解散群组 | +| 添加成员 | `POST /api/front/groups/{groupId}/members` | 邀请成员 | +| 移除成员 | `DELETE /api/front/groups/{groupId}/members/{userId}` | 踢出成员 | +| 成员列表 | `GET /api/front/groups/{groupId}/members` | 获取成员 | +| 退出群组 | `POST /api/front/groups/{groupId}/leave` | 主动退群 | +| 转让群主 | `POST /api/front/groups/{groupId}/transfer` | 转让群主 | + +--- + +### 2. 群组消息 (4个接口) - 🔴 高优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 发送群消息 | `POST /api/front/groups/{groupId}/messages` | 发送消息 | +| 群消息历史 | `GET /api/front/groups/{groupId}/messages` | 获取历史 | +| 撤回消息 | `DELETE /api/front/groups/{groupId}/messages/{messageId}` | 撤回消息 | +| 转发消息 | `POST /api/front/groups/{groupId}/messages/{messageId}/forward` | 转发消息 | + +--- + +### 3. 消息搜索 (3个接口) - 🟡 中优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 搜索会话 | `GET /api/front/messages/search/conversations` | 搜索会话 | +| 搜索消息 | `GET /api/front/messages/search/messages` | 搜索消息内容 | +| 全局搜索 | `GET /api/front/messages/search/global` | 全局搜索 | + +--- + +### 4. 评论功能 (8个接口) - 🟡 中优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 发布评论 | `POST /api/front/works/comment/publish` | 发布评论 | +| 评论列表 | `GET /api/front/works/comment/list/{worksId}` | 获取评论 | +| 点赞评论 | `POST /api/front/works/comment/like/{commentId}` | 点赞 | +| 取消点赞 | `POST /api/front/works/comment/unlike/{commentId}` | 取消点赞 | +| 删除评论 | `POST /api/front/works/comment/delete/{commentId}` | 删除评论 | +| 回复列表 | `GET /api/front/works/comment/reply/list/{commentId}` | 获取回复 | +| 评论详情 | `GET /api/front/works/comment/detail/{commentId}` | 评论详情 | +| 检查点赞 | `GET /api/front/works/comment/check-liked/{commentId}` | 检查状态 | + +--- + +### 5. 通知推送 (9个接口) - 🟡 中优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 通知列表 | `GET /api/front/notification/list` | 获取通知 | +| 未读数量 | `GET /api/front/notification/unread-count` | 未读数 | +| 标记已读 | `POST /api/front/notification/mark-read/{id}` | 单条已读 | +| 全部已读 | `POST /api/front/notification/mark-all-read` | 全部已读 | +| 注册FCM | `POST /api/front/notification/fcm/register` | 注册推送 | +| 移除FCM | `POST /api/front/notification/fcm/remove` | 移除推送 | +| 删除通知 | `DELETE /api/front/notification/{id}` | 删除单条 | +| 清空通知 | `DELETE /api/front/notification/clear-all` | 清空全部 | +| 按类型统计 | `GET /api/front/notification/unread-count-by-type` | 分类统计 | + +--- + +### 6. 分类管理 (7个接口) - 🟢 低优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 直播间分类 | `GET /api/front/category/live-room` | 直播分类 | +| 作品分类 | `GET /api/front/category/work` | 作品分类 | +| 分类列表 | `GET /api/front/category/list` | 全部分类 | +| 分类详情 | `GET /api/front/category/{id}` | 分类详情 | +| 分类统计 | `GET /api/front/category/statistics` | 统计数据 | +| 热门分类 | `GET /api/front/category/hot` | 热门分类 | +| 子分类 | `GET /api/front/category/{parentId}/children` | 子分类 | + +--- + +### 7. 文件上传补充 (3个接口) - 🟢 低优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 通用图片上传 | `POST /api/upload/image` | 通用图片 | +| 通用文件上传 | `POST /api/upload/file` | 通用文件 | +| 语音上传 | `POST /api/upload/chat/voice` | 语音消息 | + +--- + +### 8. 直播间补充 (4个接口) - 🟢 低优先级 + +| 接口 | 路径 | 说明 | +|------|------|------| +| 开始直播 | `POST /api/front/live/room/{id}/start` | 开播 | +| 结束直播 | `POST /api/front/live/room/{id}/stop` | 停播 | +| 观众列表 | `GET /api/rooms/{roomId}/viewers` | 观众列表 | +| 手动广播人数 | `POST /api/live/online/broadcast/{roomId}` | 广播人数 | + +--- + +## 📋 接入优先级建议 + +### 第一优先级 (核心社交) +1. **群组管理** - 10个接口 +2. **群组消息** - 4个接口 + +### 第二优先级 (内容互动) +3. **评论功能** - 8个接口 +4. **通知推送** - 9个接口 + +### 第三优先级 (辅助功能) +5. **消息搜索** - 3个接口 +6. **分类管理** - 7个接口 +7. **文件上传补充** - 3个接口 +8. **直播间补充** - 4个接口 + +--- + +## 📝 备注 + +- 后端接口已全部完成 (131个) +- Android端已接入约85个接口 +- 待接入约46个接口 +- 核心功能已基本完成,待接入的主要是群组和辅助功能 diff --git a/android-app/app/release-key.jks b/android-app/app/release-key.jks new file mode 100644 index 0000000000000000000000000000000000000000..74c4fc78752b4ceb8d17994090972cea09370d62 GIT binary patch literal 2226 zcmchY={wX58^>p7m|+;pAY+e+expGpwAhCT5r>d{O&TLhwj?r9mWq*`6460rdF)HJ z7-zCY36&*dj;4+!%{)`joAVbu*L~eDzTa2(c71LRlfwjoKoDMnf2Tlj0LAADIo!uH zglEwJuX79#2o3_0V0(ZNLO=rnhJn=~B498a1WSUg6xfI^RgmoF2Iw}i?+>0e&N5P(&G?=bxvGTcP5IgcE&hf`h%`R!ZWNSja)8RQEsoHsiWC>DSZJ>WBDm>)X4p_}V`*wi0qwT({ou1VeXyQ~2dDs$`WC?-c?4h<@ zsog&}D}qSH2A_Z6G1V+vr0cX0L@fSm2uG|zYBv4cwj`}=D9UlW6LQI}eN24#eX~C2 z$x5cs@G++TvI&v8zMAaGU2(l!v4`p9bdSZXG>kr~Z)td)DAy<3ul#ktw#I02Hd+NQ zc29S*_25r|5;w#NPQHbeD(QA4q=3|nOY3{_7eZZE_3x^?Vz4IpFQ({xt=%lk@KLTX zScR@Q(rdpx*9CKJR#YOjlr^Z^iGDsq&7<1PBmz1v7gWC5QD8N;CPb4LQ5omT0x@_8 zV#d^3n!8>KvxvK_>0TiEcDpf1bb=8R7DMW8ci+h}39N836(9&j9!oQ+ED;yE?#v}F z3<31~OJ|TvG>6s_v~~IFOMM3ONK@mcX0)(!CM(nSINLC*^t-sI-etJUjQ-^d7p7a{ z$C~#IC&p;s31qjgJ#DqqJ zmF(AHJoK>1FL)4VJ^!>7Gu%GeJX4iGVzBQAVz=Wa{G_P$HHg64fs7_=4Ydr1$GV?< z%&m#IWkv}+DYEcV?uTnOvvCfO`~xf~u{p;TaxYFokoSyAT&pzYHNDWRUJ>)dg>?~ zqU1|~5Gf)|%bFn*;kgMi5s@OP(K|&Y32UoKB+J>4D=$*VS)uLzCd>K?mERJ9O~LPy zt4`vD!#P5}>EoIkRex7UhsaovGpz{eA3kTGN4l0>r%W!jpY`C|Q{&Ycnk@ zj&quSimP0p*XKKZ{QF}sE*Hu9c`p*nBp&?eUmdhGjR=`AOPXHFU%=PQ&hV)Yg4ka@ zGQf&|x^VHS9n?OZV1JRbwfy2*GONW+Yag7Y^#TJzlZ^Z|HHs%dQu$UJ((&g{Kj}@G zu;^}`PqFkpOGQ4g2wVT+5&8PuUT2zmnbsu^LS8>QD|?4BUGB{+FuG_mv1KsWXWQ|Z zssNH5RYlPlo6!siEH_`=EmiL|Yh@<%ph3K4l}dUo2rvu1Q1Pl+ZJSl+XJAEl6%ri8KfC2=5If7WIu@8lS1w?)+C|1CV$7lQB zdi@31a`=zUBA=w4|L$4glkX zub^g&@5|UJ8&_|SDz0@_NH)=&>d{_|SRkW=w)e-&MO0;e_B}@JwzPeGgb&uEw&t6{ zYDs_2Df1m>0&Qdmb&ySz7RmGf7Ey|M?bbi|IK7HNerow>YiR&=y^wYQ*G0#Qtchkx zAn{L%N=vH}0$JM4>oK#6djt<-&qL==gf3$~CqU!P6O43N?5?%Bwj{x~Z|Xv5_pcK3 zb~%zKnR6`rysASH-NsqwMZ1V_O}(4XY*nH%+JX{~oyyKi#kDp(cx7lfc^#Asfr3Hc zG6g^mkmYSqRtP2m6ARqj5OP?Uq5tQoWX0d!p?M9eNBaLgMBe@Ih6o0s_bF^$YY>s9 zaI}5VRjBToSwbDd7BVdUO6n?xn`M@9L`=gmX1Z$S?$tz{4NeX&S7Bx!MjR>dKKR8I zr`!x|Iv3v!8FOXph459wL2W%&@5NNK!rL(Wt*Z~R-JW@9HH0`DI+ZW>t&LygcFEs1 z%T{Z)*tnH}9-*f8|1rA94VpVV#4 zPG%_7iKiFt*6xoIZ8M!AxTJV>-bhUh#-4Xkd$u(2*hu|d#7_q@*U?{ikPjJa{ub<1 zZC6g-A7}UDcHQN8R1fF+g~$iN&vkLmCB54$ko&}sXZd6WwfUF3rye}4XiUyJD742A MMGGTd6JtyM3sYLs0RR91 literal 0 HcmV?d00001 diff --git a/android-app/app/src/main/java/com/example/livestreaming/FansListActivity.java b/android-app/app/src/main/java/com/example/livestreaming/FansListActivity.java index 6d917769..c9f2eb33 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/FansListActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/FansListActivity.java @@ -13,7 +13,7 @@ import com.example.livestreaming.databinding.ActivityFansListBinding; import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.ApiService; import com.example.livestreaming.net.PageResponse; -import com.example.livestreaming.net.RetrofitClient; +import com.example.livestreaming.net.ApiClient; import java.util.ArrayList; import java.util.List; @@ -58,7 +58,7 @@ public class FansListActivity extends AppCompatActivity { if (isLoading) return; isLoading = true; - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>>> call = apiService.getFollowersList(currentPage, 20); call.enqueue(new Callback>>>() { @@ -96,7 +96,7 @@ public class FansListActivity extends AppCompatActivity { } } else { Toast.makeText(FansListActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "获取粉丝列表失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "获取粉丝列表失败", Toast.LENGTH_SHORT).show(); } } else { diff --git a/android-app/app/src/main/java/com/example/livestreaming/FollowingListActivity.java b/android-app/app/src/main/java/com/example/livestreaming/FollowingListActivity.java index c64c3a22..cab286c5 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/FollowingListActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/FollowingListActivity.java @@ -13,7 +13,7 @@ import com.example.livestreaming.databinding.ActivityFollowingListBinding; import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.ApiService; import com.example.livestreaming.net.PageResponse; -import com.example.livestreaming.net.RetrofitClient; +import com.example.livestreaming.net.ApiClient; import java.util.ArrayList; import java.util.List; @@ -58,7 +58,7 @@ public class FollowingListActivity extends AppCompatActivity { if (isLoading) return; isLoading = true; - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>>> call = apiService.getFollowingList(currentPage, 20); call.enqueue(new Callback>>>() { @@ -94,7 +94,7 @@ public class FollowingListActivity extends AppCompatActivity { } } else { Toast.makeText(FollowingListActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "获取关注列表失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "获取关注列表失败", Toast.LENGTH_SHORT).show(); } } else { diff --git a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java index 5dc3ebba..cd98c15b 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ProfileActivity.java @@ -679,7 +679,7 @@ public class ProfileActivity extends AppCompatActivity { */ private void loadFollowStats() { com.example.livestreaming.net.ApiService apiService = - com.example.livestreaming.net.RetrofitClient.getInstance(this).getApiService(); + com.example.livestreaming.net.ApiClient.getService(this); retrofit2.Call>> call = apiService.getFollowStats(null); // null表示查询当前用户 diff --git a/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java b/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java index 5aa7fb84..7dd0d7eb 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/PublishWorkActivity.java @@ -638,7 +638,7 @@ public class PublishWorkActivity extends AppCompatActivity { RequestBody model = RequestBody.create(okhttp3.MediaType.parse("text/plain"), "works"); RequestBody pid = RequestBody.create(okhttp3.MediaType.parse("text/plain"), "0"); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call = apiService.uploadImage(body, model, pid); call.enqueue(new retrofit2.Callback>() { @@ -649,7 +649,7 @@ public class PublishWorkActivity extends AppCompatActivity { if (apiResponse.getCode() == 200 && apiResponse.getData() != null) { callback.onSuccess(apiResponse.getData().getUrl()); } else { - callback.onFailure(apiResponse.getMsg() != null ? apiResponse.getMsg() : "上传失败"); + callback.onFailure(apiResponse.getMessage() != null ? apiResponse.getMessage() : "上传失败"); } } else { callback.onFailure("上传失败"); @@ -687,7 +687,7 @@ public class PublishWorkActivity extends AppCompatActivity { RequestBody model = RequestBody.create(okhttp3.MediaType.parse("text/plain"), "works"); RequestBody pid = RequestBody.create(okhttp3.MediaType.parse("text/plain"), "0"); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call = apiService.uploadVideo(body, model, pid); call.enqueue(new retrofit2.Callback>() { @@ -698,7 +698,7 @@ public class PublishWorkActivity extends AppCompatActivity { if (apiResponse.getCode() == 200 && apiResponse.getData() != null) { callback.onSuccess(apiResponse.getData().getUrl()); } else { - callback.onFailure(apiResponse.getMsg() != null ? apiResponse.getMsg() : "上传失败"); + callback.onFailure(apiResponse.getMessage() != null ? apiResponse.getMessage() : "上传失败"); } } else { callback.onFailure("上传失败"); @@ -762,7 +762,7 @@ public class PublishWorkActivity extends AppCompatActivity { request.setVideoUrl(videoUrl); request.setImageUrls(imageUrls); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call = apiService.publishWork(request); call.enqueue(new retrofit2.Callback>() { @@ -777,7 +777,7 @@ public class PublishWorkActivity extends AppCompatActivity { finish(); } else { Toast.makeText(PublishWorkActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "发布失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "发布失败", Toast.LENGTH_SHORT).show(); } } else { diff --git a/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java b/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java index a6eb75c8..10a05907 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/RoomDetailActivity.java @@ -34,7 +34,6 @@ import com.example.livestreaming.net.CreateRechargeResponse; import com.example.livestreaming.net.OrderPayRequest; import com.example.livestreaming.net.OrderPayResultResponse; import com.example.livestreaming.net.RechargeOptionResponse; -import com.example.livestreaming.net.RetrofitClient; import com.example.livestreaming.net.Room; import com.example.livestreaming.net.StreamConfig; import com.example.livestreaming.ShareUtils; @@ -1112,7 +1111,7 @@ public class RoomDetailActivity extends AppCompatActivity { * 加载充值选项列表 */ private void loadRechargeOptions(RechargeAdapter adapter, View dialogView) { - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>> call = apiService.getRechargeOptions(); call.enqueue(new Callback>>() { @@ -1135,7 +1134,7 @@ public class RoomDetailActivity extends AppCompatActivity { adapter.setOptions(options); } else { Toast.makeText(RoomDetailActivity.this, - "加载充值选项失败: " + apiResponse.getMsg(), + "加载充值选项失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); // 使用默认选项 setDefaultRechargeOptions(adapter); @@ -1178,7 +1177,7 @@ public class RoomDetailActivity extends AppCompatActivity { * 创建充值订单 */ private void createRechargeOrder(RechargeOption selectedOption, androidx.appcompat.app.AlertDialog rechargeDialog) { - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); CreateRechargeRequest request = new CreateRechargeRequest( Integer.parseInt(selectedOption.getId()), @@ -1203,7 +1202,7 @@ public class RoomDetailActivity extends AppCompatActivity { showPaymentMethodDialog(orderId, selectedOption, rechargeDialog); } else { Toast.makeText(RoomDetailActivity.this, - "创建充值订单失败: " + apiResponse.getMsg(), + "创建充值订单失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); } } else { @@ -1264,10 +1263,10 @@ public class RoomDetailActivity extends AppCompatActivity { */ private void processPayment(String orderId, String payType, String payChannel, RechargeOption selectedOption, androidx.appcompat.app.AlertDialog rechargeDialog) { - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); OrderPayRequest payRequest = new OrderPayRequest(orderId, payType, payChannel); - Call> call = apiService.payment(payRequest); + Call> call = apiService.processPayment(payRequest); call.enqueue(new Callback>() { @Override @@ -1293,7 +1292,7 @@ public class RoomDetailActivity extends AppCompatActivity { simulateRechargeSuccess(selectedOption, rechargeDialog); } else { Toast.makeText(RoomDetailActivity.this, - "支付失败: " + apiResponse.getMsg(), + "支付失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); } } else { diff --git a/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java b/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java index 9b287f4b..a3683950 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/SearchActivity.java @@ -18,7 +18,7 @@ import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.ApiService; import com.example.livestreaming.net.HotSearchResponse; import com.example.livestreaming.net.PageResponse; -import com.example.livestreaming.net.RetrofitClient; +import com.example.livestreaming.net.ApiClient; import com.example.livestreaming.net.Room; import com.example.livestreaming.net.SearchHistoryResponse; @@ -151,7 +151,7 @@ public class SearchActivity extends AppCompatActivity { Log.d(TAG, "执行搜索: " + keyword); - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>>> call = apiService.searchLiveRooms(keyword, null, null, 1, 20); @@ -191,9 +191,9 @@ public class SearchActivity extends AppCompatActivity { } } else { Toast.makeText(SearchActivity.this, - "搜索失败: " + apiResponse.getMsg(), + "搜索失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); - Log.e(TAG, "搜索失败: " + apiResponse.getMsg()); + Log.e(TAG, "搜索失败: " + apiResponse.getMessage()); } } else { Toast.makeText(SearchActivity.this, @@ -253,7 +253,7 @@ public class SearchActivity extends AppCompatActivity { private void loadHotSearch() { Log.d(TAG, "加载热门搜索"); - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>> call = apiService.getHotSearch(2, 10); // 2-直播间 call.enqueue(new Callback>>() { @@ -282,7 +282,7 @@ public class SearchActivity extends AppCompatActivity { * 加载搜索建议(可选功能) */ private void loadSearchSuggestions(String keyword) { - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>> call = apiService.getSearchSuggestions(keyword, 2, 10); // 2-直播间 call.enqueue(new Callback>>() { diff --git a/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java b/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java index 5ebf5e0e..cfda302a 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/UserProfileReadOnlyActivity.java @@ -12,7 +12,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import com.example.livestreaming.databinding.ActivityUserProfileReadOnlyBinding; import com.example.livestreaming.net.ApiResponse; import com.example.livestreaming.net.ApiService; -import com.example.livestreaming.net.RetrofitClient; +import com.example.livestreaming.net.ApiClient; import com.google.android.material.tabs.TabLayout; import java.util.ArrayList; @@ -101,7 +101,7 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity { try { int userId = Integer.parseInt(currentUserId); - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>> call = apiService.checkFollowStatus(userId); call.enqueue(new Callback>>() { @@ -137,7 +137,7 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity { Map requestBody = new HashMap<>(); requestBody.put("userId", userId); - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>> call = apiService.followUser(requestBody); call.enqueue(new Callback>>() { @@ -152,7 +152,7 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity { Toast.makeText(UserProfileReadOnlyActivity.this, "关注成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(UserProfileReadOnlyActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "关注失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "关注失败", Toast.LENGTH_SHORT).show(); } } else { @@ -179,7 +179,7 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity { Map requestBody = new HashMap<>(); requestBody.put("userId", userId); - ApiService apiService = RetrofitClient.getInstance(this).getApiService(); + ApiService apiService = ApiClient.getService(this); Call>> call = apiService.unfollowUser(requestBody); call.enqueue(new Callback>>() { @@ -194,7 +194,7 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity { Toast.makeText(UserProfileReadOnlyActivity.this, "取消关注成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(UserProfileReadOnlyActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "取消关注失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "取消关注失败", Toast.LENGTH_SHORT).show(); } } else { @@ -224,17 +224,6 @@ public class UserProfileReadOnlyActivity extends AppCompatActivity { } } - private void updateFollowButton() { - if (binding == null) return; - if (isFollowing) { - binding.addFriendButton.setText("已关注"); - binding.addFriendButton.setAlpha(0.7f); - } else { - binding.addFriendButton.setText("关注"); - binding.addFriendButton.setAlpha(1f); - } - } - private void setupTabsAndWorks() { if (binding == null) return; diff --git a/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java b/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java index 3c6fa8f6..925778eb 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/WorkDetailActivity.java @@ -443,7 +443,7 @@ public class WorkDetailActivity extends AppCompatActivity { try { long worksId = Long.parseLong(workItem.getId()); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call; if (isLiked) { @@ -480,7 +480,7 @@ public class WorkDetailActivity extends AppCompatActivity { workItem.setLikeCount(oldCount); updateLikeButton(); Toast.makeText(WorkDetailActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "操作失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "操作失败", Toast.LENGTH_SHORT).show(); } } else { @@ -514,7 +514,7 @@ public class WorkDetailActivity extends AppCompatActivity { try { long worksId = Long.parseLong(workItem.getId()); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call; if (isFavorited) { @@ -544,7 +544,7 @@ public class WorkDetailActivity extends AppCompatActivity { isFavorited = oldFavorited; updateFavoriteButton(); Toast.makeText(WorkDetailActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "操作失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "操作失败", Toast.LENGTH_SHORT).show(); } } else { @@ -699,7 +699,7 @@ public class WorkDetailActivity extends AppCompatActivity { try { long worksId = Long.parseLong(workItem.getId()); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call = apiService.deleteWork(worksId); call.enqueue(new retrofit2.Callback>() { @@ -712,7 +712,7 @@ public class WorkDetailActivity extends AppCompatActivity { finish(); } else { Toast.makeText(WorkDetailActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "删除失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "删除失败", Toast.LENGTH_SHORT).show(); } } else { @@ -787,7 +787,7 @@ public class WorkDetailActivity extends AppCompatActivity { try { long worksId = Long.parseLong(workId); - ApiService apiService = ApiClient.getApiService(this); + ApiService apiService = ApiClient.getService(this); Call> call = apiService.getWorkDetail(worksId); call.enqueue(new retrofit2.Callback>() { @@ -813,7 +813,7 @@ public class WorkDetailActivity extends AppCompatActivity { setupActionButton(); } else { Toast.makeText(WorkDetailActivity.this, - apiResponse.getMsg() != null ? apiResponse.getMsg() : "获取作品详情失败", + apiResponse.getMessage() != null ? apiResponse.getMessage() : "获取作品详情失败", Toast.LENGTH_SHORT).show(); finish(); } diff --git a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java index 76a46fce..e7f34063 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java +++ b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java @@ -349,15 +349,10 @@ public interface ApiService { // ==================== 支付集成 ==================== - @GET("api/front/gift/recharge/options") - Call>> getRechargeOptions(); - - @POST("api/front/gift/recharge/create") - Call> createRecharge(@Body CreateRechargeRequest body); - @GET("api/front/pay/alipay/queryPayResult") Call> queryAliPayResult(@Query("orderNo") String orderNo); @POST("api/front/pay/payment") - Call> payment(@Body OrderPayRequest body); + Call> processPayment(@Body OrderPayRequest body); + } From 7d1896d9d2262f396a7744bdebb7cd07bfd1e32b Mon Sep 17 00:00:00 2001 From: xiao12feng8 <16507319+xiao12feng8@user.noreply.gitee.com> Date: Tue, 30 Dec 2025 10:10:35 +0800 Subject: [PATCH 2/3] =?UTF-8?q?bug=EF=BC=9A=E4=BF=AE=E5=A4=8D=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E4=B8=8D=E8=83=BD=E6=AD=A3=E7=A1=AE=E5=88=A4=E6=96=AD?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/livestreaming/ChatMessage.java | 26 ++++++++++- .../livestreaming/ConversationActivity.java | 45 +++++++++++++++++-- .../ConversationMessagesAdapter.java | 3 +- .../livestreaming/RoomDetailActivity.java | 36 +++++++++------ .../example/livestreaming/net/ApiService.java | 5 --- 5 files changed, 92 insertions(+), 23 deletions(-) diff --git a/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java b/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java index bbf09bf1..c0dfecff 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ChatMessage.java @@ -25,6 +25,7 @@ public class ChatMessage { private long timestamp; private boolean isSystemMessage; private boolean isGiftMessage; // 是否是礼物消息 + private boolean isOutgoing; // 是否是自己发送的消息 private MessageStatus status; private String avatarUrl; // 发送者头像 URL,后续从后端获取 @@ -49,7 +50,8 @@ public class ChatMessage { this.isGiftMessage = false; this.messageType = MessageType.TEXT; // 如果是自己发送的消息,初始状态为发送中 - this.status = "我".equals(username) ? MessageStatus.SENDING : MessageStatus.SENT; + this.isOutgoing = "我".equals(username); + this.status = this.isOutgoing ? MessageStatus.SENDING : MessageStatus.SENT; } // 系统消息构造函数 @@ -86,6 +88,20 @@ public class ChatMessage { this.isSystemMessage = isSystemMessage; this.status = status; this.messageType = MessageType.TEXT; + this.isOutgoing = "我".equals(username); + } + + // 完整构造函数(从后端数据构造,带 isOutgoing 参数) + public ChatMessage(String messageId, String username, String message, long timestamp, + boolean isSystemMessage, MessageStatus status, boolean isOutgoing) { + this.messageId = messageId; + this.username = username; + this.message = message; + this.timestamp = timestamp; + this.isSystemMessage = isSystemMessage; + this.status = status; + this.messageType = MessageType.TEXT; + this.isOutgoing = isOutgoing; } // 图片消息构造函数 @@ -140,6 +156,14 @@ public class ChatMessage { isGiftMessage = giftMessage; } + public boolean isOutgoing() { + return isOutgoing; + } + + public void setOutgoing(boolean outgoing) { + isOutgoing = outgoing; + } + public MessageStatus getStatus() { return status; } diff --git a/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java index 451ff398..d2712b55 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java @@ -181,7 +181,8 @@ public class ConversationActivity extends AppCompatActivity { return; } - String url = ApiConfig.getBaseUrl() + "/api/front/user/info"; + // 使用正确的用户信息接口 + String url = ApiConfig.getBaseUrl() + "/api/front/user"; Log.d(TAG, "获取用户信息: " + url); Request request = new Request.Builder() @@ -205,14 +206,31 @@ public class ConversationActivity extends AppCompatActivity { if (json.optInt("code", -1) == 200) { JSONObject data = json.optJSONObject("data"); if (data != null) { + // 尝试多种方式获取用户ID int uid = data.optInt("uid", 0); + if (uid == 0) { + uid = data.optInt("id", 0); + } + if (uid == 0) { + // 尝试从字符串解析 + String uidStr = data.optString("uid", ""); + if (!uidStr.isEmpty()) { + try { + uid = (int) Double.parseDouble(uidStr); + } catch (NumberFormatException e) { + Log.e(TAG, "解析uid失败: " + uidStr, e); + } + } + } if (uid > 0) { currentUserId = String.valueOf(uid); // 保存到 AuthStore - AuthStore.setUserInfo(ConversationActivity.this, currentUserId, data.optString("nickname", "")); + AuthStore.setUserInfo(ConversationActivity.this, currentUserId, data.optString("nickname", data.optString("nikeName", ""))); Log.d(TAG, "从服务器获取到用户ID: " + currentUserId); // 重新加载消息以正确显示 runOnUiThread(() -> loadMessagesFromServer()); + } else { + Log.w(TAG, "无法从响应中获取用户ID: " + body); } } } @@ -308,8 +326,28 @@ public class ConversationActivity extends AppCompatActivity { boolean isMine = false; if (myUserId != null && !myUserId.isEmpty() && senderId > 0) { - isMine = myUserId.equals(String.valueOf(senderId)); + // 处理可能的浮点数格式(如 "1.0" vs "1") + try { + int myUid = (int) Double.parseDouble(myUserId); + isMine = (myUid == senderId); + } catch (NumberFormatException e) { + isMine = myUserId.equals(String.valueOf(senderId)); + } } + + // 如果 senderId 为 0,尝试从 senderId 字段获取 + if (senderId == 0) { + senderId = item.optInt("senderId", 0); + if (myUserId != null && !myUserId.isEmpty() && senderId > 0) { + try { + int myUid = (int) Double.parseDouble(myUserId); + isMine = (myUid == senderId); + } catch (NumberFormatException e) { + isMine = myUserId.equals(String.valueOf(senderId)); + } + } + } + Log.d(TAG, "消息判断: myUserId=" + myUserId + ", senderId=" + senderId + ", isMine=" + isMine); String displayName = isMine ? "我" : username; @@ -327,6 +365,7 @@ public class ConversationActivity extends AppCompatActivity { } ChatMessage chatMessage = new ChatMessage(messageId, displayName, message, timestamp, isSystem, msgStatus); + chatMessage.setOutgoing(isMine); // 关键:设置消息方向,确保自己发送的消息显示在右侧 chatMessage.setAvatarUrl(avatarUrl); return chatMessage; } catch (Exception e) { diff --git a/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java b/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java index ec9fab4d..ffb54ce3 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ConversationMessagesAdapter.java @@ -70,7 +70,8 @@ public class ConversationMessagesAdapter extends ListAdapter 0 ? - String.valueOf(AuthStore.getUserId(this)) : + String userIdStr = AuthStore.getUserId(this); + String clientId = (userIdStr != null && !userIdStr.isEmpty()) ? + userIdStr : "guest_" + System.currentTimeMillis(); String wsUrl = WS_ONLINE_BASE_URL + roomId + "?clientId=" + clientId; @@ -1149,7 +1156,7 @@ public class RoomDetailActivity extends AppCompatActivity { } android.util.Log.d("RoomDetail", "成功加载 " + availableGifts.size() + " 个礼物"); } else { - android.util.Log.w("RoomDetail", "加载礼物列表失败: " + apiResponse.getMsg()); + android.util.Log.w("RoomDetail", "加载礼物列表失败: " + apiResponse.getMessage()); setDefaultGifts(); } } else { @@ -1485,7 +1492,7 @@ public class RoomDetailActivity extends AppCompatActivity { ApiService apiService = ApiClient.getService(this); OrderPayRequest payRequest = new OrderPayRequest(orderId, payType, payChannel); - Call> call = apiService.processPayment(payRequest); + Call> call = apiService.payment(payRequest); call.enqueue(new Callback>() { @Override @@ -1597,10 +1604,13 @@ public class RoomDetailActivity extends AppCompatActivity { ApiService apiService = ApiClient.getService(getApplicationContext()); - SendGiftRequest request = new SendGiftRequest(); - request.setRoomId(Integer.parseInt(roomId)); - request.setGiftId(Integer.parseInt(selectedGift.getId())); - request.setCount(count); + // 获取主播ID(使用房间ID作为主播ID,或者从房间信息中获取) + Integer streamerId = Integer.parseInt(roomId); + SendGiftRequest request = new SendGiftRequest( + Integer.parseInt(selectedGift.getId()), + streamerId, + count + ); Call> call = apiService.sendRoomGift(roomId, request); @@ -1635,7 +1645,7 @@ public class RoomDetailActivity extends AppCompatActivity { giftCountText.setText("1"); } else { Toast.makeText(RoomDetailActivity.this, - "赠送失败: " + apiResponse.getMsg(), + "赠送失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); } } else { @@ -1665,7 +1675,7 @@ public class RoomDetailActivity extends AppCompatActivity { ApiService apiService = ApiClient.getService(getApplicationContext()); java.util.Map body = new java.util.HashMap<>(); - body.put("streamerId", room.getStreamerId()); + body.put("streamerId", roomId); // 使用房间ID作为主播ID body.put("action", "follow"); Call>> call = apiService.followStreamer(body); @@ -1682,7 +1692,7 @@ public class RoomDetailActivity extends AppCompatActivity { binding.followButton.setEnabled(false); } else { Toast.makeText(RoomDetailActivity.this, - "关注失败: " + apiResponse.getMsg(), + "关注失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); } } else { @@ -1731,7 +1741,7 @@ public class RoomDetailActivity extends AppCompatActivity { fetchRoom(); } else { Toast.makeText(RoomDetailActivity.this, - "开始直播失败: " + apiResponse.getMsg(), + "开始直播失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); } } else { @@ -1785,7 +1795,7 @@ public class RoomDetailActivity extends AppCompatActivity { fetchRoom(); } else { Toast.makeText(RoomDetailActivity.this, - "结束直播失败: " + apiResponse.getMsg(), + "结束直播失败: " + apiResponse.getMessage(), Toast.LENGTH_SHORT).show(); } } else { diff --git a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java index 8a5d0242..8de13724 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java +++ b/android-app/app/src/main/java/com/example/livestreaming/net/ApiService.java @@ -373,10 +373,6 @@ public interface ApiService { Call> queryAliPayResult(@Query("orderNo") String orderNo); @POST("api/front/pay/payment") -<<<<<<< HEAD - Call> processPayment(@Body OrderPayRequest body); - -======= Call> payment(@Body OrderPayRequest body); // ==================== 分类管理 ==================== @@ -405,5 +401,4 @@ public interface ApiService { Call>> getChildCategories( @Path("parentId") Integer parentId, @Query("recursive") Boolean recursive); ->>>>>>> d8f9cc8959c6d8d0d49e022a1f05833053abb8be } From e83568f5fb953e86abfe08e8ecdf585c66a9758a Mon Sep 17 00:00:00 2001 From: xiao12feng8 <16507319+xiao12feng8@user.noreply.gitee.com> Date: Tue, 30 Dec 2025 11:11:07 +0800 Subject: [PATCH 3/3] =?UTF-8?q?bug=EF=BC=9A=E4=BF=AE=E5=A4=8D=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E4=B8=8D=E8=83=BD=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Zhibo/admin/src/api/session.js | 20 + Zhibo/admin/src/views/session/list/index.vue | 676 +++++++++++++++--- .../admin/controller/SessionController.java | 300 +++++++- .../common/model/chat/PrivateMessage.java | 8 + .../zbkj/front/controller/CallController.java | 1 + .../controller/ConversationController.java | 11 + .../service/impl/ConversationServiceImpl.java | 4 +- Zhibo/zhibo-h/sql/add_recall_fields.sql | 8 + .../livestreaming/ConversationActivity.java | 79 +- .../main/res/layout/activity_conversation.xml | 7 +- 10 files changed, 951 insertions(+), 163 deletions(-) create mode 100644 Zhibo/zhibo-h/sql/add_recall_fields.sql diff --git a/Zhibo/admin/src/api/session.js b/Zhibo/admin/src/api/session.js index 20494afd..7a7f34ff 100644 --- a/Zhibo/admin/src/api/session.js +++ b/Zhibo/admin/src/api/session.js @@ -10,7 +10,27 @@ export function sessionDetailApi(id) { return request({ url: '/admin/session/detail/' + id, method: 'get' }) } +// 会话消息列表 +export function sessionMessagesApi(conversationId, params) { + return request({ url: '/admin/session/' + conversationId + '/messages', method: 'get', params }) +} + // 删除会话 export function sessionDeleteApi(id) { return request({ url: '/admin/session/delete/' + id, method: 'post' }) } + +// 删除消息 +export function sessionMessageDeleteApi(messageId) { + return request({ url: '/admin/session/message/delete/' + messageId, method: 'post' }) +} + +// 撤回消息 +export function sessionMessageRecallApi(messageId) { + return request({ url: '/admin/session/message/recall/' + messageId, method: 'post' }) +} + +// 会话统计 +export function sessionStatisticsApi() { + return request({ url: '/admin/session/statistics', method: 'get' }) +} diff --git a/Zhibo/admin/src/views/session/list/index.vue b/Zhibo/admin/src/views/session/list/index.vue index cf03b96a..3e25b12e 100644 --- a/Zhibo/admin/src/views/session/list/index.vue +++ b/Zhibo/admin/src/views/session/list/index.vue @@ -1,164 +1,602 @@ - diff --git a/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/SessionController.java b/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/SessionController.java index 58701259..30a6eb7b 100644 --- a/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/SessionController.java +++ b/Zhibo/zhibo-h/crmeb-admin/src/main/java/com/zbkj/admin/controller/SessionController.java @@ -10,80 +10,302 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * 会话管理控制器(社交互动模块) + * 与私聊管理使用相同的数据表:eb_conversation 和 eb_private_message + */ @Slf4j @RestController @RequestMapping("api/admin/session") -@Api(tags = "会话管理") +@Api(tags = "社交互动 - 会话管理") @Validated public class SessionController { @Autowired private JdbcTemplate jdbcTemplate; + /** + * 获取会话列表 + */ @ApiOperation(value = "会话列表") - @RequestMapping(value = "/list", method = RequestMethod.GET) + @GetMapping("/list") public CommonResult>> getList( @RequestParam(value = "senderNickname", required = false) String senderNickname, @RequestParam(value = "senderPhone", required = false) String senderPhone, + @RequestParam(value = "keyword", required = false) String keyword, @RequestParam(value = "startTime", required = false) String startTime, @RequestParam(value = "endTime", required = false) String endTime, @RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "limit", defaultValue = "10") Integer limit) { - - StringBuilder sql = new StringBuilder("SELECT * FROM eb_session WHERE 1=1"); - StringBuilder countSql = new StringBuilder("SELECT COUNT(*) FROM eb_session WHERE 1=1"); - - if (senderNickname != null && !senderNickname.isEmpty()) { - String safeName = senderNickname.replace("'", "''"); - sql.append(" AND sender_nickname LIKE '%").append(safeName).append("%'"); - countSql.append(" AND sender_nickname LIKE '%").append(safeName).append("%'"); + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT c.id, c.user1_id, c.user2_id, c.last_message, c.last_message_time, c.create_time, "); + sql.append("u1.nickname as sender_nickname, u1.avatar as sender_avatar, u1.phone as sender_phone, "); + sql.append("u2.nickname as receiver_nickname, u2.avatar as receiver_avatar, u2.phone as receiver_phone, "); + sql.append("(SELECT COUNT(*) FROM eb_private_message WHERE conversation_id = c.id) as message_count "); + sql.append("FROM eb_conversation c "); + sql.append("LEFT JOIN eb_user u1 ON c.user1_id = u1.uid "); + sql.append("LEFT JOIN eb_user u2 ON c.user2_id = u2.uid "); + sql.append("WHERE 1=1 "); + + StringBuilder countSql = new StringBuilder(); + countSql.append("SELECT COUNT(*) FROM eb_conversation c "); + countSql.append("LEFT JOIN eb_user u1 ON c.user1_id = u1.uid "); + countSql.append("LEFT JOIN eb_user u2 ON c.user2_id = u2.uid "); + countSql.append("WHERE 1=1 "); + + // 发送方昵称搜索 + if (senderNickname != null && !senderNickname.trim().isEmpty()) { + String condition = " AND (u1.nickname LIKE '%" + senderNickname.replace("'", "''") + "%' OR u2.nickname LIKE '%" + senderNickname.replace("'", "''") + "%') "; + sql.append(condition); + countSql.append(condition); } - if (senderPhone != null && !senderPhone.isEmpty()) { - String safePhone = senderPhone.replace("'", "''"); - sql.append(" AND sender_phone LIKE '%").append(safePhone).append("%'"); - countSql.append(" AND sender_phone LIKE '%").append(safePhone).append("%'"); + + // 发送方电话搜索 + if (senderPhone != null && !senderPhone.trim().isEmpty()) { + String condition = " AND (u1.phone LIKE '%" + senderPhone.replace("'", "''") + "%' OR u2.phone LIKE '%" + senderPhone.replace("'", "''") + "%') "; + sql.append(condition); + countSql.append(condition); } - if (startTime != null && !startTime.isEmpty()) { - sql.append(" AND create_time >= '").append(startTime).append("'"); - countSql.append(" AND create_time >= '").append(startTime).append("'"); + + // 关键词搜索(兼容私聊管理的参数) + if (keyword != null && !keyword.trim().isEmpty()) { + String condition = " AND (u1.nickname LIKE '%" + keyword + "%' OR u2.nickname LIKE '%" + keyword + "%' " + + "OR c.user1_id = '" + keyword + "' OR c.user2_id = '" + keyword + "') "; + sql.append(condition); + countSql.append(condition); } - if (endTime != null && !endTime.isEmpty()) { - sql.append(" AND create_time <= '").append(endTime).append("'"); - countSql.append(" AND create_time <= '").append(endTime).append("'"); + + // 时间范围 + if (startTime != null && !startTime.trim().isEmpty()) { + String condition = " AND c.create_time >= '" + startTime + "' "; + sql.append(condition); + countSql.append(condition); } - - sql.append(" ORDER BY id DESC"); - Long total = jdbcTemplate.queryForObject(countSql.toString(), Long.class); - + if (endTime != null && !endTime.trim().isEmpty()) { + String condition = " AND c.create_time <= '" + endTime + "' "; + sql.append(condition); + countSql.append(condition); + } + + // 排序 + sql.append("ORDER BY c.last_message_time DESC "); + + // 统计总数 + Long total = 0L; + try { + total = jdbcTemplate.queryForObject(countSql.toString(), Long.class); + } catch (Exception e) { + log.error("查询会话总数失败: {}", e.getMessage()); + } + + // 分页 int offset = (page - 1) * limit; - sql.append(" LIMIT ").append(offset).append(", ").append(limit); - List> list = jdbcTemplate.queryForList(sql.toString()); - + sql.append("LIMIT ").append(offset).append(", ").append(limit); + + List> list; + try { + list = jdbcTemplate.queryForList(sql.toString()); + } catch (Exception e) { + log.error("查询会话列表失败: {}", e.getMessage()); + list = java.util.Collections.emptyList(); + } + CommonPage> result = new CommonPage<>(); result.setList(list); - result.setTotal(total); + result.setTotal(total != null ? total : 0L); result.setPage(page); result.setLimit(limit); - result.setTotalPage((int) Math.ceil((double) total / limit)); - + result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / limit)); + return CommonResult.success(result); } + /** + * 获取会话详情 + */ @ApiOperation(value = "会话详情") - @RequestMapping(value = "/detail/{id}", method = RequestMethod.GET) - public CommonResult> getDetail(@PathVariable Integer id) { - String sql = "SELECT * FROM eb_session WHERE id = ?"; - Map detail = jdbcTemplate.queryForMap(sql, id); - return CommonResult.success(detail); + @GetMapping("/detail/{id}") + public CommonResult> getDetail(@PathVariable Long id) { + try { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT c.*, "); + sql.append("u1.nickname as sender_nickname, u1.avatar as sender_avatar, u1.phone as sender_phone, "); + sql.append("u2.nickname as receiver_nickname, u2.avatar as receiver_avatar, u2.phone as receiver_phone "); + sql.append("FROM eb_conversation c "); + sql.append("LEFT JOIN eb_user u1 ON c.user1_id = u1.uid "); + sql.append("LEFT JOIN eb_user u2 ON c.user2_id = u2.uid "); + sql.append("WHERE c.id = ?"); + + Map detail = jdbcTemplate.queryForMap(sql.toString(), id); + return CommonResult.success(detail); + } catch (Exception e) { + log.error("获取会话详情失败: {}", e.getMessage()); + return CommonResult.failed("获取会话详情失败"); + } } + /** + * 获取会话消息列表 + */ + @ApiOperation(value = "会话消息列表") + @GetMapping("/{conversationId}/messages") + public CommonResult>> getMessages( + @PathVariable Long conversationId, + @RequestParam(value = "page", defaultValue = "1") Integer page, + @RequestParam(value = "pageSize", defaultValue = "20") Integer pageSize) { + + StringBuilder sql = new StringBuilder(); + sql.append("SELECT m.*, u.nickname as sender_name, u.avatar as sender_avatar "); + sql.append("FROM eb_private_message m "); + sql.append("LEFT JOIN eb_user u ON m.sender_id = u.uid "); + sql.append("WHERE m.conversation_id = ? "); + sql.append("ORDER BY m.create_time DESC "); + + Long total = 0L; + try { + String countSql = "SELECT COUNT(*) FROM eb_private_message WHERE conversation_id = ?"; + total = jdbcTemplate.queryForObject(countSql, Long.class, conversationId); + } catch (Exception e) { + log.error("查询消息总数失败: {}", e.getMessage()); + } + + int offset = (page - 1) * pageSize; + sql.append("LIMIT ").append(offset).append(", ").append(pageSize); + + List> list; + try { + list = jdbcTemplate.queryForList(sql.toString(), conversationId); + // 转换字段名 + list.forEach(item -> { + item.put("senderId", item.get("sender_id")); + item.put("receiverId", item.get("receiver_id")); + item.put("senderName", item.get("sender_name")); + item.put("senderAvatar", item.get("sender_avatar")); + item.put("messageType", item.get("message_type")); + item.put("createTime", item.get("create_time")); + item.put("isRecalled", item.get("is_recalled")); + item.put("originalContent", item.get("original_content")); + item.put("recallTime", item.get("recall_time")); + }); + } catch (Exception e) { + log.error("查询消息列表失败: {}", e.getMessage()); + list = java.util.Collections.emptyList(); + } + + CommonPage> result = new CommonPage<>(); + result.setList(list); + result.setTotal(total != null ? total : 0L); + result.setPage(page); + result.setLimit(pageSize); + result.setTotalPage((int) Math.ceil((double) (total != null ? total : 0) / pageSize)); + + return CommonResult.success(result); + } + + /** + * 删除会话(包括所有消息) + */ @ApiOperation(value = "删除会话") - @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST) - public CommonResult delete(@PathVariable Integer id) { - jdbcTemplate.update("DELETE FROM eb_session WHERE id = ?", id); - return CommonResult.success("删除成功"); + @PostMapping("/delete/{id}") + public CommonResult delete(@PathVariable Long id) { + try { + // 先删除消息 + jdbcTemplate.update("DELETE FROM eb_private_message WHERE conversation_id = ?", id); + // 删除会话 + jdbcTemplate.update("DELETE FROM eb_conversation WHERE id = ?", id); + return CommonResult.success("删除成功"); + } catch (Exception e) { + log.error("删除会话失败: {}", e.getMessage()); + return CommonResult.failed("删除失败: " + e.getMessage()); + } + } + + /** + * 删除单条消息 + */ + @ApiOperation(value = "删除消息") + @PostMapping("/message/delete/{messageId}") + public CommonResult deleteMessage(@PathVariable Long messageId) { + try { + jdbcTemplate.update("DELETE FROM eb_private_message WHERE id = ?", messageId); + return CommonResult.success("删除成功"); + } catch (Exception e) { + log.error("删除消息失败: {}", e.getMessage()); + return CommonResult.failed("删除失败: " + e.getMessage()); + } + } + + /** + * 撤回消息(管理员可以撤回任意消息) + */ + @ApiOperation(value = "撤回消息") + @PostMapping("/message/recall/{messageId}") + public CommonResult recallMessage(@PathVariable Long messageId) { + try { + // 先获取原始消息内容 + Map message = jdbcTemplate.queryForMap( + "SELECT content FROM eb_private_message WHERE id = ?", messageId); + String originalContent = (String) message.get("content"); + + // 保存原始内容并标记为撤回 + int updated = jdbcTemplate.update( + "UPDATE eb_private_message SET is_recalled = 1, original_content = ?, recall_time = NOW(), content = '[消息已被管理员撤回]' WHERE id = ?", + originalContent, messageId + ); + if (updated > 0) { + return CommonResult.success("撤回成功"); + } else { + return CommonResult.failed("消息不存在"); + } + } catch (Exception e) { + log.error("撤回消息失败: {}", e.getMessage()); + return CommonResult.failed("撤回失败: " + e.getMessage()); + } + } + + /** + * 获取会话统计数据 + */ + @ApiOperation(value = "会话统计") + @GetMapping("/statistics") + public CommonResult> getStatistics() { + Map stats = new HashMap<>(); + + try { + // 总会话数 + Long totalConversations = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM eb_conversation", Long.class); + stats.put("totalConversations", totalConversations != null ? totalConversations : 0); + + // 总消息数 + Long totalMessages = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM eb_private_message", Long.class); + stats.put("totalMessages", totalMessages != null ? totalMessages : 0); + + // 今日消息数 + Long todayMessages = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM eb_private_message WHERE DATE(create_time) = CURDATE()", Long.class); + stats.put("todayMessages", todayMessages != null ? todayMessages : 0); + + // 活跃会话数(最近7天有消息的会话) + Long activeConversations = jdbcTemplate.queryForObject( + "SELECT COUNT(DISTINCT conversation_id) FROM eb_private_message WHERE create_time >= DATE_SUB(NOW(), INTERVAL 7 DAY)", + Long.class); + stats.put("activeConversations", activeConversations != null ? activeConversations : 0); + + } catch (Exception e) { + log.error("获取统计数据失败: {}", e.getMessage()); + stats.put("totalConversations", 0); + stats.put("totalMessages", 0); + stats.put("todayMessages", 0); + stats.put("activeConversations", 0); + } + + return CommonResult.success(stats); } } diff --git a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/chat/PrivateMessage.java b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/chat/PrivateMessage.java index 1d65fab9..a1ca6b17 100644 --- a/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/chat/PrivateMessage.java +++ b/Zhibo/zhibo-h/crmeb-common/src/main/java/com/zbkj/common/model/chat/PrivateMessage.java @@ -82,4 +82,12 @@ public class PrivateMessage implements Serializable { @ApiModelProperty(value = "是否已撤回") @Column(name = "is_recalled", columnDefinition = "TINYINT(1) DEFAULT 0") private Boolean isRecalled; + + @ApiModelProperty(value = "原始消息内容(撤回前)") + @Column(name = "original_content", columnDefinition = "TEXT") + private String originalContent; + + @ApiModelProperty(value = "撤回时间") + @Column(name = "recall_time") + private Date recallTime; } diff --git a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CallController.java b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CallController.java index 21da4512..41134a81 100644 --- a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CallController.java +++ b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/CallController.java @@ -229,3 +229,4 @@ public class CallController { return response; } } + \ No newline at end of file diff --git a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/ConversationController.java b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/ConversationController.java index ec6ce6b0..b2d42df6 100644 --- a/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/ConversationController.java +++ b/Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/ConversationController.java @@ -145,4 +145,15 @@ public class ConversationController { Integer userId = userService.getUserIdException(); return CommonResult.success(conversationService.deleteMessage(id, userId)); } + + /** + * 撤回消息 + */ + @ApiOperation(value = "撤回消息") + @ApiImplicitParam(name = "id", value = "消息ID", required = true) + @PostMapping("/messages/{id}/recall") + public CommonResult recallMessage(@PathVariable Long id) { + Integer userId = userService.getUserIdException(); + return CommonResult.success(conversationService.recallMessage(id, userId)); + } } diff --git a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/ConversationServiceImpl.java b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/ConversationServiceImpl.java index f4be294c..cab375ac 100644 --- a/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/ConversationServiceImpl.java +++ b/Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/service/impl/ConversationServiceImpl.java @@ -412,10 +412,12 @@ public class ConversationServiceImpl extends ServiceImpl 2) { throw new CrmebException("消息发送超过2分钟,无法撤回"); } - // 标记消息为已撤回 + // 保存原始内容,然后标记消息为已撤回 LambdaUpdateWrapper uw = new LambdaUpdateWrapper<>(); uw.eq(PrivateMessage::getId, messageId) .set(PrivateMessage::getIsRecalled, true) + .set(PrivateMessage::getOriginalContent, message.getContent()) // 保存原始内容 + .set(PrivateMessage::getRecallTime, new Date()) // 记录撤回时间 .set(PrivateMessage::getContent, "[消息已撤回]"); return privateMessageDao.update(null, uw) > 0; } diff --git a/Zhibo/zhibo-h/sql/add_recall_fields.sql b/Zhibo/zhibo-h/sql/add_recall_fields.sql new file mode 100644 index 00000000..4ccfef73 --- /dev/null +++ b/Zhibo/zhibo-h/sql/add_recall_fields.sql @@ -0,0 +1,8 @@ +-- 为私信消息表添加撤回相关字段 +-- 执行此脚本前请备份数据库 + +-- 添加原始内容字段(保存撤回前的消息内容) +ALTER TABLE eb_private_message ADD COLUMN original_content TEXT COMMENT '原始消息内容(撤回前)'; + +-- 添加撤回时间字段 +ALTER TABLE eb_private_message ADD COLUMN recall_time DATETIME COMMENT '撤回时间'; diff --git a/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java b/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java index d2712b55..ffc8b50c 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java +++ b/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java @@ -109,6 +109,9 @@ public class ConversationActivity extends AppCompatActivity { setupMessages(); setupInput(); + // 确保输入框显示正确的提示文本 + binding.messageInput.setHint("输入消息..."); + // 标记会话为已读 if (initialUnreadCount > 0 && conversationId != null) { markConversationAsRead(); @@ -414,9 +417,16 @@ public class ConversationActivity extends AppCompatActivity { PopupMenu popupMenu = new PopupMenu(this, anchorView); popupMenu.getMenu().add(0, 0, 0, "复制"); popupMenu.getMenu().add(0, 2, 0, "表情回应"); - // 只有自己发送的消息才能删除 - if ("我".equals(message.getUsername())) { + // 只有自己发送的消息才能删除和撤回 + if ("我".equals(message.getUsername()) || message.isOutgoing()) { popupMenu.getMenu().add(0, 1, 0, "删除"); + // 检查是否在2分钟内,可以撤回 + long messageTime = message.getTimestamp(); + long now = System.currentTimeMillis(); + long diffMinutes = (now - messageTime) / (1000 * 60); + if (diffMinutes <= 2) { + popupMenu.getMenu().add(0, 3, 0, "撤回"); + } } popupMenu.setOnMenuItemClickListener(item -> { @@ -429,6 +439,9 @@ public class ConversationActivity extends AppCompatActivity { } else if (item.getItemId() == 2) { showEmojiPicker(message); return true; + } else if (item.getItemId() == 3) { + recallMessage(message, position); + return true; } return false; }); @@ -497,6 +510,68 @@ public class ConversationActivity extends AppCompatActivity { }); } + /** + * 撤回消息 + */ + private void recallMessage(ChatMessage message, int position) { + if (position < 0 || position >= messages.size()) return; + + new AlertDialog.Builder(this) + .setTitle("撤回消息") + .setMessage("确定要撤回这条消息吗?") + .setPositiveButton("撤回", (dialog, which) -> recallMessageFromServer(message, position)) + .setNegativeButton("取消", null) + .show(); + } + + /** + * 调用服务器撤回消息接口 + */ + private void recallMessageFromServer(ChatMessage message, int position) { + String token = AuthStore.getToken(this); + if (token == null) return; + + String messageId = message.getMessageId(); + String url = ApiConfig.getBaseUrl() + "/api/front/conversations/messages/" + messageId + "/recall"; + Log.d(TAG, "撤回消息: " + url); + + Request request = new Request.Builder() + .url(url) + .addHeader("Authori-zation", token) + .post(RequestBody.create("", MediaType.parse("application/json"))) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + Log.e(TAG, "撤回消息失败", e); + runOnUiThread(() -> Snackbar.make(binding.getRoot(), "撤回失败", Snackbar.LENGTH_SHORT).show()); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + String body = response.body() != null ? response.body().string() : ""; + Log.d(TAG, "撤回消息响应: " + body); + runOnUiThread(() -> { + try { + JSONObject json = new JSONObject(body); + if (json.optInt("code", -1) == 200) { + // 更新本地消息显示为已撤回 + message.setMessage("[消息已撤回]"); + adapter.notifyItemChanged(position); + Snackbar.make(binding.getRoot(), "消息已撤回", Snackbar.LENGTH_SHORT).show(); + } else { + String errorMsg = json.optString("message", "撤回失败"); + Snackbar.make(binding.getRoot(), errorMsg, Snackbar.LENGTH_SHORT).show(); + } + } catch (Exception e) { + Log.e(TAG, "解析撤回响应失败", e); + } + }); + } + }); + } + private void setupInput() { binding.sendButton.setOnClickListener(new DebounceClickListener(300) { diff --git a/android-app/app/src/main/res/layout/activity_conversation.xml b/android-app/app/src/main/res/layout/activity_conversation.xml index b0e5cf61..7b5fb33d 100644 --- a/android-app/app/src/main/res/layout/activity_conversation.xml +++ b/android-app/app/src/main/res/layout/activity_conversation.xml @@ -123,7 +123,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> - + android:textColorHint="#999999" + android:textSize="14sp" + android:importantForAutofill="no" + android:autofillHints="" />