diff --git a/Log/指令/2026年1月8日任务.md b/Log/指令/2026年1月8日任务.md new file mode 100644 index 00000000..1d473d79 --- /dev/null +++ b/Log/指令/2026年1月8日任务.md @@ -0,0 +1,46 @@ +我们正在开发直播App的三个新功能,请继续之前的工作: + +【功能1】阅后即焚图片消息 +- 好友私聊中发送阅后即焚图片 +- 查看几秒后自动销毁 +- 需要在Android端和后端实现 + +【功能2】作品上热门 +- 用户付费让作品上热门 +- 热门作品在首页优先展示 +- 需要付费档位和热门队列 + +【功能3】发布动态 + 附近动态 +- 用户发布动态(文字+图片+位置) +- 查看附近的人的动态 +- 基于地理位置发现 + +当前项目结构: +- Android端:android-app/app/src/main/java/com/example/livestreaming/ +- 后端Java:Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/ +- 后端Controller:Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/ + +请根据我指定的功能继续开发。 +我们正在开发直播App的三个新功能,请继续之前的工作: + +【功能1】阅后即焚图片消息 +- 好友私聊中发送阅后即焚图片 +- 查看几秒后自动销毁 +- 需要在Android端和后端实现 + +【功能2】作品上热门 +- 用户付费让作品上热门 +- 热门作品在首页优先展示 +- 需要付费档位和热门队列 + +【功能3】发布动态 + 附近动态 +- 用户发布动态(文字+图片+位置) +- 查看附近的人的动态 +- 基于地理位置发现 + +当前项目结构: +- Android端:android-app/app/src/main/java/com/example/livestreaming/ +- 后端Java:Zhibo/zhibo-h/crmeb-service/src/main/java/com/zbkj/service/ +- 后端Controller:Zhibo/zhibo-h/crmeb-front/src/main/java/com/zbkj/front/controller/ + +请根据我指定的功能继续开发。 diff --git a/Log/1-AI指南.md b/Log/环境/1-AI指南.md similarity index 100% rename from Log/1-AI指南.md rename to Log/环境/1-AI指南.md diff --git a/Log/2-项目功能与部署指南.md b/Log/环境/2-项目功能与部署指南.md similarity index 100% rename from Log/2-项目功能与部署指南.md rename to Log/环境/2-项目功能与部署指南.md diff --git a/Log/3-JDK17环境.md b/Log/环境/3-JDK17环境.md similarity index 100% rename from Log/3-JDK17环境.md rename to Log/环境/3-JDK17环境.md diff --git a/Log/4-Live-streaming启动环境.md b/Log/环境/4-Live-streaming启动环境.md similarity index 100% rename from Log/4-Live-streaming启动环境.md rename to Log/环境/4-Live-streaming启动环境.md diff --git a/Log/5-jar包启动环境.md b/Log/环境/5-jar包启动环境.md similarity index 100% rename from Log/5-jar包启动环境.md rename to Log/环境/5-jar包启动环境.md diff --git a/Log/6-nginx配置.md b/Log/环境/6-nginx配置.md similarity index 100% rename from Log/6-nginx配置.md rename to Log/环境/6-nginx配置.md diff --git a/Log/7-Docker环境配置.md b/Log/环境/7-Docker环境配置.md similarity index 100% rename from Log/7-Docker环境配置.md rename to Log/环境/7-Docker环境配置.md 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 5c7bbf98..57ebd025 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 @@ -194,7 +194,6 @@ public class PublishWorkActivity extends AppCompatActivity { getSupportActionBar().setTitle("编辑作品"); } } - } private void setupToolbar() { setSupportActionBar(binding.toolbar); 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 116dbd78..0d26571d 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 @@ -804,171 +804,6 @@ public class WorkDetailActivity extends AppCompatActivity { } } - private void setupActionButton() { - // ============================================ - // TODO: 判断是否是当前用户的作品 - // ============================================ - // 判断方式(推荐方式3): - // 1. 从WorkItem中获取userId字段,与当前登录用户ID比较(需要从AuthStore获取当前用户ID) - // 2. 或调用接口: GET /api/works/{workId}/isOwner - // 3. 或从作品详情接口返回的WorkItem中包含isOwner字段(推荐,已在获取详情时返回) - // - // 如果使用方式3(推荐): - // - 在获取作品详情时,后端已经返回isOwner字段 - // - 直接使用 workItem.getIsOwner() 或从详情接口返回的isOwner字段 - // - 如果isOwner为true,显示编辑/删除按钮 - // - 如果isOwner为false或用户未登录,隐藏编辑/删除按钮 - // - // 如果使用方式1: - // - 需要从AuthStore获取当前登录用户的userId - // - 比较 workItem.getUserId() 与当前用户ID - // - 如果相同,显示编辑/删除按钮;否则隐藏 - // - // 如果使用方式2: - // 接口路径: GET /api/works/{workId}/isOwner - // 请求方法: GET - // 请求头: - // - Authorization: Bearer {token} (必填,需要登录) - // 路径参数: - // - workId: String (必填) - 作品ID - // 返回数据格式: ApiResponse - // 后端返回: { "code": 200, "data": { "isOwner": true/false } } - // - // 目前简化处理,所有作品都显示操作按钮 - // TODO: 根据isOwner字段控制按钮显示/隐藏 - - // 新布局中actionButton是ImageView,不是FloatingActionButton(添加防抖) - binding.actionButton.setOnClickListener(new DebounceClickListener() { - @Override - public void onDebouncedClick(View v) { - showActionMenu(); - } - }); - } - - private void showActionMenu() { - // TODO: 判断是否是当前用户的作品 - boolean isOwner = true; // 简化处理 - - if (!isOwner) { - return; - } - - String[] options = {"编辑", "删除"}; - new AlertDialog.Builder(this) - .setItems(options, (dialog, which) -> { - if (which == 0) { - // 编辑 - editWork(); - } else if (which == 1) { - // 删除 - deleteWork(); - } - }) - .show(); - } - - private void editWork() { - // ============================================ - // TODO: 实现编辑作品功能 - // ============================================ - // 方案1: 跳转到编辑页面(PublishWorkActivity,传入workItem数据) - // 方案2: 在当前页面弹出编辑对话框 - // - // 如果使用方案1(推荐): - // 1. 创建EditWorkActivity或复用PublishWorkActivity - // 2. 传入workItem数据(通过Intent传递) - // 3. 在编辑页面预填充作品信息(标题、描述、图片/视频) - // 4. 用户修改后,调用更新接口保存 - // - // 更新作品接口: - // 接口路径: PUT /api/works/{workId} - // 请求方法: PUT - // 请求头: - // - Authorization: Bearer {token} (必填,需要登录) - // 路径参数: - // - workId: String (必填) - 作品ID - // 请求体 (multipart/form-data 或 JSON): - // - title: String (可选) - 作品标题 - // - description: String (可选) - 作品描述 - // - cover: File (可选) - 新的封面图片(如果要更换封面) - // - video: File (可选) - 新的视频文件(如果要更换视频,仅视频作品) - // - images: File[] (可选) - 新的图片文件数组(如果要更换图片,仅图片作品) - // - deletedImageIds: String[] (可选) - 要删除的图片ID列表(图片作品) - // 返回数据格式: ApiResponse - // - // 前端需要传入的参数: - // - workId: String (从workItem.getId()获取) - // - title: String (用户修改后的标题) - // - description: String (用户修改后的描述,可选) - // - cover: File (如果要更换封面) - // - video/images: File/File[] (如果要更换媒体文件) - // - token: String (必填,从AuthStore获取) - // - // 实现步骤: - // 1. 跳转到编辑页面,传入workItem - // 2. 在编辑页面加载作品数据并预填充表单 - // 3. 用户修改内容后点击保存 - // 4. 验证输入(标题不能为空等) - // 5. 如果有新上传的文件,先上传文件获取URL - // 6. 调用 PUT /api/works/{workId} 更新作品信息 - // 7. 更新成功后,刷新当前页面或返回并刷新列表 - // 8. 处理错误情况(未登录、无权限、文件上传失败等) - // - // 注意: - // - 只有作品作者才能编辑 - // - 如果更换媒体文件,需要先上传新文件 - // - 图片作品可以删除部分图片,需要传递deletedImageIds - - Toast.makeText(this, "编辑功能待实现", Toast.LENGTH_SHORT).show(); - } - - private void deleteWork() { - new AlertDialog.Builder(this) - .setTitle("删除作品") - .setMessage("确定要删除这个作品吗?") - .setPositiveButton("删除", (dialog, which) -> { - // 检查登录状态 - if (!AuthHelper.requireLogin(this, "删除作品需要登录")) { - return; - } - - try { - long worksId = Long.parseLong(workItem.getId()); - ApiService apiService = ApiClient.getService(this); - Call> call = apiService.deleteWork(worksId); - - call.enqueue(new retrofit2.Callback>() { - @Override - public void onResponse(Call> call, retrofit2.Response> response) { - if (response.isSuccessful() && response.body() != null) { - ApiResponse apiResponse = response.body(); - if (apiResponse.getCode() == 200 && Boolean.TRUE.equals(apiResponse.getData())) { - Toast.makeText(WorkDetailActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); - finish(); - } else { - Toast.makeText(WorkDetailActivity.this, - apiResponse.getMessage() != null ? apiResponse.getMessage() : "删除失败", - Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(WorkDetailActivity.this, "删除失败", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Toast.makeText(WorkDetailActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); - } catch (NumberFormatException e) { - Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show(); - } - }) - .setNegativeButton("取消", null) - .show(); - } - // 图片ViewPager适配器 private static class ImagePagerAdapter extends RecyclerView.Adapter { private final List imageUris; @@ -1100,64 +935,6 @@ public class WorkDetailActivity extends AppCompatActivity { } } - /** - * 将WorksResponse转换为WorkItem - */ - private WorkItem convertToWorkItem(WorksResponse response) { - WorkItem item = new WorkItem(); - item.setId(String.valueOf(response.getId())); - item.setTitle(response.getTitle()); - item.setDescription(response.getDescription()); - item.setLikeCount(response.getLikeCount()); - item.setCollectCount(response.getCollectCount()); - item.setViewCount(response.getViewCount()); - // 防止 publishTime 为 null 导致 NullPointerException - item.setPublishTime(response.getPublishTime() != null ? response.getPublishTime() : 0L); - - // 设置作品类型 - if ("VIDEO".equals(response.getType())) { - item.setType(WorkItem.WorkType.VIDEO); - item.setVideoUrl(response.getVideoUrl()); - if (!TextUtils.isEmpty(response.getVideoUrl())) { - item.setVideoUri(Uri.parse(response.getVideoUrl())); - } - } else { - item.setType(WorkItem.WorkType.IMAGE); - item.setImageUrls(response.getImageUrls()); - if (response.getImageUrls() != null && !response.getImageUrls().isEmpty()) { - List imageUris = new ArrayList<>(); - for (String url : response.getImageUrls()) { - if (!TextUtils.isEmpty(url)) { - imageUris.add(Uri.parse(url)); - } - } - item.setImageUris(imageUris); - } - } - - // 设置封面 - item.setCoverUrl(response.getCoverUrl()); - if (!TextUtils.isEmpty(response.getCoverUrl())) { - item.setCoverUri(Uri.parse(response.getCoverUrl())); - } - - return item; - } -} - - } - }); - } catch (NumberFormatException e) { - android.util.Log.e("WorkDetail", "workId格式错误: " + workId, e); - Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show(); - finish(); - } catch (Exception e) { - android.util.Log.e("WorkDetail", "加载作品详情异常", e); - Toast.makeText(this, "加载失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); - finish(); - } - } - /** * 将WorksResponse转换为WorkItem */ @@ -1354,153 +1131,6 @@ public class WorkDetailActivity extends AppCompatActivity { .show(); } - private void setupActionButton() { - // 检查是否应该显示菜单 - boolean showMenu = getIntent().getBooleanExtra(EXTRA_SHOW_MENU, false); - if (!showMenu) { - binding.actionButton.setVisibility(View.GONE); - return; - } - - // 检查是否是当前用户的作品 - String currentUserIdStr = AuthStore.getUserId(this); - if (currentUserIdStr == null || workItem == null) { - binding.actionButton.setVisibility(View.GONE); - return; - } - - try { - int currentUserId = Integer.parseInt(currentUserIdStr); - int authorId = workItem.getAuthorId(); - - if (currentUserId != authorId) { - // 不是自己的作品,隐藏菜单 - binding.actionButton.setVisibility(View.GONE); - return; - } - } catch (NumberFormatException e) { - binding.actionButton.setVisibility(View.GONE); - return; - } - - // 是自己的作品且需要显示菜单,设置点击事件 - binding.actionButton.setVisibility(View.VISIBLE); - binding.actionButton.setOnClickListener(new DebounceClickListener() { - @Override - public void onDebouncedClick(View v) { - showActionMenu(); - } - }); - } - - private void showActionMenu() { - // 检查是否是当前用户的作品 - String currentUserIdStr = AuthStore.getUserId(this); - if (currentUserIdStr == null || workItem == null) { - return; - } - - try { - int currentUserId = Integer.parseInt(currentUserIdStr); - int authorId = workItem.getAuthorId(); - - if (currentUserId != authorId) { - Toast.makeText(this, "只能编辑自己的作品", Toast.LENGTH_SHORT).show(); - return; - } - } catch (NumberFormatException e) { - return; - } - - String[] options = {"编辑", "删除"}; - new AlertDialog.Builder(this) - .setItems(options, (dialog, which) -> { - if (which == 0) { - // 编辑 - editWork(); - } else if (which == 1) { - // 删除 - deleteWork(); - } - }) - .show(); - } - - private void editWork() { - // 检查登录状态 - if (!AuthHelper.requireLogin(this, "编辑作品需要登录")) { - return; - } - - if (workItem == null) { - Toast.makeText(this, "作品信息不完整", Toast.LENGTH_SHORT).show(); - return; - } - - // 跳转到编辑页面(复用PublishWorkActivity) - Intent intent = new Intent(this, PublishWorkActivity.class); - intent.putExtra("edit_mode", true); - intent.putExtra("work_id", workItem.getId()); - intent.putExtra("work_title", workItem.getTitle()); - intent.putExtra("work_description", workItem.getDescription()); - intent.putExtra("work_type", workItem.getType().name()); - intent.putExtra("work_cover_url", workItem.getCoverUrl()); - - if (workItem.getType() == WorkItem.WorkType.VIDEO) { - intent.putExtra("work_video_url", workItem.getVideoUrl()); - } else if (workItem.getImageUrls() != null) { - intent.putStringArrayListExtra("work_image_urls", new ArrayList<>(workItem.getImageUrls())); - } - - startActivityForResult(intent, 1001); - } - - private void deleteWork() { - new AlertDialog.Builder(this) - .setTitle("删除作品") - .setMessage("确定要删除这个作品吗?") - .setPositiveButton("删除", (dialog, which) -> { - // 检查登录状态 - if (!AuthHelper.requireLogin(this, "删除作品需要登录")) { - return; - } - - try { - long worksId = Long.parseLong(workItem.getId()); - ApiService apiService = ApiClient.getService(this); - Call> call = apiService.deleteWork(worksId); - - call.enqueue(new retrofit2.Callback>() { - @Override - public void onResponse(Call> call, retrofit2.Response> response) { - if (response.isSuccessful() && response.body() != null) { - ApiResponse apiResponse = response.body(); - if (apiResponse.getCode() == 200 && Boolean.TRUE.equals(apiResponse.getData())) { - Toast.makeText(WorkDetailActivity.this, "删除成功", Toast.LENGTH_SHORT).show(); - finish(); - } else { - Toast.makeText(WorkDetailActivity.this, - apiResponse.getMessage() != null ? apiResponse.getMessage() : "删除失败", - Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(WorkDetailActivity.this, "删除失败", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Toast.makeText(WorkDetailActivity.this, "网络错误: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); - } catch (NumberFormatException e) { - Toast.makeText(this, "作品ID格式错误", Toast.LENGTH_SHORT).show(); - } - }) - .setNegativeButton("取消", null) - .show(); - } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/android-app/app/src/main/java/com/example/livestreaming/WorkItem.java b/android-app/app/src/main/java/com/example/livestreaming/WorkItem.java index 344134fa..e4e9a354 100644 --- a/android-app/app/src/main/java/com/example/livestreaming/WorkItem.java +++ b/android-app/app/src/main/java/com/example/livestreaming/WorkItem.java @@ -23,6 +23,11 @@ public class WorkItem implements Parcelable { private long publishTime; private WorkType type; // 作品类型:图片或视频 + // 作者信息 + private int authorId; + private String authorName; + private String authorAvatar; + // 本地使用的URI(发布时使用) private transient Uri coverUri; private transient Uri videoUri; @@ -171,6 +176,30 @@ public class WorkItem implements Parcelable { this.imageUris = imageUris; } + public int getAuthorId() { + return authorId; + } + + public void setAuthorId(int authorId) { + this.authorId = authorId; + } + + public String getAuthorName() { + return authorName; + } + + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + public String getAuthorAvatar() { + return authorAvatar; + } + + public void setAuthorAvatar(String authorAvatar) { + this.authorAvatar = authorAvatar; + } + // Parcelable implementation protected WorkItem(Parcel in) { id = in.readString(); @@ -186,6 +215,9 @@ public class WorkItem implements Parcelable { int typeOrdinal = in.readInt(); type = typeOrdinal >= 0 && typeOrdinal < WorkType.values().length ? WorkType.values()[typeOrdinal] : WorkType.IMAGE; + authorId = in.readInt(); + authorName = in.readString(); + authorAvatar = in.readString(); } @Override @@ -201,6 +233,9 @@ public class WorkItem implements Parcelable { dest.writeInt(viewCount); dest.writeLong(publishTime); dest.writeInt(type != null ? type.ordinal() : -1); + dest.writeInt(authorId); + dest.writeString(authorName); + dest.writeString(authorAvatar); } @Override