feat(消息中心): 新增消息中心功能
This commit is contained in:
parent
e9ddb78d89
commit
711d7ce005
|
@ -1,15 +1,12 @@
|
|||
package io.metersphere.project.domain;
|
||||
|
||||
import io.metersphere.validation.groups.Created;
|
||||
import io.metersphere.validation.groups.Updated;
|
||||
import io.metersphere.validation.groups.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Notification implements Serializable {
|
||||
|
@ -19,7 +16,7 @@ public class Notification implements Serializable {
|
|||
|
||||
@Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{notification.type.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 30, message = "{notification.type.length_range}", groups = {Created.class, Updated.class})
|
||||
@Size(min = 1, max = 64, message = "{notification.type.length_range}", groups = {Created.class, Updated.class})
|
||||
private String type;
|
||||
|
||||
@Schema(description = "接收人", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
|
@ -34,7 +31,7 @@ public class Notification implements Serializable {
|
|||
|
||||
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{notification.status.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 30, message = "{notification.status.length_range}", groups = {Created.class, Updated.class})
|
||||
@Size(min = 1, max = 64, message = "{notification.status.length_range}", groups = {Created.class, Updated.class})
|
||||
private String status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
|
@ -57,7 +54,7 @@ public class Notification implements Serializable {
|
|||
|
||||
@Schema(description = "资源类型", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{notification.resource_type.not_blank}", groups = {Created.class})
|
||||
@Size(min = 1, max = 50, message = "{notification.resource_type.length_range}", groups = {Created.class, Updated.class})
|
||||
@Size(min = 1, max = 64, message = "{notification.resource_type.length_range}", groups = {Created.class, Updated.class})
|
||||
private String resourceType;
|
||||
|
||||
@Schema(description = "资源名称", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
|
|
|
@ -295,15 +295,15 @@ CREATE TABLE IF NOT EXISTS message_task_blob
|
|||
|
||||
CREATE TABLE IF NOT EXISTS notification(
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
|
||||
`type` VARCHAR(30) NOT NULL COMMENT '通知类型' ,
|
||||
`type` VARCHAR(64) NOT NULL COMMENT '通知类型' ,
|
||||
`receiver` VARCHAR(50) NOT NULL COMMENT '接收人' ,
|
||||
`subject` VARCHAR(255) NOT NULL COMMENT '标题' ,
|
||||
`status` VARCHAR(30) NOT NULL COMMENT '状态' ,
|
||||
`status` VARCHAR(64) NOT NULL COMMENT '状态' ,
|
||||
`create_time` BIGINT NOT NULL COMMENT '创建时间' ,
|
||||
`operator` VARCHAR(50) NOT NULL COMMENT '操作人' ,
|
||||
`operation` VARCHAR(50) NOT NULL COMMENT '操作' ,
|
||||
`resource_id` VARCHAR(50) NOT NULL COMMENT '资源ID' ,
|
||||
`resource_type` VARCHAR(50) NOT NULL COMMENT '资源类型' ,
|
||||
`resource_type` VARCHAR(64) NOT NULL COMMENT '资源类型' ,
|
||||
`resource_name` VARCHAR(255) NOT NULL COMMENT '资源名称' ,
|
||||
`content` TEXT NOT NULL COMMENT '通知内容' ,
|
||||
PRIMARY KEY (id)
|
||||
|
@ -314,11 +314,12 @@ CREATE TABLE IF NOT EXISTS notification(
|
|||
|
||||
|
||||
CREATE INDEX idx_receiver ON notification(receiver);
|
||||
CREATE INDEX idx_create_time ON notification(create_time desc);
|
||||
CREATE INDEX idx_create_time ON notification(create_time);
|
||||
CREATE INDEX idx_type ON notification(type);
|
||||
CREATE INDEX idx_subject ON notification(subject);
|
||||
CREATE INDEX idx_resource_id ON notification(resource_id);
|
||||
CREATE INDEX idx_resource_type ON notification(resource_type);
|
||||
CREATE INDEX idx_operator ON notification(operator);
|
||||
CREATE INDEX idx_resource_id ON notification(resource_id);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS project_robot(
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.github.pagehelper.Page;
|
|||
import com.github.pagehelper.PageHelper;
|
||||
|
||||
import io.metersphere.project.domain.Notification;
|
||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||
import io.metersphere.system.dto.sdk.request.NotificationRequest;
|
||||
import io.metersphere.system.service.NotificationService;
|
||||
import io.metersphere.system.utils.PageUtils;
|
||||
|
@ -35,19 +36,19 @@ public class NotificationController {
|
|||
|
||||
@GetMapping(value = "/read/{id}")
|
||||
@Operation(summary = "消息中心-将消息设置为已读")
|
||||
public Integer read(@PathVariable int id) {
|
||||
public Integer read(@PathVariable long id) {
|
||||
return notificationService.read(id, SessionUtils.getUserId());
|
||||
}
|
||||
|
||||
@GetMapping(value = "/read/all")
|
||||
@Operation(summary = "消息中心-获取消息中心所有已读消息")
|
||||
@Operation(summary = "消息中心-将消息中心所有信息设置为已读消息")
|
||||
public Integer readAll() {
|
||||
return notificationService.readAll(SessionUtils.getUserId());
|
||||
}
|
||||
|
||||
@PostMapping(value = "/count")
|
||||
@Operation(summary = "消息中心-获取消息中心消息具体类型具体状态的数量")
|
||||
public Integer countNotification(@RequestBody Notification notification) {
|
||||
return notificationService.countNotification(notification, SessionUtils.getUserId());
|
||||
public List<OptionDTO> countNotification(@RequestBody NotificationRequest notificationRequest) {
|
||||
return notificationService.countNotification(notificationRequest, SessionUtils.getUserId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ public class NotificationRequest extends BasePageRequest {
|
|||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "资源类型: TEST_PLAN/BUG/CASE/API/UI/LOAD/JENKINS")
|
||||
private String resourceType;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Long createTime;
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@ import java.util.List;
|
|||
|
||||
public interface BaseNotificationMapper {
|
||||
|
||||
List<Notification> listNotification(@Param("notificationRequest") NotificationRequest notificationRequest);
|
||||
|
||||
int countNotification(@Param("notification") Notification notification);
|
||||
List<Notification> listNotification(@Param("request") NotificationRequest notificationRequest);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,27 +4,20 @@
|
|||
|
||||
<select id="listNotification" resultType="io.metersphere.project.domain.Notification">
|
||||
SELECT * FROM notification
|
||||
WHERE receiver = #{notificationRequest.receiver} AND create_time > (unix_timestamp() - 90 * 24 * 3600) * 1000
|
||||
<if test='notification.title != null and notification.title != ""'>
|
||||
AND ( title LIKE #{notificationRequest.title} OR content LIKE #{notificationRequest.title} )
|
||||
WHERE receiver = #{request.receiver} AND create_time > (unix_timestamp() - 90 * 24 * 3600) * 1000
|
||||
<if test='request.title != null and request.title != ""'>
|
||||
AND ( title LIKE #{request.title} OR content LIKE #{request.title} )
|
||||
</if>
|
||||
<if test='notification.type != null and notification.type != ""'>
|
||||
AND type = #{notificationRequest.type}
|
||||
<if test='request.type != null and request.type != ""'>
|
||||
AND type = #{request.type}
|
||||
</if>
|
||||
<if test='request.status != null and request.status != ""'>
|
||||
AND status = #{request.status}
|
||||
</if>
|
||||
<if test='request.resourceType != null and notificationRequest.resourceType != ""'>
|
||||
AND resource_type LIKE #{request.resourceType}
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="countNotification" resultType="java.lang.Integer">
|
||||
SELECT COUNT(*) FROM notification
|
||||
WHERE receiver = #{notification.receiver} AND create_time > (unix_timestamp() - 3600) * 1000
|
||||
<if test="notification.type != null">
|
||||
AND type = #{notification.type}
|
||||
</if>
|
||||
<if test="notification.status != null">
|
||||
AND status = #{notification.status}
|
||||
</if>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
|
@ -1,14 +1,15 @@
|
|||
package io.metersphere.system.notice.sender;
|
||||
|
||||
|
||||
import io.metersphere.system.dto.sdk.SessionUser;
|
||||
import io.metersphere.system.notice.annotation.SendNotice;
|
||||
import io.metersphere.sdk.util.CommonBeanFactory;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.sdk.util.LogUtils;
|
||||
import io.metersphere.system.dto.sdk.SessionUser;
|
||||
import io.metersphere.system.notice.annotation.SendNotice;
|
||||
import io.metersphere.system.utils.SessionUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.beanutils.BeanMap;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
|
@ -40,6 +41,8 @@ public class SendNoticeAspect {
|
|||
private StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
|
||||
private ThreadLocal<String> source = new ThreadLocal<>();
|
||||
|
||||
private final static String ID = "id";
|
||||
|
||||
@Pointcut("@annotation(io.metersphere.system.notice.annotation.SendNotice)")
|
||||
public void pointcut() {
|
||||
}
|
||||
|
@ -149,6 +152,11 @@ public class SendNoticeAspect {
|
|||
SessionUser sessionUser = SessionUtils.getUser();
|
||||
String currentProjectId = SessionUtils.getCurrentProjectId();
|
||||
LogUtils.info("event:" + event);
|
||||
String resultStr = JSON.toJSONString(retValue);
|
||||
Map object = JSON.parseMap(resultStr);
|
||||
if (MapUtils.isNotEmpty(object) && object.containsKey(ID)) {
|
||||
resources.add(object);
|
||||
}
|
||||
afterReturningNoticeSendService.sendNotice(taskType, event, resources, sessionUser, currentProjectId);
|
||||
} catch (Exception e) {
|
||||
LogUtils.error(e.getMessage(), e);
|
||||
|
|
|
@ -9,7 +9,6 @@ import io.metersphere.system.notice.Receiver;
|
|||
import io.metersphere.system.notice.constants.NotificationConstants;
|
||||
import io.metersphere.system.notice.sender.AbstractNoticeSender;
|
||||
import io.metersphere.system.service.NotificationService;
|
||||
import io.metersphere.system.uid.IDGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -47,7 +46,6 @@ public class InSiteNoticeSender extends AbstractNoticeSender {
|
|||
|
||||
Map<String, Object> paramMap = noticeModel.getParamMap();
|
||||
Notification notification = new Notification();
|
||||
notification.setId(IDGenerator.nextNum());
|
||||
notification.setSubject(noticeModel.getSubject());
|
||||
notification.setOperator(noticeModel.getOperator());
|
||||
notification.setOperation(noticeModel.getEvent());
|
||||
|
|
|
@ -5,6 +5,7 @@ package io.metersphere.system.service;
|
|||
import io.metersphere.project.domain.Notification;
|
||||
import io.metersphere.project.domain.NotificationExample;
|
||||
import io.metersphere.project.mapper.NotificationMapper;
|
||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||
import io.metersphere.system.dto.sdk.request.NotificationRequest;
|
||||
import io.metersphere.system.mapper.BaseNotificationMapper;
|
||||
import io.metersphere.system.notice.constants.NotificationConstants;
|
||||
|
@ -13,7 +14,11 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
@ -25,9 +30,6 @@ public class NotificationService {
|
|||
private BaseNotificationMapper baseNotificationMapper;
|
||||
|
||||
public List<Notification> listNotification(NotificationRequest notificationRequest, String userId) {
|
||||
if (StringUtils.isNotBlank(notificationRequest.getTitle())) {
|
||||
notificationRequest.setTitle("%" + notificationRequest.getTitle() + "%");
|
||||
}
|
||||
if (StringUtils.isBlank(notificationRequest.getReceiver())) {
|
||||
notificationRequest.setReceiver(userId);
|
||||
}
|
||||
|
@ -50,11 +52,52 @@ public class NotificationService {
|
|||
return notificationMapper.updateByExampleSelective(record, example);
|
||||
}
|
||||
|
||||
public int countNotification(Notification notification, String userId) {
|
||||
if (StringUtils.isBlank(notification.getReceiver())) {
|
||||
notification.setReceiver(userId);
|
||||
public List<OptionDTO> countNotification(NotificationRequest notificationRequest, String userId) {
|
||||
List<OptionDTO>optionDTOS = new ArrayList<>();
|
||||
if (StringUtils.isBlank(notificationRequest.getReceiver())) {
|
||||
notificationRequest.setReceiver(userId);
|
||||
}
|
||||
return baseNotificationMapper.countNotification(notification);
|
||||
List<Notification> notifications = baseNotificationMapper.listNotification(notificationRequest);
|
||||
OptionDTO totalOptionDTO = new OptionDTO();
|
||||
totalOptionDTO.setId("total");
|
||||
totalOptionDTO.setName(String.valueOf(notifications.size()));
|
||||
optionDTOS.add(totalOptionDTO);
|
||||
buildSourceCount(notifications, optionDTOS);
|
||||
return optionDTOS;
|
||||
}
|
||||
|
||||
private static void buildSourceCount(List<Notification> notifications, List<OptionDTO> optionDTOS) {
|
||||
Map<String,Integer>countMap = new HashMap<>();
|
||||
Map<String, List<Notification>> resourceMap = notifications.stream().collect(Collectors.groupingBy(Notification::getResourceType));
|
||||
resourceMap.forEach((k,v)->{
|
||||
if (k.contains("TEST_PLAN")) {
|
||||
countMap.merge("TEST_PLAN", v.size(), Integer::sum);
|
||||
}
|
||||
if (k.contains("BUG")) {
|
||||
countMap.merge("BUG", v.size(), Integer::sum);
|
||||
}
|
||||
if (k.contains("CASE")) {
|
||||
countMap.merge("CASE", v.size(), Integer::sum);
|
||||
}
|
||||
if (k.contains("API")) {
|
||||
countMap.merge("API", v.size(), Integer::sum);
|
||||
}
|
||||
if (k.contains("UI")) {
|
||||
countMap.merge("UI", v.size(), Integer::sum);
|
||||
}
|
||||
if (k.contains("LOAD")) {
|
||||
countMap.merge("LOAD", v.size(), Integer::sum);
|
||||
}
|
||||
if (k.contains("JENKINS")) {
|
||||
countMap.merge("JENKINS", v.size(), Integer::sum);
|
||||
}
|
||||
});
|
||||
countMap.forEach((k,v)->{
|
||||
OptionDTO optionDTO = new OptionDTO();
|
||||
optionDTO.setId("total");
|
||||
optionDTO.setName(String.valueOf(notifications.size()));
|
||||
optionDTOS.add(optionDTO);
|
||||
});
|
||||
}
|
||||
|
||||
public void sendAnnouncement(Notification notification) {
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package io.metersphere.system.controller;
|
||||
|
||||
import io.metersphere.project.domain.Notification;
|
||||
import io.metersphere.project.domain.NotificationExample;
|
||||
import io.metersphere.project.mapper.NotificationMapper;
|
||||
import io.metersphere.sdk.util.JSON;
|
||||
import io.metersphere.system.base.BaseTest;
|
||||
import io.metersphere.system.controller.handler.ResultHolder;
|
||||
import io.metersphere.system.dto.sdk.OptionDTO;
|
||||
import io.metersphere.system.dto.sdk.request.NotificationRequest;
|
||||
import io.metersphere.system.notice.constants.NotificationConstants;
|
||||
import io.metersphere.system.utils.Pager;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@AutoConfigureMockMvc
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class NotificationControllerTests extends BaseTest {
|
||||
|
||||
public static final String NOTIFICATION_LIST_PAGE = "/notification/list/all/page";
|
||||
public static final String NOTIFICATION_READ = "/notification/read/";
|
||||
public static final String NOTIFICATION_READ_ALL = "/notification/read/all";
|
||||
public static final String NOTIFICATION_COUNT = "/notification/count";
|
||||
|
||||
@Resource
|
||||
protected NotificationMapper notificationMapper;
|
||||
|
||||
void saveNotice() {
|
||||
Notification notification = new Notification();
|
||||
notification.setSubject("功能用例更新通知");
|
||||
notification.setOperator("admin");
|
||||
notification.setOperation("UPDATE");
|
||||
notification.setResourceType("FUNCTIONAL_CASE_TASK");
|
||||
notification.setResourceName("功能用例导入测4");
|
||||
notification.setType("SYSTEM_NOTICE");
|
||||
notification.setStatus(NotificationConstants.Status.UNREAD.name());
|
||||
notification.setCreateTime(System.currentTimeMillis());
|
||||
notification.setReceiver("admin");
|
||||
notification.setResourceId("122334");
|
||||
notification.setContent("nihao");
|
||||
notificationMapper.insert(notification);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
void getNotificationSuccess() throws Exception {
|
||||
saveNotice();
|
||||
NotificationRequest notificationRequest = new NotificationRequest();
|
||||
notificationRequest.setPageSize(10);
|
||||
notificationRequest.setCurrent(1);
|
||||
notificationRequest.setReceiver("admin");
|
||||
notificationRequest.setType("SYSTEM_NOTICE");
|
||||
MvcResult mvcResult = this.requestPostWithOkAndReturn(NOTIFICATION_LIST_PAGE, notificationRequest);
|
||||
Pager<List<Notification>> tableData = JSON.parseObject(JSON.toJSONString(
|
||||
JSON.parseObject(mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8), ResultHolder.class).getData()),
|
||||
Pager.class);
|
||||
//返回值的页码和当前页码相同
|
||||
Assertions.assertEquals(tableData.getCurrent(), notificationRequest.getCurrent());
|
||||
Assertions.assertFalse(tableData.getList().isEmpty());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
void setNotificationReadSuccess() throws Exception {
|
||||
NotificationExample notificationExample = new NotificationExample();
|
||||
notificationExample.createCriteria().andStatusEqualTo(NotificationConstants.Status.UNREAD.name());
|
||||
List<Notification> notifications = notificationMapper.selectByExample(notificationExample);
|
||||
this.requestGetWithOkAndReturn(NOTIFICATION_READ+notifications.get(0).getId());
|
||||
notificationExample = new NotificationExample();
|
||||
notificationExample.createCriteria().andStatusEqualTo(NotificationConstants.Status.READ.name());
|
||||
List<Notification> readNotifications = notificationMapper.selectByExample(notificationExample);
|
||||
Assertions.assertFalse(readNotifications.isEmpty());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
void setNotificationReadAll() throws Exception {
|
||||
saveNotice();
|
||||
this.requestGetWithOk(NOTIFICATION_READ_ALL);
|
||||
NotificationExample notificationExample = new NotificationExample();
|
||||
notificationExample.createCriteria().andStatusEqualTo(NotificationConstants.Status.READ.name());
|
||||
List<Notification> notifications = notificationMapper.selectByExample(notificationExample);
|
||||
Assertions.assertFalse(notifications.isEmpty());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
void getNotificationCount() throws Exception {
|
||||
NotificationRequest notificationRequest = new NotificationRequest();
|
||||
notificationRequest.setPageSize(10);
|
||||
notificationRequest.setCurrent(1);
|
||||
notificationRequest.setReceiver("admin");
|
||||
notificationRequest.setType("SYSTEM_NOTICE");
|
||||
MvcResult mvcResult = this.requestPostWithOkAndReturn(NOTIFICATION_COUNT, notificationRequest);
|
||||
String updateReturnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||
ResultHolder resultHolder = JSON.parseObject(updateReturnData, ResultHolder.class);
|
||||
List<OptionDTO> optionDTOS = JSON.parseArray(JSON.toJSONString(resultHolder.getData()), OptionDTO.class);
|
||||
Assertions.assertFalse(optionDTOS.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue