feat(relation): 实现用户关注功能

- 新增关注用户接口,支持通过用户ID关注其他用户
- 添加参数校验,确保被关注用户ID不为空
- 实现关注用户时的业务逻辑,包括:
  -不能关注自己
  - 校验被关注用户是否存
  - 集成Feign客户端,调用用户服务查询用户信息
- 定义关注相关的异常码和错误信息
- 更新网关配置,路由/relation/**请求到用户关系服务- 添加HTTP客户端测试用例,用于验证关注功能
- 引入用户API依赖,支持远程调用用户服务
This commit is contained in:
2025-10-12 15:02:15 +08:00
parent 16ab8a13d2
commit 7942a46592
11 changed files with 182 additions and 1 deletions

View File

@@ -3,9 +3,11 @@ package com.hanserwei.hannote.user.relation.biz;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@MapperScan("com.hanserwei.hannote.user.relation.biz.domain.mapper")
@EnableFeignClients(basePackages = "com.hanserwei.hannote")
public class HannoteUserRelationBizApplication {
public static void main(String[] args) {
SpringApplication.run(HannoteUserRelationBizApplication.class, args);

View File

@@ -0,0 +1,29 @@
package com.hanserwei.hannote.user.relation.biz.controller;
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
import com.hanserwei.hannote.user.relation.biz.service.RelationService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/relation")
@Slf4j
public class RelationController {
@Resource
private RelationService relationService;
@PostMapping("/follow")
@ApiOperationLog(description = "关注用户")
public Response<?> follow(@Validated @RequestBody FollowUserReqVO followUserReqVO) {
return relationService.follow(followUserReqVO);
}
}

View File

@@ -13,6 +13,8 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
PARAM_NOT_VALID("RELATION-10001", "参数错误"),
// ----------- 业务异常状态码 -----------
CANT_FOLLOW_YOUR_SELF("RELATION-20001", "无法关注自己"),
FOLLOW_USER_NOT_EXISTED("RELATION-20002", "关注的用户不存在"),
;
// 异常码

View File

@@ -0,0 +1,17 @@
package com.hanserwei.hannote.user.relation.biz.model.vo;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FollowUserReqVO {
@NotNull(message = "被关注用户 ID 不能为空")
private Long followUserId;
}

View File

@@ -0,0 +1,37 @@
package com.hanserwei.hannote.user.relation.biz.rpc;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.api.UserFeignApi;
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class UserRpcService {
@Resource
private UserFeignApi userFeignApi;
/**
* 根据用户 ID 查询
*
* @param userId 用户 ID
* @return 用户信息
*/
public FindUserByIdRspDTO findById(Long userId) {
FindUserByIdReqDTO findUserByIdReqDTO = new FindUserByIdReqDTO();
findUserByIdReqDTO.setId(userId);
Response<FindUserByIdRspDTO> response = userFeignApi.findById(findUserByIdReqDTO);
if (!response.isSuccess() || Objects.isNull(response.getData())) {
return null;
}
return response.getData();
}
}

View File

@@ -0,0 +1,15 @@
package com.hanserwei.hannote.user.relation.biz.service;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
public interface RelationService {
/**
* 关注用户
*
* @param followUserReqVO 关注用户请求
* @return 响应
*/
Response<?> follow(FollowUserReqVO followUserReqVO);
}

View File

@@ -0,0 +1,48 @@
package com.hanserwei.hannote.user.relation.biz.service.impl;
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
import com.hanserwei.framework.common.exception.ApiException;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
import com.hanserwei.hannote.user.relation.biz.enums.ResponseCodeEnum;
import com.hanserwei.hannote.user.relation.biz.model.vo.FollowUserReqVO;
import com.hanserwei.hannote.user.relation.biz.rpc.UserRpcService;
import com.hanserwei.hannote.user.relation.biz.service.RelationService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
@Slf4j
public class RelationServiceImpl implements RelationService {
@Resource
private UserRpcService userRpcService;
@Override
public Response<?> follow(FollowUserReqVO followUserReqVO) {
// 获取被关注用户 ID
Long followUserId = followUserReqVO.getFollowUserId();
// 获取当前登录用户 ID
Long userId = LoginUserContextHolder.getUserId();
if (Objects.equals(userId, followUserId)) {
throw new ApiException(ResponseCodeEnum.CANT_FOLLOW_YOUR_SELF);
}
// 校验关注的用户是否存在
FindUserByIdRspDTO findUserByIdRspDTO = userRpcService.findById(followUserId);
if (Objects.isNull(findUserByIdRspDTO)){
throw new ApiException(ResponseCodeEnum.FOLLOW_USER_NOT_EXISTED);
}
// TODO: 校验关注数是否已经达到上限
// TODO: 写入 Redis ZSET 关注列表
// TODO: 发送 MQ
return Response.success();
}
}