refactor(project): 重构权限常量定义并移除需求进度聚合功能
- 将产品和项目查询权限码统一提取到常量类中 - 移除需求进度聚合计算的相关实现代码 - 更新权限验证注解使用新的常量定义 - 清理相关的单元测试代码 - 更新错误码注释说明
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package com.njcn.rdms.module.system.api.notify;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.module.system.api.notify.dto.NotifySingleSendReqDTO;
|
||||
import com.njcn.rdms.module.system.enums.ApiConstants;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 站内信发送 API(跨模块统一入口契约)。
|
||||
*
|
||||
* <p>业务方(如 project)经此发送站内信,不依赖 system-boot 内部发送实现。
|
||||
* userType 在能力层固定 ADMIN,不暴露给业务方。</p>
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@FeignClient(name = ApiConstants.NAME)
|
||||
@Tag(name = "RPC 服务 - 站内信发送") // 对 NotifySendService 的封装,供其它模块统一入口调用
|
||||
public interface NotifyMessageSendApi {
|
||||
|
||||
String PREFIX = ApiConstants.PREFIX + "/notify-message-send";
|
||||
|
||||
@PostMapping(PREFIX + "/send-single")
|
||||
@Operation(summary = "发送单条站内信")
|
||||
CommonResult<Long> sendSingleNotify(@Valid @RequestBody NotifySingleSendReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 便捷方法:发送单条站内信给管理后台用户(userType 固定 ADMIN,由实现层处理)。
|
||||
*
|
||||
* @param userId 接收用户编号
|
||||
* @param templateCode 模板编码(场景标识,发送前模板必须已配置)
|
||||
* @param templateParams 模板参数
|
||||
*/
|
||||
default void sendSingleNotifyToAdmin(Long userId, String templateCode, Map<String, Object> templateParams) {
|
||||
sendSingleNotify(new NotifySingleSendReqDTO().setUserId(userId)
|
||||
.setTemplateCode(templateCode).setTemplateParams(templateParams)).checkError();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.njcn.rdms.module.system.api.notify.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 发送单条站内信 Request DTO(跨模块统一入口入参)。
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Schema(description = "RPC 服务 - 发送单条站内信 Request DTO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class NotifySingleSendReqDTO {
|
||||
|
||||
@Schema(description = "接收用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "接收用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "站内信模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "task_assigned")
|
||||
@NotNull(message = "模板编码不能为空")
|
||||
private String templateCode;
|
||||
|
||||
@Schema(description = "模板参数(占位符 -> 值)", example = "{\"taskName\":\"联调\"}")
|
||||
private Map<String, Object> templateParams;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.rdms.module.system.api.notify;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.module.system.api.notify.dto.NotifySingleSendReqDTO;
|
||||
import com.njcn.rdms.module.system.service.notify.NotifySendService;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* {@link NotifyMessageSendApi} 的实现:委托现有 {@link NotifySendService},不重写发送逻辑。
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@Validated
|
||||
@Hidden
|
||||
public class NotifyMessageSendApiImpl implements NotifyMessageSendApi {
|
||||
|
||||
@Resource
|
||||
private NotifySendService notifySendService;
|
||||
|
||||
@Override
|
||||
public CommonResult<Long> sendSingleNotify(NotifySingleSendReqDTO reqDTO) {
|
||||
Long logId = notifySendService.sendSingleNotifyToAdmin(
|
||||
reqDTO.getUserId(), reqDTO.getTemplateCode(), reqDTO.getTemplateParams());
|
||||
return success(logId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -99,4 +99,13 @@ public class NoticeController {
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/recent")
|
||||
@Operation(summary = "获取最近公告", description = "工作台首页用,登录即可访问,仅返回正常状态公告")
|
||||
public CommonResult<List<NoticeRespVO>> getRecentNotices(
|
||||
@RequestParam(name = "size", defaultValue = "3") Integer size,
|
||||
@RequestParam(name = "type", required = false) Integer type) {
|
||||
List<NoticeDO> list = noticeService.getRecentNotices(size, type);
|
||||
return success(BeanUtils.toBean(list, NoticeRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ public class NotifyMessageMyPageReqVO extends PageParam {
|
||||
@Schema(description = "是否已读", example = "true")
|
||||
private Boolean readStatus;
|
||||
|
||||
@Schema(description = "关键字,对消息正文模糊匹配;不传或空串 = 不过滤", example = "指派")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
@@ -17,4 +17,17 @@ public interface NoticeMapper extends BaseMapperX<NoticeDO> {
|
||||
.orderByDesc(NoticeDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询最近 N 条公告:仅 status 正常(CommonStatusEnum.ENABLE=0),可按 type 过滤,按 id 倒序取 size 条。
|
||||
* type 为 null 时不过滤类型;type 动态透传,不做代码侧白名单校验。
|
||||
*/
|
||||
default java.util.List<NoticeDO> selectRecentList(Integer size, Integer type) {
|
||||
int limit = (size == null || size <= 0) ? 3 : size;
|
||||
return selectList(new LambdaQueryWrapperX<NoticeDO>()
|
||||
.eq(NoticeDO::getStatus, com.njcn.rdms.framework.common.enums.CommonStatusEnum.ENABLE.getStatus())
|
||||
.eqIfPresent(NoticeDO::getType, type)
|
||||
.orderByDesc(NoticeDO::getId)
|
||||
.last("LIMIT " + limit));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,9 +29,12 @@ public interface NotifyMessageMapper extends BaseMapperX<NotifyMessageDO> {
|
||||
default PageResult<NotifyMessageDO> selectPage(NotifyMessageMyPageReqVO reqVO, Long userId, Integer userType) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<NotifyMessageDO>()
|
||||
.eqIfPresent(NotifyMessageDO::getReadStatus, reqVO.getReadStatus())
|
||||
// 关键字对最终渲染正文模糊匹配;空串/不传由 likeIfPresent 跳过,不影响存量调用
|
||||
.likeIfPresent(NotifyMessageDO::getTemplateContent, reqVO.getKeyword())
|
||||
.betweenIfPresent(NotifyMessageDO::getCreateTime, reqVO.getCreateTime())
|
||||
.eq(NotifyMessageDO::getUserId, userId)
|
||||
.eq(NotifyMessageDO::getUserType, userType)
|
||||
// 雪花 id 按时间单调递增,id 倒序 = 收到时间倒序且排序唯一稳定(与前端分页口径约定一致,勿改为按 read_status/read_time 排序)
|
||||
.orderByDesc(NotifyMessageDO::getId));
|
||||
}
|
||||
|
||||
|
||||
@@ -57,4 +57,13 @@ public interface NoticeService {
|
||||
*/
|
||||
NoticeDO getNotice(Long id);
|
||||
|
||||
/**
|
||||
* 获取最近 N 条公告(工作台首页用):仅正常状态,可按 type 过滤。
|
||||
*
|
||||
* @param size 条数,默认 3
|
||||
* @param type 公告类型({@link com.njcn.rdms.module.system.enums.notice.NoticeTypeEnum}),null 表示不过滤
|
||||
* @return 公告列表
|
||||
*/
|
||||
List<NoticeDO> getRecentNotices(Integer size, Integer type);
|
||||
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ public class NoticeServiceImpl implements NoticeService {
|
||||
return noticeMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NoticeDO> getRecentNotices(Integer size, Integer type) {
|
||||
int limit = (size == null || size <= 0) ? 3 : size;
|
||||
return noticeMapper.selectRecentList(limit, type);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void validateNoticeExists(Long id) {
|
||||
if (id == null) {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.njcn.rdms.module.system.dal.mysql.notify;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;
|
||||
import com.njcn.rdms.module.system.dal.dataobject.notify.NotifyMessageDO;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* {@link NotifyMessageMapper} 的单元测试 —— 我的站内信分页(keyword 检索 + 排序口径)。
|
||||
*/
|
||||
class NotifyMessageMapperTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initMyBatisPlusTableInfo() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), NotifyMessageDO.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private String captureMyPageSqlSegment(NotifyMessageMyPageReqVO reqVO) {
|
||||
NotifyMessageMapper mapper = mock(NotifyMessageMapper.class, invocation -> invocation.callRealMethod());
|
||||
doReturn(PageResult.empty()).when(mapper).selectPage(eq(reqVO), any(Wrapper.class));
|
||||
|
||||
mapper.selectPage(reqVO, 1L, 2);
|
||||
|
||||
ArgumentCaptor<Wrapper<NotifyMessageDO>> wrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
verify(mapper).selectPage(eq(reqVO), wrapperCaptor.capture());
|
||||
return wrapperCaptor.getValue().getSqlSegment().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void myPage_keywordShouldFilterTemplateContentByLike() {
|
||||
NotifyMessageMyPageReqVO reqVO = new NotifyMessageMyPageReqVO();
|
||||
reqVO.setKeyword("指派");
|
||||
reqVO.setReadStatus(false);
|
||||
|
||||
String sqlSegment = captureMyPageSqlSegment(reqVO);
|
||||
|
||||
assertTrue(sqlSegment.contains("template_content like"));
|
||||
assertTrue(sqlSegment.contains("read_status"));
|
||||
assertTrue(sqlSegment.contains("user_id"));
|
||||
assertTrue(sqlSegment.contains("user_type"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void myPage_blankKeywordShouldNotAddLikeCondition() {
|
||||
NotifyMessageMyPageReqVO noKeyword = new NotifyMessageMyPageReqVO();
|
||||
assertFalse(captureMyPageSqlSegment(noKeyword).contains("like"));
|
||||
|
||||
NotifyMessageMyPageReqVO blankKeyword = new NotifyMessageMyPageReqVO();
|
||||
blankKeyword.setKeyword("");
|
||||
assertFalse(captureMyPageSqlSegment(blankKeyword).contains("like"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void myPage_shouldOrderByIdDescOnly() {
|
||||
String sqlSegment = captureMyPageSqlSegment(new NotifyMessageMyPageReqVO());
|
||||
|
||||
// 与前端约定的分页口径:id 倒序(= 收到时间倒序、唯一稳定),不随读状态重排
|
||||
assertTrue(sqlSegment.contains("order by id desc"));
|
||||
assertFalse(sqlSegment.contains("read_status desc"));
|
||||
assertFalse(sqlSegment.contains("read_time"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.njcn.rdms.module.system.service.notice;
|
||||
|
||||
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import com.njcn.rdms.module.system.dal.dataobject.notice.NoticeDO;
|
||||
import com.njcn.rdms.module.system.dal.mysql.notice.NoticeMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link NoticeServiceImpl} 的单元测试 —— 工作台最近公告查询。
|
||||
*/
|
||||
class NoticeServiceImplTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private NoticeServiceImpl noticeService;
|
||||
|
||||
@Mock
|
||||
private NoticeMapper noticeMapper;
|
||||
|
||||
@Test
|
||||
void testGetRecentNotices_delegatesToMapper() {
|
||||
NoticeDO notice = new NoticeDO();
|
||||
notice.setId(100L);
|
||||
when(noticeMapper.selectRecentList(3, 2)).thenReturn(singletonList(notice));
|
||||
|
||||
List<NoticeDO> result = noticeService.getRecentNotices(3, 2);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(100L, result.get(0).getId());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user