Commit affc409022d066c4b53f8fcf33eb7690383bce04
1 parent
758dfcbb
✨ feat(friend): 新增 IM 好友管理功能,包括添加、删除和更新好友信息
实现了 IM 好友的增删改查功能,支持免打扰状态的更新。新增了相关的请求和响应对象,优化了好友列表的获取逻辑,并确保了好友关系的有效性校验。 - 新增 ImFriendRespVO 和 ImFriendUpdateReqVO 类 - 更新 ImFriendController,添加更新好友信息接口 - 实现 ImFriendService 中的好友管理逻辑
Showing
6 changed files
with
330 additions
and
23 deletions
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.friend; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | |
| 5 | 4 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO; |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; | |
| 6 | 6 | import cn.iocoder.yudao.module.im.service.friend.ImFriendService; |
| 7 | 7 | import io.swagger.v3.oas.annotations.Operation; |
| 8 | 8 | import io.swagger.v3.oas.annotations.Parameter; |
| 9 | 9 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 10 | 10 | import jakarta.annotation.Resource; |
| 11 | +import jakarta.validation.Valid; | |
| 11 | 12 | import jakarta.validation.constraints.NotNull; |
| 12 | 13 | import org.springframework.validation.annotation.Validated; |
| 13 | 14 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.*; |
| 15 | 16 | import java.util.List; |
| 16 | 17 | |
| 17 | 18 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 19 | +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; | |
| 18 | 20 | |
| 19 | 21 | @Tag(name = "管理后台 - IM 好友") |
| 20 | 22 | @RestController |
| ... | ... | @@ -28,16 +30,14 @@ public class ImFriendController { |
| 28 | 30 | @GetMapping("/list") |
| 29 | 31 | @Operation(summary = "获得当前登录用户的好友列表") |
| 30 | 32 | public CommonResult<List<ImFriendRespVO>> getMyFriendList() { |
| 31 | - Long userId = SecurityFrameworkUtils.getLoginUserId(); | |
| 32 | - return success(friendService.getMyFriendList(userId)); | |
| 33 | + return success(friendService.getMyFriendList(getLoginUserId())); | |
| 33 | 34 | } |
| 34 | 35 | |
| 35 | 36 | @GetMapping("/get") |
| 36 | 37 | @Operation(summary = "获得好友详情") |
| 37 | 38 | @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") |
| 38 | 39 | public CommonResult<ImFriendRespVO> getFriend(@RequestParam("friendUserId") Long friendUserId) { |
| 39 | - Long userId = SecurityFrameworkUtils.getLoginUserId(); | |
| 40 | - return success(friendService.getFriend(userId, friendUserId)); | |
| 40 | + return success(friendService.getFriend(getLoginUserId(), friendUserId)); | |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | 43 | @PostMapping("/add") |
| ... | ... | @@ -45,8 +45,7 @@ public class ImFriendController { |
| 45 | 45 | @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") |
| 46 | 46 | public CommonResult<Boolean> addFriend( |
| 47 | 47 | @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) { |
| 48 | - Long userId = SecurityFrameworkUtils.getLoginUserId(); | |
| 49 | - friendService.addFriend(userId, friendUserId); | |
| 48 | + friendService.addFriend(getLoginUserId(), friendUserId); | |
| 50 | 49 | return success(true); |
| 51 | 50 | } |
| 52 | 51 | |
| ... | ... | @@ -55,11 +54,15 @@ public class ImFriendController { |
| 55 | 54 | @Parameter(name = "friendUserId", description = "好友的用户编号", required = true, example = "2048") |
| 56 | 55 | public CommonResult<Boolean> deleteFriend( |
| 57 | 56 | @RequestParam("friendUserId") @NotNull(message = "好友用户编号不能为空") Long friendUserId) { |
| 58 | - Long userId = SecurityFrameworkUtils.getLoginUserId(); | |
| 59 | - friendService.deleteFriend(userId, friendUserId); | |
| 57 | + friendService.deleteFriend(getLoginUserId(), friendUserId); | |
| 60 | 58 | return success(true); |
| 61 | 59 | } |
| 62 | 60 | |
| 63 | - // TODO @AI:增加一个 updateFriend 接口,目前暂时就 dnd(免打扰)功能,后续可以增加备注等功能 | |
| 61 | + @PutMapping("/update") | |
| 62 | + @Operation(summary = "更新好友信息(当前仅免打扰)") | |
| 63 | + public CommonResult<Boolean> updateFriend(@Valid @RequestBody ImFriendUpdateReqVO reqVO) { | |
| 64 | + friendService.updateFriend(getLoginUserId(), reqVO); | |
| 65 | + return success(true); | |
| 66 | + } | |
| 64 | 67 | |
| 65 | 68 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.friend.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * IM 好友 Response VO | |
| 10 | + * | |
| 11 | + * @author 芋道源码 | |
| 12 | + */ | |
| 13 | +@Schema(description = "管理后台 - IM 好友 Response VO") | |
| 14 | +@Data | |
| 15 | +public class ImFriendRespVO { | |
| 16 | + | |
| 17 | + @Schema(description = "关系记录编号", example = "1024") | |
| 18 | + private Long id; | |
| 19 | + | |
| 20 | + @Schema(description = "好友的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 21 | + private Long friendUserId; | |
| 22 | + | |
| 23 | + @Schema(description = "是否免打扰", example = "false") | |
| 24 | + private Boolean muted; | |
| 25 | + | |
| 26 | + @Schema(description = "好友状态(0=正常,1=已删除)", example = "0") | |
| 27 | + private Integer status; | |
| 28 | + | |
| 29 | + @Schema(description = "添加好友时间") | |
| 30 | + private LocalDateTime addTime; | |
| 31 | + | |
| 32 | + @Schema(description = "删除好友时间") | |
| 33 | + private LocalDateTime deleteTime; | |
| 34 | + | |
| 35 | + // ========== 下面是聚合字段,方便前端显示 ========== | |
| 36 | + | |
| 37 | + @Schema(description = "好友昵称(实时聚合自 AdminUser)", example = "芋道") | |
| 38 | + private String nickname; | |
| 39 | + | |
| 40 | + @Schema(description = "好友头像(实时聚合自 AdminUser)") | |
| 41 | + private String avatar; | |
| 42 | + | |
| 43 | +} | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/vo/ImFriendUpdateReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.im.controller.admin.friend.vo; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotNull; | |
| 5 | +import lombok.Data; | |
| 6 | + | |
| 7 | +@Schema(description = "管理后台 - IM 好友更新 Request VO") | |
| 8 | +@Data | |
| 9 | +public class ImFriendUpdateReqVO { | |
| 10 | + | |
| 11 | + @Schema(description = "好友的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 12 | + @NotNull(message = "好友用户编号不能为空") | |
| 13 | + private Long friendUserId; | |
| 14 | + | |
| 15 | + @Schema(description = "是否免打扰", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 16 | + @NotNull(message = "免打扰状态不能为空") | |
| 17 | + private Boolean muted; | |
| 18 | + | |
| 19 | +} | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/friend/ImFriendDO.java
| 1 | 1 | package cn.iocoder.yudao.module.im.dal.dataobject.friend; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 5 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 5 | 6 | import com.baomidou.mybatisplus.annotation.TableId; |
| 6 | 7 | import com.baomidou.mybatisplus.annotation.TableName; |
| 7 | 8 | import lombok.*; |
| 8 | 9 | |
| 10 | +import java.time.LocalDateTime; | |
| 11 | + | |
| 9 | 12 | /** |
| 10 | 13 | * IM 好友关系 DO |
| 11 | 14 | * <p> |
| 12 | 15 | * 业务语义: |
| 13 | 16 | * - 双向关系:A-B 互为好友会存 2 条记录(userId=A, friendUserId=B 和 userId=B, friendUserId=A) |
| 14 | - * - 软删除:`deleted=1` 表示单向解除好友;如果需要"全部不看到",双向都要 deleted=1 | |
| 15 | - * - 免打扰:`muted` 控制是否屏蔽来自该好友的通知 | |
| 17 | + * - 状态管理:{@link #status} 使用 {@link CommonStatusEnum},ENABLE=正常,DISABLE=已删除 | |
| 18 | + * - 免打扰:{@link #muted} 控制是否屏蔽来自该好友的通知 | |
| 16 | 19 | * |
| 17 | 20 | * @author 芋道源码 |
| 18 | 21 | */ |
| ... | ... | @@ -47,12 +50,21 @@ public class ImFriendDO extends BaseDO { |
| 47 | 50 | * 是否免打扰 |
| 48 | 51 | */ |
| 49 | 52 | private Boolean muted; |
| 50 | - | |
| 51 | - // TODO @AI:status 字段,枚举 CommonStatusEnum,表示正常 / 解散;如果是解散状态,则不允许任何操作了。 | |
| 52 | - // TODO @AI:addTime、deleteTime 字段,分别表示添加好友的时间和删除好友的时间;如果 deleteTime 不为 null,则表示已删除。不使用 deleted; | |
| 53 | - | |
| 54 | - // 说明: | |
| 55 | - // - 删除状态用 BaseDO 的 deleted 字段表达(0=正常,1=已删除) | |
| 56 | - // - 添加时间用 BaseDO 的 createTime;删除时间对应 updateTime | |
| 53 | + /** | |
| 54 | + * 好友状态 | |
| 55 | + * <p> | |
| 56 | + * 枚举 {@link CommonStatusEnum} | |
| 57 | + */ | |
| 58 | + private Integer status; | |
| 59 | + /** | |
| 60 | + * 添加好友时间 | |
| 61 | + */ | |
| 62 | + private LocalDateTime addTime; | |
| 63 | + /** | |
| 64 | + * 删除好友时间 | |
| 65 | + * <p> | |
| 66 | + * 不为 null 时表示已删除 | |
| 67 | + */ | |
| 68 | + private LocalDateTime deleteTime; | |
| 57 | 69 | |
| 58 | 70 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO; | |
| 4 | +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; | |
| 5 | +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | |
| 6 | + | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 3 | 9 | /** |
| 4 | 10 | * IM 好友关系 Service 接口 |
| 5 | 11 | * |
| ... | ... | @@ -9,12 +15,75 @@ public interface ImFriendService { |
| 9 | 15 | |
| 10 | 16 | /** |
| 11 | 17 | * 校验两个用户是否是好友关系(未删除状态) |
| 18 | + * | |
| 19 | + * @param userId 用户编号 | |
| 20 | + * @param friendUserId 好友用户编号 | |
| 21 | + */ | |
| 22 | + void validateFriendExists(Long userId, Long friendUserId); | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 获得当前用户的好友列表(含昵称/头像聚合) | |
| 26 | + * <p> | |
| 27 | + * 返回的列表里**包含已删除状态**,前端按 status 区分; | |
| 28 | + * 原因是本地已经建立会话的好友被单向删除后,前端仍需要展示头像/昵称。 | |
| 29 | + * | |
| 30 | + * @param userId 用户编号 | |
| 31 | + * @return 好友列表(含聚合后的用户信息) | |
| 32 | + */ | |
| 33 | + List<ImFriendRespVO> getMyFriendList(Long userId); | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 查询一个好友(仅限本人视角的好友关系) | |
| 37 | + * <p> | |
| 38 | + * 只返回"本人→对方"的关系记录; | |
| 39 | + * 对方不是本人好友时抛 {@link cn.iocoder.yudao.module.im.enums.ErrorCodeConstants#FRIEND_NOT_FRIEND}。 | |
| 40 | + * | |
| 41 | + * @param userId 当前登录用户编号 | |
| 42 | + * @param friendUserId 目标用户编号 | |
| 43 | + * @return 好友信息(含聚合后的用户信息) | |
| 44 | + */ | |
| 45 | + ImFriendRespVO getFriend(Long userId, Long friendUserId); | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 添加好友(双向绑定) | |
| 49 | + * <p> | |
| 50 | + * 核心流程: | |
| 51 | + * 1. 校验:不允许添加自己;对方必须存在 | |
| 52 | + * 2. 双向插入/更新关系(若已存在则刷新为未删除) | |
| 53 | + * 3. 推送"你们已成为好友"系统提示(TODO) | |
| 54 | + * | |
| 55 | + * @param userId 当前登录用户编号 | |
| 56 | + * @param friendUserId 好友的用户编号 | |
| 57 | + */ | |
| 58 | + void addFriend(Long userId, Long friendUserId); | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * 删除好友(双向软删除) | |
| 62 | + * <p> | |
| 63 | + * 两边关系都标记为已删除状态。 | |
| 64 | + * | |
| 65 | + * @param userId 当前登录用户编号 | |
| 66 | + * @param friendUserId 好友的用户编号 | |
| 67 | + */ | |
| 68 | + void deleteFriend(Long userId, Long friendUserId); | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * 更新好友信息(仅更新本人→对方的单边关系属性) | |
| 12 | 72 | * <p> |
| 13 | - * 如果不是好友,直接抛出业务异常 | |
| 73 | + * 目前支持:免打扰(muted);后续可扩展备注等字段。 | |
| 74 | + * | |
| 75 | + * @param userId 当前登录用户编号 | |
| 76 | + * @param reqVO 更新请求 | |
| 77 | + */ | |
| 78 | + void updateFriend(Long userId, ImFriendUpdateReqVO reqVO); | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * 查询某个用户视角下的好友关系记录(仅单边) | |
| 14 | 82 | * |
| 15 | 83 | * @param userId 用户编号 |
| 16 | 84 | * @param friendUserId 好友用户编号 |
| 85 | + * @return 关系记录,不存在返回 null | |
| 17 | 86 | */ |
| 18 | - void validateIsFriend(Long userId, Long friendUserId); | |
| 87 | + ImFriendDO getFriendDO(Long userId, Long friendUserId); | |
| 19 | 88 | |
| 20 | 89 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.im.service.friend; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 4 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 5 | +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendRespVO; | |
| 6 | +import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; | |
| 4 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 5 | 8 | import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; |
| 9 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 10 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 6 | 11 | import jakarta.annotation.Resource; |
| 7 | 12 | import org.springframework.stereotype.Service; |
| 13 | +import org.springframework.transaction.annotation.Transactional; | |
| 8 | 14 | import org.springframework.validation.annotation.Validated; |
| 9 | 15 | |
| 16 | +import java.time.LocalDateTime; | |
| 17 | +import java.util.Collections; | |
| 18 | +import java.util.List; | |
| 19 | +import java.util.Map; | |
| 20 | +import java.util.Objects; | |
| 21 | + | |
| 10 | 22 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 23 | +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF; | |
| 11 | 24 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; |
| 12 | 25 | |
| 13 | 26 | /** |
| ... | ... | @@ -22,12 +35,160 @@ public class ImFriendServiceImpl implements ImFriendService { |
| 22 | 35 | @Resource |
| 23 | 36 | private ImFriendMapper imFriendMapper; |
| 24 | 37 | |
| 38 | + @Resource | |
| 39 | + private AdminUserApi adminUserApi; | |
| 40 | + | |
| 25 | 41 | @Override |
| 26 | - public void validateIsFriend(Long userId, Long friendUserId) { | |
| 42 | + public void validateFriendExists(Long userId, Long friendUserId) { | |
| 27 | 43 | ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); |
| 28 | - if (friend == null || CommonStatusEnum.DISABLE.getStatus().equals(friend.getStatus())) { | |
| 44 | + if (friend == null || CommonStatusEnum.isDisable(friend.getStatus())) { | |
| 45 | + throw exception(FRIEND_NOT_FRIEND); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + | |
| 49 | + // TODO @AI:这里只负责返回 List<ImFriendDO>;VO 组装在 controller 搞; | |
| 50 | + // TODO @AI:方法改成 getEnabledFriendList 之类的,明确只返回未删除的好友;如果需要展示已删除的好友,提供一个 getAllFriendList 之类的方法。 | |
| 51 | + @Override | |
| 52 | + public List<ImFriendRespVO> getMyFriendList(Long userId) { | |
| 53 | + List<ImFriendDO> friends = imFriendMapper.selectListByUserId(userId); | |
| 54 | + if (friends.isEmpty()) { | |
| 55 | + return Collections.emptyList(); | |
| 56 | + } | |
| 57 | + // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1 | |
| 58 | + List<Long> friendUserIds = CollectionUtils.convertList(friends, ImFriendDO::getFriendUserId); | |
| 59 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(friendUserIds); | |
| 60 | + return CollectionUtils.convertList(friends, f -> buildRespVO(f, userMap.get(f.getFriendUserId()))); | |
| 61 | + } | |
| 62 | + | |
| 63 | + // TODO @AI:这里只负责返回 ImFriendDO; | |
| 64 | + @Override | |
| 65 | + public ImFriendRespVO getFriend(Long userId, Long friendUserId) { | |
| 66 | + ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); | |
| 67 | + if (friend == null) { | |
| 68 | + throw exception(FRIEND_NOT_FRIEND); | |
| 69 | + } | |
| 70 | + AdminUserRespDTO user = adminUserApi.getUser(friendUserId); | |
| 71 | + return buildRespVO(friend, user); | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public ImFriendDO getFriendDO(Long userId, Long friendUserId) { | |
| 76 | + return imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); | |
| 77 | + } | |
| 78 | + | |
| 79 | + @Override | |
| 80 | + public void updateFriend(Long userId, ImFriendUpdateReqVO reqVO) { | |
| 81 | + // 1. 校验好友关系存在 | |
| 82 | + ImFriendDO friend = imFriendMapper.selectByUserIdAndFriendUserId(userId, reqVO.getFriendUserId()); | |
| 83 | + if (friend == null) { | |
| 29 | 84 | throw exception(FRIEND_NOT_FRIEND); |
| 30 | 85 | } |
| 86 | + // 2. 更新好友属性(目前仅免打扰) | |
| 87 | + ImFriendDO updateObj = new ImFriendDO(); | |
| 88 | + updateObj.setId(friend.getId()); | |
| 89 | + updateObj.setMuted(reqVO.getMuted()); | |
| 90 | + imFriendMapper.updateById(updateObj); | |
| 91 | + } | |
| 92 | + | |
| 93 | + @Override | |
| 94 | + @Transactional(rollbackFor = Exception.class) | |
| 95 | + public void addFriend(Long userId, Long friendUserId) { | |
| 96 | + // 1.1 校验:不允许添加自己为好友 | |
| 97 | + if (Objects.equals(userId, friendUserId)) { | |
| 98 | + throw exception(FRIEND_ADD_SELF); | |
| 99 | + } | |
| 100 | + // 1.2 校验对方存在(禁用/不存在的账号不允许添加) | |
| 101 | + adminUserApi.validateUser(friendUserId); | |
| 102 | + | |
| 103 | + // 双向绑定 | |
| 104 | + // DONE @AI:改成 createOrUpdateFriend 之类的接口,内部自动处理"已删除的好友再加回来"的情况;或者直接在数据库层面通过唯一索引 + 物理更新来解决。 | |
| 105 | + // 说明:bindFriend 内部已处理 DISABLE → ENABLE 的恢复逻辑 | |
| 106 | + bindFriend(userId, friendUserId); | |
| 107 | + bindFriend(friendUserId, userId); | |
| 108 | + | |
| 109 | + // TODO @AI:推送异步化:1)好友的打招呼;2)通知好友发生变化的消息; | |
| 110 | + | |
| 111 | + // TODO @AI:实现如下: | |
| 112 | + // TODO @芋艿:【对齐】这里缺少了好友添加成功的系统提示消息, | |
| 113 | + // 需要通过 `ImPrivateMessageService` 插入一条 TIP_TEXT 类型的消息并走 WebSocket 推送给双方。 | |
| 114 | + // 涉及 WebSocket 推送器的事件类型扩展(当前 `ImWsEventType` 无 FRIEND_NEW),待统一后补上。 | |
| 115 | + } | |
| 116 | + | |
| 117 | + @Override | |
| 118 | + @Transactional(rollbackFor = Exception.class) | |
| 119 | + public void deleteFriend(Long userId, Long friendUserId) { | |
| 120 | + // 双向标记为 DISABLE + 记录 deleteTime | |
| 121 | + unbindFriend(userId, friendUserId); | |
| 122 | + unbindFriend(friendUserId, userId); | |
| 123 | + | |
| 124 | + // TODO @芋艿:【对齐】这里缺少了好友删除的系统提示 + FRIEND_DEL WebSocket 推送, | |
| 125 | + // 待 WS 事件类型扩展后补上。 | |
| 126 | + } | |
| 127 | + | |
| 128 | + /** | |
| 129 | + * 单向绑定好友关系: | |
| 130 | + * - 首次:新增记录(status=ENABLE,addTime=now) | |
| 131 | + * - 已存在且 ENABLE:无需重复操作 | |
| 132 | + * - 已存在但 DISABLE:恢复关系(status=ENABLE,刷新 addTime,清空 deleteTime) | |
| 133 | + */ | |
| 134 | + // TODO @AI:spring cache;参考别的模块的写法; | |
| 135 | + private void bindFriend(Long userId, Long friendUserId) { | |
| 136 | + ImFriendDO exists = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); | |
| 137 | + if (exists != null) { | |
| 138 | + if (CommonStatusEnum.isEnable(exists.getStatus())) { | |
| 139 | + return; // 已是好友,跳过 | |
| 140 | + } | |
| 141 | + // 恢复已删除的好友关系 | |
| 142 | + ImFriendDO updateObj = new ImFriendDO(); | |
| 143 | + updateObj.setId(exists.getId()); | |
| 144 | + updateObj.setStatus(CommonStatusEnum.ENABLE.getStatus()); | |
| 145 | + updateObj.setAddTime(LocalDateTime.now()); | |
| 146 | + updateObj.setDeleteTime(null); | |
| 147 | + imFriendMapper.updateById(updateObj); | |
| 148 | + return; | |
| 149 | + } | |
| 150 | + // 新增 | |
| 151 | + ImFriendDO friend = ImFriendDO.builder() | |
| 152 | + .userId(userId) | |
| 153 | + .friendUserId(friendUserId) | |
| 154 | + .muted(false) | |
| 155 | + .status(CommonStatusEnum.ENABLE.getStatus()) | |
| 156 | + .addTime(LocalDateTime.now()) | |
| 157 | + .build(); | |
| 158 | + imFriendMapper.insert(friend); | |
| 159 | + } | |
| 160 | + | |
| 161 | + /** | |
| 162 | + * 单向解除好友关系(status 设为 DISABLE,记录 deleteTime) | |
| 163 | + */ | |
| 164 | + private void unbindFriend(Long userId, Long friendUserId) { | |
| 165 | + ImFriendDO exists = imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); | |
| 166 | + if (exists == null || CommonStatusEnum.isDisable(exists.getStatus())) { | |
| 167 | + return; | |
| 168 | + } | |
| 169 | + ImFriendDO updateObj = new ImFriendDO(); | |
| 170 | + updateObj.setId(exists.getId()); | |
| 171 | + updateObj.setStatus(CommonStatusEnum.DISABLE.getStatus()); | |
| 172 | + updateObj.setDeleteTime(LocalDateTime.now()); | |
| 173 | + imFriendMapper.updateById(updateObj); | |
| 174 | + } | |
| 175 | + | |
| 176 | + /** | |
| 177 | + * 聚合 ImFriendDO + AdminUser 构造响应 VO | |
| 178 | + */ | |
| 179 | + private ImFriendRespVO buildRespVO(ImFriendDO friend, AdminUserRespDTO user) { | |
| 180 | + ImFriendRespVO vo = new ImFriendRespVO(); | |
| 181 | + vo.setId(friend.getId()); | |
| 182 | + vo.setFriendUserId(friend.getFriendUserId()); | |
| 183 | + vo.setMuted(friend.getMuted()); | |
| 184 | + vo.setStatus(friend.getStatus()); | |
| 185 | + vo.setAddTime(friend.getAddTime()); | |
| 186 | + vo.setDeleteTime(friend.getDeleteTime()); | |
| 187 | + if (user != null) { | |
| 188 | + vo.setNickname(user.getNickname()); | |
| 189 | + vo.setAvatar(user.getAvatar()); | |
| 190 | + } | |
| 191 | + return vo; | |
| 31 | 192 | } |
| 32 | 193 | |
| 33 | 194 | } | ... | ... |