init
This commit is contained in:
26
notice/notice-api/pom.xml
Normal file
26
notice/notice-api/pom.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>notice</artifactId>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>notice-api</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-swagger</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.fateverse.notice.dto;
|
||||
|
||||
import cn.fateverse.notice.enums.ActionEnums;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("公告发送实体")
|
||||
public class NoticeDto implements Serializable {
|
||||
|
||||
@ApiModelProperty("公告标题")
|
||||
@NotNull(message = "公告标题不能为空")
|
||||
private String noticeTitle;
|
||||
|
||||
@ApiModelProperty("公告类型(1通知 2公告)")
|
||||
@NotNull(message = "公告类型不能为空")
|
||||
private String noticeType;
|
||||
|
||||
@ApiModelProperty("发送类型,用户:user,用户数组:user,角色:role,部门:dept,全发:all")
|
||||
@NotNull(message = "发送类型不能为空")
|
||||
private String sendType;
|
||||
|
||||
private ActionEnums action;
|
||||
|
||||
@ApiModelProperty("发送类型的id")
|
||||
@NotNull(message = "发送对象id不能为空")
|
||||
private List<Long> senderIds;
|
||||
|
||||
@ApiModelProperty("公告内容")
|
||||
private String noticeContent;
|
||||
|
||||
@ApiModelProperty("内容类型: html,text 等")
|
||||
@NotNull(message = "内容类型不能为空")
|
||||
private String contentType;
|
||||
|
||||
@ApiModelProperty("发送群组")
|
||||
@NotNull(message = "消息发送群组不能为空")
|
||||
private String cluster;
|
||||
|
||||
@ApiModelProperty("公告备注")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.fateverse.notice.dubbo;
|
||||
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
public interface DubboNoticeService {
|
||||
|
||||
/**
|
||||
* 异步发送消息
|
||||
* @param noticeDto 发送消息实体
|
||||
*/
|
||||
@Async
|
||||
void syncSend(NoticeDto noticeDto);
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param noticeDto 发送消息实体
|
||||
*/
|
||||
void send(NoticeDto noticeDto);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import cn.fateverse.notice.enums.ActionEnums;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Data
|
||||
public class Message implements Serializable {
|
||||
private String id;
|
||||
|
||||
private Object message;
|
||||
|
||||
private ActionEnums type;
|
||||
|
||||
private String group;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.fateverse.notice.enums;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-04-14
|
||||
*/
|
||||
public enum ActionEnums {
|
||||
|
||||
/**
|
||||
* notice 的操作
|
||||
*/
|
||||
SEND("send"),
|
||||
REMOVE("remove"),
|
||||
;
|
||||
|
||||
|
||||
private final String type;
|
||||
|
||||
ActionEnums(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
79
notice/notice-biz/pom.xml
Normal file
79
notice/notice-biz/pom.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>notice</artifactId>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>notice-biz</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--通用模块-->
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-log</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-swagger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>notice-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-file</artifactId>
|
||||
</dependency>
|
||||
<!--rabbitmq-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<artifactId>common-excel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-reactor-netty</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.7.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.fateverse.notice;
|
||||
|
||||
import cn.fateverse.common.security.annotation.EnableSecurity;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-04-12
|
||||
*/
|
||||
@EnableSecurity
|
||||
@EnableDiscoveryClient
|
||||
@SpringBootApplication
|
||||
public class NoticeApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(NoticeApplication.class, args);
|
||||
System.out.println("notice 服务启动成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.fateverse.notice.config;
|
||||
|
||||
import cn.fateverse.notice.netty.NettyApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Netty Websocket服务配置类
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-04-14
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({NoticeProperties.class})
|
||||
public class NettyWebSocketConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
NettyApplication nettyApplication() {
|
||||
return new NettyApplication();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package cn.fateverse.notice.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
|
||||
/**
|
||||
* Netty Websocket 配置文件
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-04-14
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "notice")
|
||||
public class NoticeProperties {
|
||||
|
||||
public static final String BROAD_ROUTING_KEY = "broad";
|
||||
/**
|
||||
* 应用名称
|
||||
*/
|
||||
private String applicationName;
|
||||
/**
|
||||
* 服务端口,不填写则直接随机端口
|
||||
*/
|
||||
private Integer port;
|
||||
/**
|
||||
* socket路径
|
||||
*/
|
||||
private String path;
|
||||
/**
|
||||
* 交换机名称
|
||||
*/
|
||||
private String exchangeChatRanch = "exchange.chat.ranch";
|
||||
/**
|
||||
* 队列名称
|
||||
*/
|
||||
private String queueChatRanch = "queue.chat.ranch.";
|
||||
/**
|
||||
* 路由
|
||||
*/
|
||||
private String routingKey = "chat.key.";
|
||||
|
||||
public NoticeProperties(Environment environment) {
|
||||
ServerSocket socket = null;
|
||||
try {
|
||||
socket = new ServerSocket(0);
|
||||
this.port = socket.getLocalPort();
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("端口获取失败,没有空余端口可以使用");
|
||||
}
|
||||
this.applicationName = environment.getProperty("spring.application.name");
|
||||
this.path = "ws";
|
||||
|
||||
}
|
||||
|
||||
public String getApplicationName() {
|
||||
return applicationName;
|
||||
}
|
||||
|
||||
public void setApplicationName(String applicationName) {
|
||||
this.applicationName = applicationName;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
if (0 != port) {
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getExchangeChatRanch() {
|
||||
return exchangeChatRanch;
|
||||
}
|
||||
|
||||
public void setExchangeChatRanch(String exchangeChatRanch) {
|
||||
if (!ObjectUtils.isEmpty(exchangeChatRanch)){
|
||||
this.exchangeChatRanch = exchangeChatRanch;
|
||||
}
|
||||
}
|
||||
|
||||
public String getQueueChatRanch() {
|
||||
return queueChatRanch;
|
||||
}
|
||||
|
||||
public void setQueueChatRanch(String queueChatRanch) {
|
||||
if (!ObjectUtils.isEmpty(queueChatRanch)){
|
||||
this.queueChatRanch = queueChatRanch;
|
||||
}
|
||||
}
|
||||
|
||||
public String getRoutingKey() {
|
||||
return routingKey;
|
||||
}
|
||||
|
||||
public void setRoutingKey(String routingKey) {
|
||||
if (!ObjectUtils.isEmpty(routingKey)){
|
||||
this.routingKey = routingKey;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 广播路由
|
||||
*/
|
||||
public String getBroadRoutingKey() {
|
||||
return routingKey + BROAD_ROUTING_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.fateverse.notice.config;
|
||||
|
||||
import cn.fateverse.notice.entity.UserInfo;
|
||||
import org.redisson.spring.data.connection.RedissonConnectionFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-15
|
||||
*/
|
||||
//@Configuration
|
||||
public class RedisTemplateConfig {
|
||||
|
||||
|
||||
@Bean("noticeRedisTemplate")
|
||||
public RedisTemplate<String, UserInfo> noticeRedisTemplate(RedissonConnectionFactory redissonConnectionFactory) {
|
||||
RedisTemplate<String, UserInfo> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redissonConnectionFactory);
|
||||
//设置key序列化方式string
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.fateverse.notice.constant;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-10-15
|
||||
*/
|
||||
public class NoticeConstant {
|
||||
|
||||
public final static String USER = "user";
|
||||
public final static String ROLE = "role";
|
||||
public final static String DEPT = "dept";
|
||||
public final static String ALL = "all";
|
||||
public final static String READ = "1";
|
||||
public final static String NOT_READ = "0";
|
||||
public final static String USABLE = "1";
|
||||
public final static String DISABLED = "0";
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cn.fateverse.notice.controller;
|
||||
|
||||
import cn.fateverse.common.core.result.Result;
|
||||
import cn.fateverse.common.core.result.page.TableDataInfo;
|
||||
import cn.fateverse.common.core.utils.ObjectUtils;
|
||||
import cn.fateverse.common.excel.utils.ExcelUtil;
|
||||
import cn.fateverse.common.log.annotation.Log;
|
||||
import cn.fateverse.common.log.enums.BusinessType;
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import cn.fateverse.notice.entity.query.NoticeQuery;
|
||||
import cn.fateverse.notice.entity.vo.NoticeVo;
|
||||
import cn.fateverse.notice.service.NoticeService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Api(value = "公告管理",tags = "公告管理")
|
||||
@RestController
|
||||
@RequestMapping("/notice")
|
||||
public class NoticeController {
|
||||
|
||||
private final NoticeService noticeService;
|
||||
|
||||
public NoticeController(NoticeService noticeService) {
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
@ApiOperation("获取公告列表")
|
||||
@GetMapping
|
||||
@PreAuthorize("@ss.hasPermission('notice:notice:list')")
|
||||
public Result<TableDataInfo<NoticeVo>> list(NoticeQuery query) {
|
||||
TableDataInfo<NoticeVo> dataInfo = noticeService.searchList(query);
|
||||
return Result.ok(dataInfo);
|
||||
}
|
||||
|
||||
@ApiOperation("导出excel数据")
|
||||
@GetMapping("/export")
|
||||
@PreAuthorize("@ss.hasPermission('notice:notice:export')")
|
||||
public void export(NoticeQuery query){
|
||||
List<NoticeVo> list = noticeService.exportList(query);
|
||||
ExcelUtil.exportExcel(list,NoticeVo.class);
|
||||
}
|
||||
|
||||
@ApiOperation("获取万能查询详细信息")
|
||||
@GetMapping("/{noticeId}")
|
||||
@PreAuthorize("@ss.hasPermission('notice:notice:info')")
|
||||
public Result<NoticeVo> info(@PathVariable Long noticeId) {
|
||||
ObjectUtils.checkPk(noticeId);
|
||||
NoticeVo notice = noticeService.searchById(noticeId);
|
||||
return Result.ok(notice);
|
||||
}
|
||||
|
||||
@ApiOperation("新增公告")
|
||||
@PostMapping
|
||||
@Log(title = "新增公告",businessType = BusinessType.INSERT)
|
||||
@PreAuthorize("@ss.hasPermission('notice:notice:add')")
|
||||
public Result<Void> add(@RequestBody @Validated NoticeDto dto){
|
||||
if (ObjectUtils.isEmpty(dto.getSenderIds())){
|
||||
return Result.error("发送对象不能为空!");
|
||||
}
|
||||
noticeService.save(dto);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@ApiOperation("删除公告")
|
||||
@DeleteMapping("/{noticeId}")
|
||||
@Log(title = "删除公告",businessType = BusinessType.DELETE)
|
||||
@PreAuthorize("@ss.hasPermission('notice:notice:del')")
|
||||
public Result<Void> del(@PathVariable Long noticeId){
|
||||
ObjectUtils.checkPk(noticeId);
|
||||
noticeService.removeById(noticeId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.fateverse.notice.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.fateverse.common.core.result.Result;
|
||||
import cn.fateverse.common.file.entity.FileInfo;
|
||||
import cn.fateverse.common.file.service.FileStoreService;
|
||||
import cn.fateverse.common.file.service.impl.MinioFileStoreService;
|
||||
import cn.fateverse.notice.entity.NoticeFile;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-08-01
|
||||
*/
|
||||
@Api(value = "文件上传", tags = "文件上传")
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class NoticeFileController {
|
||||
|
||||
private final FileStoreService fileStoreService;
|
||||
|
||||
public NoticeFileController(MinioFileStoreService fileStoreService) {
|
||||
this.fileStoreService = fileStoreService;
|
||||
}
|
||||
|
||||
private final Set<String> fileType = new HashSet<>(Arrays.asList("pdf", "txt", "zip", "rar", "7z", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "mp3", "mp4", "jpg", "jpeg", "png", "gif", "mp3", "mp4"));
|
||||
|
||||
|
||||
@ApiOperation("上传文件")
|
||||
@PostMapping
|
||||
public Result<NoticeFile> uploadFile(MultipartFile file) {
|
||||
String fileName = file.getOriginalFilename();
|
||||
if (StrUtil.isBlank(fileName)){
|
||||
return Result.error("无文件名!");
|
||||
}
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
if (!(dotIndex > 0 && dotIndex < fileName.length() - 1)) {
|
||||
return Result.error("文件不不合法!");
|
||||
}
|
||||
String extension = fileName.substring(dotIndex + 1).toLowerCase();
|
||||
if (!fileType.contains(extension)){
|
||||
return Result.error("文件类型不合法!");
|
||||
}
|
||||
FileInfo upload = fileStoreService.upload(file);
|
||||
NoticeFile noticeFile = NoticeFile.builder()
|
||||
.url(upload.getUrl())
|
||||
.isImage(upload.getIsImage())
|
||||
.size(upload.getSize())
|
||||
.build();
|
||||
return Result.ok(noticeFile);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package cn.fateverse.notice.controller;
|
||||
|
||||
import cn.fateverse.common.core.result.Result;
|
||||
import cn.fateverse.common.core.result.page.TableDataInfo;
|
||||
import cn.fateverse.common.core.utils.LongUtils;
|
||||
import cn.fateverse.notice.entity.vo.NotifyVo;
|
||||
import cn.fateverse.notice.service.NotifyService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-07
|
||||
*/
|
||||
@Api(value = "用户公告管理", tags = "用户公告管理")
|
||||
@RestController
|
||||
@RequestMapping("/notify")
|
||||
public class NotifyController {
|
||||
|
||||
private final NotifyService notifyService;
|
||||
|
||||
public NotifyController(NotifyService notifyService) {
|
||||
this.notifyService = notifyService;
|
||||
}
|
||||
|
||||
@ApiOperation("获取用户公告列表")
|
||||
@GetMapping
|
||||
public Result<TableDataInfo<NotifyVo>> list(String cluster, String state) {
|
||||
TableDataInfo<NotifyVo> table = notifyService.searchList(cluster, state);
|
||||
return Result.ok(table);
|
||||
}
|
||||
|
||||
@ApiOperation("获取用户公告详细信息")
|
||||
@GetMapping("/{noticeId}")
|
||||
public Result<NotifyVo> info(@PathVariable Long noticeId) {
|
||||
if (LongUtils.isNull(noticeId)) {
|
||||
return Result.error("公告id不能为空");
|
||||
}
|
||||
NotifyVo notify = notifyService.searchById(noticeId);
|
||||
if (null == notify){
|
||||
return Result.error("获取数据失败!");
|
||||
}
|
||||
return Result.ok(notify);
|
||||
}
|
||||
|
||||
@ApiOperation("已读消息")
|
||||
@PutMapping("/read/{noticeId}")
|
||||
public Result<Void> read(@PathVariable Long noticeId) {
|
||||
if (LongUtils.isNull(noticeId)) {
|
||||
return Result.error("公告id不能为空");
|
||||
}
|
||||
notifyService.read(noticeId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@ApiOperation("全部已读消息")
|
||||
@PutMapping("/read/all")
|
||||
public Result<Void> readAll() {
|
||||
notifyService.readAll();
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@ApiOperation("删除消息")
|
||||
@DeleteMapping("/{noticeId}")
|
||||
public Result<Void> remove(@PathVariable Long noticeId) {
|
||||
if (LongUtils.isNull(noticeId)) {
|
||||
return Result.error("公告id不能为空");
|
||||
}
|
||||
notifyService.remove(noticeId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@ApiOperation("删除所有消息")
|
||||
@DeleteMapping("/batch/{noticeIds}")
|
||||
public Result<Void> batch(@PathVariable List<Long> noticeIds) {
|
||||
if (null == noticeIds || noticeIds.isEmpty()){
|
||||
return Result.error("参数不能为空");
|
||||
}
|
||||
notifyService.batchRemove(noticeIds);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@ApiOperation("删除所有消息")
|
||||
@DeleteMapping("/all")
|
||||
public Result<Void> remove() {
|
||||
notifyService.removeAll();
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.fateverse.notice.dubbo;
|
||||
|
||||
import cn.fateverse.notice.service.NoticeService;
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@DubboService
|
||||
public class DubboNoticeServiceImpl implements DubboNoticeService{
|
||||
|
||||
private final NoticeService noticeService;
|
||||
|
||||
public DubboNoticeServiceImpl(NoticeService noticeService) {
|
||||
this.noticeService = noticeService;
|
||||
}
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void syncSend(NoticeDto noticeDto) {
|
||||
noticeService.save(noticeDto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(NoticeDto noticeDto) {
|
||||
noticeService.save(noticeDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-07-26
|
||||
*/
|
||||
public class DelayedTask implements Delayed {
|
||||
|
||||
final private Channel channel;
|
||||
|
||||
final private long expire;
|
||||
|
||||
/**
|
||||
* 构造延时任务
|
||||
*
|
||||
* @param channel 通道
|
||||
* @param expire 任务延时时间(ms)
|
||||
*/
|
||||
public DelayedTask(Channel channel, long expire) {
|
||||
super();
|
||||
this.expire = expire + System.currentTimeMillis();
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDelay(TimeUnit unit) {
|
||||
return unit.convert(expire - System.currentTimeMillis(), unit);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(Delayed delayed) {
|
||||
long delta = getDelay(TimeUnit.NANOSECONDS) - delayed.getDelay(TimeUnit.NANOSECONDS);
|
||||
return (int) delta;
|
||||
}
|
||||
|
||||
public Channel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import cn.fateverse.common.core.annotaion.AutoUser;
|
||||
import cn.fateverse.common.core.annotaion.EnableAutoField;
|
||||
import cn.fateverse.common.core.entity.BaseEntity;
|
||||
import cn.fateverse.common.core.enums.AutoUserEnum;
|
||||
import cn.fateverse.common.core.enums.MethodEnum;
|
||||
import cn.fateverse.common.core.enums.StateEnum;
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@EnableAutoField
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Notice extends BaseEntity {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private Long noticeId;
|
||||
|
||||
private String noticeTitle;
|
||||
|
||||
private String noticeType;
|
||||
|
||||
private String sendType;
|
||||
|
||||
private String senderIds;
|
||||
|
||||
@AutoUser(value = AutoUserEnum.USER_ID,method = MethodEnum.INSERT)
|
||||
private Long publishId;
|
||||
|
||||
private String noticeContent;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private String state;
|
||||
|
||||
private String cluster;
|
||||
|
||||
public static Notice toNoticeMq(NoticeDto dto) {
|
||||
Notice notice = Notice.builder()
|
||||
.noticeTitle(dto.getNoticeTitle())
|
||||
.noticeType(dto.getNoticeType())
|
||||
.sendType(dto.getSendType())
|
||||
.state(StateEnum.NORMAL.getCode())
|
||||
.cluster(dto.getCluster())
|
||||
.senderIds(JSON.toJSONString(dto.getSenderIds()))
|
||||
.noticeContent(dto.getNoticeContent())
|
||||
.contentType(dto.getContentType())
|
||||
.build();
|
||||
notice.setRemark(dto.getRemark());
|
||||
return notice;
|
||||
}
|
||||
|
||||
public NoticeMq toNoticeMq() {
|
||||
return NoticeMq.builder()
|
||||
.noticeId(noticeId)
|
||||
.noticeType(noticeType)
|
||||
.sendType(sendType)
|
||||
.senderIds(JSON.parseArray(senderIds,Long.class))
|
||||
.cluster(cluster)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-08-01
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class NoticeFile {
|
||||
//附件路径
|
||||
private String url;
|
||||
//是否是图片
|
||||
private Boolean isImage;
|
||||
//附件大小
|
||||
private Long size;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import cn.fateverse.notice.enums.ActionEnums;
|
||||
import io.netty.channel.ChannelId;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-24
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class NoticeMq implements Serializable {
|
||||
|
||||
private Long noticeId;
|
||||
|
||||
private String noticeTitle;
|
||||
|
||||
private String noticeType;
|
||||
|
||||
private String sendType;
|
||||
|
||||
private ActionEnums action;
|
||||
|
||||
private List<Long> senderIds;
|
||||
|
||||
private String noticeContent;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private String cluster;
|
||||
|
||||
private String remark;
|
||||
|
||||
private ChannelId channelId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-05
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class SendNotice {
|
||||
|
||||
private String noticeTitle;
|
||||
|
||||
private String noticeType;
|
||||
|
||||
private String noticeContent;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private String remark;
|
||||
|
||||
public static SendNotice toSendNotice(NoticeDto dto) {
|
||||
return SendNotice.builder()
|
||||
.noticeTitle(dto.getNoticeTitle())
|
||||
.noticeType(dto.getNoticeType())
|
||||
.noticeContent(dto.getNoticeContent())
|
||||
.contentType(dto.getContentType())
|
||||
.remark(dto.getRemark())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Data
|
||||
public class SocketAuth {
|
||||
|
||||
private String type;
|
||||
|
||||
private String token;
|
||||
|
||||
private String cluster;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import io.netty.channel.ChannelId;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-04-16
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UserInfo implements Serializable {
|
||||
|
||||
private String userId;
|
||||
|
||||
private String redisKey;
|
||||
|
||||
private String routingKey;
|
||||
|
||||
private String cluster;
|
||||
|
||||
private Set<Long> roleSet;
|
||||
|
||||
private Long deptId;
|
||||
|
||||
private Set<Long> deptAncestors;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.fateverse.notice.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UserNotice {
|
||||
|
||||
private Long noticeId;
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String state;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.fateverse.notice.entity.query;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-05
|
||||
*/
|
||||
@Data
|
||||
public class NoticeQuery {
|
||||
|
||||
@ApiModelProperty("公告标题")
|
||||
private String noticeTitle;
|
||||
|
||||
@ApiModelProperty("公告类型(1通知 2公告)")
|
||||
private String noticeType;
|
||||
|
||||
@ApiModelProperty("发送类型,用户,用户数组,角色,部门,全发")
|
||||
private String sendType;
|
||||
|
||||
@ApiModelProperty("内容类型: html,text等")
|
||||
private String contentType;
|
||||
|
||||
@ApiModelProperty("公告状态(0正常 1关闭)")
|
||||
private String state;
|
||||
|
||||
@ApiModelProperty("群组")
|
||||
private String cluster;
|
||||
|
||||
@JsonIgnore
|
||||
private Long publishId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package cn.fateverse.notice.entity.vo;
|
||||
|
||||
import cn.fateverse.notice.entity.Notice;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-05
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class NoticeVo {
|
||||
|
||||
private Long noticeId;
|
||||
|
||||
@ApiModelProperty("公告标题")
|
||||
private String noticeTitle;
|
||||
|
||||
@ApiModelProperty("公告类型(1通知 2公告)")
|
||||
private String noticeType;
|
||||
|
||||
@ApiModelProperty("发送类型,用户,用户数组,角色,部门,全发")
|
||||
private String sendType;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@ApiModelProperty("发送类型对应的信息")
|
||||
private List<String> senders;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String noticeContent;
|
||||
|
||||
@ApiModelProperty("内容类型: html,text等")
|
||||
private String contentType;
|
||||
|
||||
@ApiModelProperty("公告状态(0正常 1关闭)")
|
||||
private String state;
|
||||
|
||||
@ApiModelProperty("群组")
|
||||
private String cluster;
|
||||
|
||||
@ApiModelProperty("创建人")
|
||||
private Object createBy;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private Date createTime;
|
||||
|
||||
public static NoticeVo toNoticeVo(Notice notice){
|
||||
return NoticeVo.builder()
|
||||
.noticeId(notice.getNoticeId())
|
||||
.noticeTitle(notice.getNoticeTitle())
|
||||
.noticeType(notice.getNoticeType())
|
||||
.sendType(notice.getSendType())
|
||||
.noticeContent(notice.getNoticeContent())
|
||||
.contentType(notice.getContentType())
|
||||
.state(notice.getState())
|
||||
.cluster(notice.getCluster())
|
||||
.createBy(notice.getCreateBy())
|
||||
.createTime(notice.getCreateTime())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.fateverse.notice.entity.vo;
|
||||
|
||||
import cn.fateverse.notice.constant.NoticeConstant;
|
||||
import cn.fateverse.notice.entity.Notice;
|
||||
import cn.fateverse.notice.entity.NoticeMq;
|
||||
import cn.fateverse.notice.enums.ActionEnums;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-07
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class NotifyVo {
|
||||
|
||||
private Long noticeId;
|
||||
|
||||
@ApiModelProperty("公告标题")
|
||||
private String noticeTitle;
|
||||
|
||||
@ApiModelProperty("公告类型(1通知 2公告)")
|
||||
private String noticeType;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String noticeContent;
|
||||
|
||||
@ApiModelProperty("消息阅读状态")
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
private String state;
|
||||
|
||||
@JsonIgnore
|
||||
private ActionEnums action;
|
||||
|
||||
@ApiModelProperty("群组")
|
||||
private String cluster;
|
||||
|
||||
public static NotifyVo toNotifyVo(Notice notice) {
|
||||
return NotifyVo.builder()
|
||||
.noticeId(notice.getNoticeId())
|
||||
.noticeTitle(notice.getNoticeTitle())
|
||||
.noticeContent(notice.getNoticeContent())
|
||||
.state(notice.getState())
|
||||
.cluster(notice.getCluster())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static NotifyVo toNotifyVo(NoticeMq notice) {
|
||||
NotifyVo vo = NotifyVo.builder()
|
||||
.noticeId(notice.getNoticeId())
|
||||
.noticeTitle(notice.getNoticeTitle())
|
||||
.noticeType(notice.getNoticeType())
|
||||
.noticeContent(notice.getNoticeContent())
|
||||
.action(notice.getAction())
|
||||
.cluster(notice.getCluster())
|
||||
.build();
|
||||
if (notice.getAction().equals(ActionEnums.SEND)){
|
||||
vo.setState(NoticeConstant.NOT_READ);
|
||||
}
|
||||
return vo;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package cn.fateverse.notice.handler;
|
||||
|
||||
import cn.fateverse.notice.entity.NoticeMq;
|
||||
import cn.fateverse.notice.entity.UserInfo;
|
||||
import cn.fateverse.notice.entity.vo.NotifyVo;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelId;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
import io.netty.channel.group.DefaultChannelGroup;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 管道池
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-04-14
|
||||
*/
|
||||
public class ChannelHandlerPool {
|
||||
|
||||
public static final AttributeKey<UserInfo> USER_INFO = AttributeKey.valueOf("userInfo");
|
||||
|
||||
/**
|
||||
* channelGroup通道组
|
||||
*/
|
||||
private static final Map<String, ChannelGroup> CHANNEL_GROUP_CLUSTER = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 可以存储userId与ChannelId的映射表
|
||||
*/
|
||||
public static String channelUserKey = "notice:user:channel:";
|
||||
|
||||
/**
|
||||
* 添加管道
|
||||
*
|
||||
* @param channel 需要新增的管道
|
||||
*/
|
||||
public static void addChannel(Channel channel, String cluster) {
|
||||
ChannelGroup channelGroup = CHANNEL_GROUP_CLUSTER.get(cluster);
|
||||
if (null == channelGroup) {
|
||||
synchronized (CHANNEL_GROUP_CLUSTER) {
|
||||
channelGroup = CHANNEL_GROUP_CLUSTER.get(cluster);
|
||||
if (null == channelGroup) {
|
||||
channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
channelGroup.add(channel);
|
||||
CHANNEL_GROUP_CLUSTER.put(cluster, channelGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到管道
|
||||
*
|
||||
* @param channelId 管道id
|
||||
* @return 返回管道
|
||||
*/
|
||||
public static Channel getChannel(ChannelId channelId, String cluster) {
|
||||
ChannelGroup channelGroup = CHANNEL_GROUP_CLUSTER.get(cluster);
|
||||
if (null == channelGroup) {
|
||||
return null;
|
||||
}
|
||||
return channelGroup.find(channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件发送到
|
||||
*
|
||||
* @param predicate 筛选条件
|
||||
* @param notice 需要推送的消息
|
||||
* @return 推送结果
|
||||
*/
|
||||
public static boolean sendPredicateChannel(Predicate<Channel> predicate, NoticeMq notice) {
|
||||
ChannelGroup channelGroup = CHANNEL_GROUP_CLUSTER.get(notice.getCluster());
|
||||
if (null == channelGroup) {
|
||||
return true;
|
||||
}
|
||||
JSONObject send = getSendNotice(notice);
|
||||
channelGroup.writeAndFlush(ChannelHandlerPool.getText(send), predicate::test);
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JSONObject getSendNotice(NoticeMq notice) {
|
||||
NotifyVo notifyVo = NotifyVo.toNotifyVo(notice);
|
||||
JSONObject send = new JSONObject();
|
||||
send.put("type","notice");
|
||||
send.put("notice",notifyVo);
|
||||
return send;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除管道
|
||||
*
|
||||
* @param channel 需要移除的管道
|
||||
*/
|
||||
public static void removeChannel(Channel channel, String cluster) {
|
||||
ChannelGroup channelGroup = CHANNEL_GROUP_CLUSTER.get(cluster);
|
||||
if (null != channelGroup) {
|
||||
channelGroup.remove(channel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到redis中的key
|
||||
*
|
||||
* @param cluster 群组
|
||||
* @return 拼接完成的Redis key
|
||||
*/
|
||||
public static String getRedisKey(String cluster) {
|
||||
return ChannelHandlerPool.channelUserKey + cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为netty需要的文本类型对象
|
||||
*
|
||||
* @param object 需要转换的对象
|
||||
* @return netty文本对象
|
||||
*/
|
||||
public static TextWebSocketFrame getText(Object object) {
|
||||
return new TextWebSocketFrame(JSON.toJSONString(object));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.fateverse.notice.handler;
|
||||
|
||||
import cn.fateverse.notice.entity.DelayedTask;
|
||||
import cn.fateverse.notice.entity.UserInfo;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-07-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DelayQueueChannelAuth implements CommandLineRunner {
|
||||
|
||||
|
||||
private final DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
|
||||
|
||||
/**
|
||||
* 加入到延时队列中
|
||||
* @param task
|
||||
*/
|
||||
public void put(DelayedTask task) {
|
||||
log.error("加入延时任务:{}", task);
|
||||
delayQueue.put(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
Executors.newSingleThreadExecutor().execute(new Thread(this::executeThread));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 延时任务执行线程
|
||||
*/
|
||||
private void executeThread() {
|
||||
while (true) {
|
||||
try {
|
||||
DelayedTask task = delayQueue.take();
|
||||
Channel channel = task.getChannel();
|
||||
UserInfo userInfo = channel.attr(ChannelHandlerPool.USER_INFO).get();
|
||||
if (null == userInfo){
|
||||
channel.close();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package cn.fateverse.notice.handler;
|
||||
|
||||
import cn.fateverse.notice.entity.NoticeMq;
|
||||
import cn.fateverse.notice.entity.UserInfo;
|
||||
import cn.fateverse.notice.entity.vo.NotifyVo;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static cn.fateverse.notice.constant.NoticeConstant.*;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-04-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NoticeConsumeHandler {
|
||||
|
||||
/**
|
||||
* 消费公告
|
||||
*
|
||||
* @param notice 公告
|
||||
* @return 发送结果
|
||||
*/
|
||||
public boolean consumeNotice(NoticeMq notice) {
|
||||
switch (notice.getSendType()) {
|
||||
case USER:
|
||||
return sendUser(notice);
|
||||
case ROLE:
|
||||
return sendRole(notice);
|
||||
case DEPT:
|
||||
return sendDept(notice);
|
||||
case ALL:
|
||||
return sendAllUser(notice);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向用户发送
|
||||
*
|
||||
* @param notice 公告
|
||||
* @return 发送结果
|
||||
*/
|
||||
private boolean sendUser(NoticeMq notice) {
|
||||
//多个用户或者当前用户拥有多个连接时,判断当前用户是否处于活跃状态
|
||||
Predicate<Channel> predicate = channel -> {
|
||||
UserInfo userInfo = channel.attr(ChannelHandlerPool.USER_INFO).get();
|
||||
return notice.getSenderIds().contains(Long.valueOf(userInfo.getUserId()));
|
||||
};
|
||||
return ChannelHandlerPool.sendPredicateChannel(predicate, notice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向角色发送
|
||||
*
|
||||
* @param notice 公告
|
||||
* @return 发送结果
|
||||
*/
|
||||
private boolean sendRole(NoticeMq notice) {
|
||||
Predicate<Channel> predicate = channel -> {
|
||||
UserInfo userInfo = channel.attr(ChannelHandlerPool.USER_INFO).get();
|
||||
List<Long> senderIds = notice.getSenderIds();
|
||||
boolean flag = false;
|
||||
Set<Long> roleSet = userInfo.getRoleSet();
|
||||
for (Long senderId : senderIds) {
|
||||
flag = roleSet.contains(senderId);
|
||||
if (flag) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
return ChannelHandlerPool.sendPredicateChannel(predicate, notice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向部门发送
|
||||
*
|
||||
* @param notice 公告
|
||||
* @return 发送结果
|
||||
*/
|
||||
private boolean sendDept(NoticeMq notice) {
|
||||
Predicate<Channel> predicate = channel -> {
|
||||
UserInfo userInfo = channel.attr(ChannelHandlerPool.USER_INFO).get();
|
||||
boolean state = false;
|
||||
//查询当前用户部门是否匹配
|
||||
state = notice.getSenderIds().contains(userInfo.getDeptId());
|
||||
//匹配直接返回
|
||||
if (state) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
//不匹配则查询当前用户的组级id是否存在当前部门id,存在则返回
|
||||
for (Long senderId : notice.getSenderIds()) {
|
||||
state = userInfo.getDeptAncestors().contains(senderId);
|
||||
if (state) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
return ChannelHandlerPool.sendPredicateChannel(predicate, notice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向所有的用户发送
|
||||
*
|
||||
* @param notice 公告
|
||||
* @return 发送结果
|
||||
*/
|
||||
private boolean sendAllUser(NoticeMq notice) {
|
||||
Predicate<Channel> predicate = channel -> true;
|
||||
return ChannelHandlerPool.sendPredicateChannel(predicate, notice);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package cn.fateverse.notice.handler;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.fateverse.admin.entity.Role;
|
||||
import cn.fateverse.admin.entity.User;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
import cn.fateverse.notice.entity.DelayedTask;
|
||||
import cn.fateverse.notice.entity.SocketAuth;
|
||||
import cn.fateverse.notice.entity.UserInfo;
|
||||
import cn.fateverse.notice.mq.RabbitConfig;
|
||||
import cn.fateverse.common.security.entity.LoginUser;
|
||||
import cn.fateverse.common.security.service.TokenService;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-04-12
|
||||
*/
|
||||
@Slf4j
|
||||
@ChannelHandler.Sharable
|
||||
@Component
|
||||
public class NoticeSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, UserInfo> redisTemplate;
|
||||
|
||||
private final TokenService tokenService;
|
||||
|
||||
private final RabbitConfig rabbitConfig;
|
||||
|
||||
private final DelayQueueChannelAuth delayQueueChannelAuth;
|
||||
|
||||
public NoticeSocketServerHandler(TokenService tokenService, RabbitConfig rabbitConfig, DelayQueueChannelAuth delayQueueChannelAuth) {
|
||||
this.tokenService = tokenService;
|
||||
this.rabbitConfig = rabbitConfig;
|
||||
this.delayQueueChannelAuth = delayQueueChannelAuth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
delayQueueChannelAuth.put(new DelayedTask(ctx.channel(), 3000));
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
Channel channel = ctx.channel();
|
||||
if (!ctx.isRemoved()) {
|
||||
UserInfo info = channel.attr(ChannelHandlerPool.USER_INFO).get();
|
||||
if (null == info) {
|
||||
return;
|
||||
}
|
||||
redisTemplate.delete(ChannelHandlerPool.getRedisKey(info.getCluster()) + ":" + info.getRedisKey());
|
||||
log.info("用户被删除******************************");
|
||||
ChannelHandlerPool.removeChannel(channel, info.getCluster());
|
||||
log.info("用户退出");
|
||||
}
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理WebSocket事件,websocket传输只会进行授权,并且实现ping ping 的状态验证,防止nginx代理时自动断开连接
|
||||
*
|
||||
* @param ctx the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}
|
||||
* belongs to
|
||||
* @param frame the message to handle
|
||||
*/
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
|
||||
// 处理文本消息
|
||||
String text = frame.text();
|
||||
Channel channel = ctx.channel();
|
||||
JSONObject object;
|
||||
try {
|
||||
object = JSON.parseObject(text);
|
||||
} catch (Exception e) {
|
||||
object = new JSONObject();
|
||||
closeChannel(channel, object, "数据异常");
|
||||
return;
|
||||
}
|
||||
String type = object.getString("type");
|
||||
if (StrUtil.isBlank(type)) {
|
||||
closeChannel(channel, new JSONObject(), "类型为空");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
switch (type) {
|
||||
case "ping":
|
||||
writePong(channel);
|
||||
UserInfo userInfo = channel.attr(ChannelHandlerPool.USER_INFO).get();
|
||||
redisTemplate.expire(ChannelHandlerPool.getRedisKey(userInfo.getCluster()) + RedisConstant.REDIS_SEPARATOR + userInfo.getRedisKey(), 60, TimeUnit.SECONDS);
|
||||
return;
|
||||
case "auth":
|
||||
auth(text, channel);
|
||||
break;
|
||||
default:
|
||||
closeChannel(channel, object, "类型异常");
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.getMessage());
|
||||
closeChannel(channel, object, "数据异常");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void auth(String text, Channel channel) {
|
||||
SocketAuth auth = JSON.parseObject(text, SocketAuth.class);
|
||||
if (auth != null && !StrUtil.isEmpty(auth.getToken())) {
|
||||
//根据token获取到授权信息
|
||||
LoginUser loginUser = tokenService.getLoginUser(auth.getToken());
|
||||
if (null == loginUser) {
|
||||
closeChannel(channel, new JSONObject(), "授权失败");
|
||||
return;
|
||||
}
|
||||
//获取到用户信息
|
||||
User user = loginUser.getUser();
|
||||
String userId = user.getUserId().toString();
|
||||
String ip = "";
|
||||
try {
|
||||
ip = InetAddress.getLocalHost().getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
ip = "0.0.0.0";
|
||||
}
|
||||
String redisKey = ChannelHandlerPool.getRedisKey(auth.getCluster());
|
||||
String subKey = userId + RedisConstant.REDIS_SEPARATOR + ip + RedisConstant.REDIS_SEPARATOR + System.currentTimeMillis();
|
||||
//整理netty集群中需要的用户信息
|
||||
UserInfo info = UserInfo.builder()
|
||||
.userId(userId)
|
||||
.redisKey(subKey)
|
||||
.cluster(auth.getCluster())
|
||||
.routingKey(rabbitConfig.getRoutingKey())
|
||||
.build();
|
||||
//存放到redis中
|
||||
redisTemplate.opsForValue().set(redisKey + RedisConstant.REDIS_SEPARATOR + subKey, info, RedisConstant.REDIS_EXPIRE, TimeUnit.SECONDS);
|
||||
//将用户的角色和部门放入到info中,并设置到channel中,方便后续取用
|
||||
Set<Long> roleSet = user.getRoles().stream().map(Role::getRoleId).collect(Collectors.toSet());
|
||||
info.setRoleSet(roleSet);
|
||||
info.setDeptId(user.getDeptId());
|
||||
try {
|
||||
Set<Long> deptAncestors = Arrays.stream(user.getDept().getAncestors().trim().split(",")).map(Long::valueOf).collect(Collectors.toSet());
|
||||
info.setDeptAncestors(deptAncestors);
|
||||
} catch (Exception e) {
|
||||
log.error("部门祖级列表转换失败");
|
||||
info.setDeptAncestors(new HashSet<>());
|
||||
}
|
||||
//设置用户信息
|
||||
channel.attr(ChannelHandlerPool.USER_INFO).set(info);
|
||||
//将当前channel添加到channel池中
|
||||
ChannelHandlerPool.addChannel(channel, auth.getCluster());
|
||||
log.info("用户授权成功");
|
||||
}
|
||||
}
|
||||
|
||||
private void writePong(Channel channel) {
|
||||
String SEND_PONG = "{\"type\": \"pong\"}";
|
||||
channel.writeAndFlush(new TextWebSocketFrame(SEND_PONG));
|
||||
}
|
||||
|
||||
|
||||
private void closeChannel(Channel channel, JSONObject object, String msg) {
|
||||
object.put("type", "error");
|
||||
object.put("msg", msg);
|
||||
channel.writeAndFlush(ChannelHandlerPool.getText(object));
|
||||
channel.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.fateverse.notice.mapper;
|
||||
|
||||
import cn.fateverse.notice.entity.Notice;
|
||||
import cn.fateverse.notice.entity.query.NoticeQuery;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
public interface NoticeMapper {
|
||||
|
||||
/**
|
||||
* 查询公告
|
||||
*
|
||||
* @param noticeId 公告id
|
||||
* @param publishId 用户id
|
||||
* @return 公告对象
|
||||
*/
|
||||
Notice selectById(@Param("noticeId") Long noticeId, @Param("publishId") Long publishId);
|
||||
|
||||
/**
|
||||
* 查询公告 不携带内容
|
||||
*
|
||||
* @param noticeId 公告id
|
||||
* @param publishId 发布人id
|
||||
* @return 公告对象
|
||||
*/
|
||||
Notice selectSimpleById(@Param("noticeId") Long noticeId, @Param("publishId") Long publishId);
|
||||
|
||||
/**
|
||||
* 查询公告列表
|
||||
*
|
||||
* @param query 查询对象
|
||||
* @return 公告对象数组
|
||||
*/
|
||||
List<Notice> selectList(NoticeQuery query);
|
||||
|
||||
/**
|
||||
* 新增公告
|
||||
*
|
||||
* @param notice 公告对象
|
||||
* @return 影响对象
|
||||
*/
|
||||
int insert(Notice notice);
|
||||
|
||||
/**
|
||||
* 修改公告
|
||||
*
|
||||
* @param notice 公告对象
|
||||
* @return 影响对象
|
||||
*/
|
||||
int update(Notice notice);
|
||||
|
||||
/**
|
||||
* 修改公告状态
|
||||
*
|
||||
* @param notice 公告对象
|
||||
* @return 影响对象
|
||||
*/
|
||||
int changeState(Notice notice);
|
||||
|
||||
/**
|
||||
* 删除公告
|
||||
*
|
||||
* @param id 公告id
|
||||
* @return 影响对象
|
||||
*/
|
||||
int deleteById(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param idList id数组
|
||||
* @return 影响对象
|
||||
*/
|
||||
int batchDeleteByIdList(List<Long> idList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.fateverse.notice.mapper;
|
||||
|
||||
import cn.fateverse.notice.entity.Notice;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-07
|
||||
*/
|
||||
public interface NotifyMapper {
|
||||
/**
|
||||
* 获取到当前用户的公告列表
|
||||
*
|
||||
* @param cluster 群组
|
||||
* @param state 状态
|
||||
* @param userId 用户id
|
||||
* @return 消息列表
|
||||
*/
|
||||
List<Notice> selectNoticeList(@Param("cluster") String cluster, @Param("state") String state, @Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取到消息详情
|
||||
* @param noticeId
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
Notice selectNoticeByNoticeId(@Param("noticeId") Long noticeId,@Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package cn.fateverse.notice.mapper;
|
||||
|
||||
import cn.fateverse.notice.entity.UserNotice;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
public interface UserNoticeMapper {
|
||||
/**
|
||||
* 添加用户与公告之间的映射关系
|
||||
*
|
||||
* @param userNotice 映射对象
|
||||
* @return 影响数量
|
||||
*/
|
||||
int insert(UserNotice userNotice);
|
||||
|
||||
/**
|
||||
* 批量添加用户公告之间的映射关系
|
||||
*
|
||||
* @param list 映射关系
|
||||
* @return 影响数量
|
||||
*/
|
||||
int batchInsert(List<UserNotice> list);
|
||||
|
||||
/**
|
||||
* 用户全部阅读公告
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @return 影响结果
|
||||
*/
|
||||
int batchRead(Long userId);
|
||||
|
||||
/**
|
||||
* 阅读消息
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param noticeId 公告id
|
||||
* @return 影响结果
|
||||
*/
|
||||
int read(@Param("userId") Long userId, @Param("noticeId") Long noticeId);
|
||||
|
||||
/**
|
||||
* 用户删除公告
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param noticeId 公告id
|
||||
* @return 影响结果
|
||||
*/
|
||||
int delete(@Param("userId") Long userId, @Param("noticeId") Long noticeId);
|
||||
|
||||
/**
|
||||
* 用户批量删除公告
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param noticeIdList 公告列表
|
||||
* @return 影响结果
|
||||
*/
|
||||
int batchDelete(@Param("userId") Long userId, @Param("noticeIdList") List<Long> noticeIdList);
|
||||
|
||||
/**
|
||||
* 删除全部消息
|
||||
* @param userId 用户id
|
||||
* @return 删除结果
|
||||
*/
|
||||
int deleteAll(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 根据消息id删除消息公告
|
||||
*
|
||||
* @param noticeId 公告id
|
||||
* @return 影响结果
|
||||
*/
|
||||
int deleteByNoticeId(Long noticeId);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.fateverse.notice.mq;
|
||||
|
||||
import cn.fateverse.notice.entity.NoticeMq;
|
||||
import cn.fateverse.notice.handler.NoticeConsumeHandler;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.support.AmqpHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MQ 监听器
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-04-15
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RabbiListener {
|
||||
|
||||
/**
|
||||
* 最大重试次数
|
||||
*/
|
||||
private static final int MAX_RETRIES = 3;
|
||||
|
||||
@Resource
|
||||
private NoticeConsumeHandler consumeHandler;
|
||||
|
||||
@RabbitListener(queues = "#{queueChat.name}")
|
||||
public void consumeNotice(NoticeMq notice, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
|
||||
// 获取消息的重试次数
|
||||
log.info("**********************************");
|
||||
log.info(notice.toString());
|
||||
// 重试次数
|
||||
int retryCount = 0;
|
||||
boolean consumeStart = false;
|
||||
while (retryCount < MAX_RETRIES) {
|
||||
retryCount++;
|
||||
log.info("消费业务!");
|
||||
consumeStart = consumeHandler.consumeNotice(notice);
|
||||
if (consumeStart) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (consumeStart) {
|
||||
channel.basicAck(tag, false);
|
||||
} else {
|
||||
channel.basicNack(tag, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cn.fateverse.notice.mq;
|
||||
|
||||
import cn.fateverse.notice.config.NoticeProperties;
|
||||
import org.springframework.amqp.core.*;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Mq的相关配置
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2023-04-15
|
||||
*/
|
||||
@Configuration
|
||||
public class RabbitConfig {
|
||||
|
||||
@Resource
|
||||
private NoticeProperties properties;
|
||||
|
||||
/**
|
||||
* 交换机
|
||||
*
|
||||
* @return topic交换机
|
||||
*/
|
||||
@Bean
|
||||
public TopicExchange exchangeChat() {
|
||||
return new TopicExchange(properties.getExchangeChatRanch());
|
||||
}
|
||||
|
||||
/**
|
||||
* 队列
|
||||
*
|
||||
* @return 队列
|
||||
*/
|
||||
@Bean
|
||||
public Queue queueChat() {
|
||||
Map<String, Object> args = new HashMap<>(8);
|
||||
// args.put("x-expires", 60000);
|
||||
return new Queue(getKey(properties.getQueueChatRanch()), true, false, false, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到集群动态的key值,key值由ip加的端口组成
|
||||
*
|
||||
* @param key 路由或者交换机
|
||||
* @return 最总的key
|
||||
*/
|
||||
private String getKey(String key) {
|
||||
String ip = "";
|
||||
try {
|
||||
ip = InetAddress.getLocalHost().getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
ip = "0.0.0.0";
|
||||
}
|
||||
return key + "(" + ip + ":" + properties.getPort() + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取到路由的key
|
||||
*
|
||||
* @return 路由key
|
||||
*/
|
||||
public String getRoutingKey() {
|
||||
return getKey(properties.getRoutingKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前节点绑定的mq
|
||||
*
|
||||
* @param queueChat 队列
|
||||
* @param exchangeChat 交换机
|
||||
* @return 绑定结果
|
||||
*/
|
||||
@Bean
|
||||
public Binding binding(Queue queueChat, TopicExchange exchangeChat) {
|
||||
return BindingBuilder.bind(queueChat).to(exchangeChat).with(getRoutingKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播点绑定的mq
|
||||
*
|
||||
* @param queueChat 队列
|
||||
* @param exchangeChat 交换机
|
||||
* @return 绑定结果
|
||||
*/
|
||||
@Bean
|
||||
public Binding bindingAll(Queue queueChat, TopicExchange exchangeChat) {
|
||||
return BindingBuilder.bind(queueChat).to(exchangeChat).with(properties.getBroadRoutingKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package cn.fateverse.notice.netty;
|
||||
|
||||
import cn.fateverse.notice.config.NoticeProperties;
|
||||
import cn.fateverse.notice.handler.NoticeSocketServerHandler;
|
||||
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
|
||||
import com.alibaba.cloud.nacos.NacosServiceManager;
|
||||
import com.alibaba.cloud.nacos.registry.NacosRegistration;
|
||||
import com.alibaba.nacos.api.exception.NacosException;
|
||||
import com.alibaba.nacos.api.naming.NamingService;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-07
|
||||
*/
|
||||
@Slf4j
|
||||
@SuppressWarnings("all")
|
||||
public class NettyApplication implements ApplicationRunner {
|
||||
|
||||
@Autowired
|
||||
private NoticeSocketServerHandler noticeSocketServerHandler;
|
||||
@Autowired
|
||||
private NacosDiscoveryProperties nacosDiscoveryProperties;
|
||||
@Autowired
|
||||
private NacosServiceManager nacosServiceManager;
|
||||
@Autowired
|
||||
private NacosRegistration registration;
|
||||
@Autowired
|
||||
private NoticeProperties properties;
|
||||
|
||||
private NamingService namingService() {
|
||||
return this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());
|
||||
}
|
||||
|
||||
public void nettyRun() throws InterruptedException, NacosException {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup();
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
String group = this.nacosDiscoveryProperties.getGroup();
|
||||
String service = this.properties.getApplicationName();
|
||||
String host = registration.getHost();
|
||||
try {
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel channel) throws Exception {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast("Http 编码(HTTP 协议解析,用于握手阶段)", new HttpServerCodec());
|
||||
pipeline.addLast(new LoggingHandler(LogLevel.DEBUG));
|
||||
pipeline.addLast(new HttpObjectAggregator(100 * 1024 * 1024));
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
pipeline.addLast("WebSocket 数据压缩扩展", new WebSocketServerCompressionHandler());
|
||||
pipeline.addLast("WebSocket 握手 控制帧处理", new WebSocketServerProtocolHandler(properties.getPath()));
|
||||
pipeline.addLast(noticeSocketServerHandler);
|
||||
}
|
||||
});
|
||||
ChannelFuture channelFuture = serverBootstrap.bind(properties.getPort()).sync();
|
||||
log.info("Netty启动端口为:" + properties.getPort());
|
||||
namingService().registerInstance(service, group, host, properties.getPort());
|
||||
channelFuture.channel().closeFuture().sync();
|
||||
} finally {
|
||||
bossGroup.shutdownGracefully();
|
||||
workerGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
log.info("netty启动");
|
||||
try {
|
||||
nettyRun();
|
||||
} catch (InterruptedException | NacosException e) {
|
||||
e.printStackTrace();
|
||||
log.error("netty启动失败!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package cn.fateverse.notice.service;
|
||||
|
||||
import cn.fateverse.common.core.result.page.TableDataInfo;
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import cn.fateverse.notice.entity.query.NoticeQuery;
|
||||
import cn.fateverse.notice.entity.vo.NoticeVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
public interface NoticeService {
|
||||
|
||||
/**
|
||||
* 查询公告
|
||||
*
|
||||
* @param noticeId 公告id
|
||||
* @return 公告vo对象
|
||||
*/
|
||||
NoticeVo searchById(Long noticeId);
|
||||
|
||||
/**
|
||||
* 查询公告数组
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 返回对象集合
|
||||
*/
|
||||
TableDataInfo<NoticeVo> searchList(NoticeQuery query);
|
||||
|
||||
/**
|
||||
* 导出公告
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 返回对象集合
|
||||
*/
|
||||
List<NoticeVo> exportList(NoticeQuery query);
|
||||
|
||||
/**
|
||||
* 保存公告
|
||||
*
|
||||
* @param dto 传输对象
|
||||
*/
|
||||
void save(NoticeDto dto);
|
||||
|
||||
/**
|
||||
* 删除公告
|
||||
*
|
||||
* @param noticeId 公告id
|
||||
*/
|
||||
void removeById(Long noticeId);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.fateverse.notice.service;
|
||||
|
||||
import cn.fateverse.common.core.result.page.TableDataInfo;
|
||||
import cn.fateverse.notice.entity.vo.NotifyVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-07
|
||||
*/
|
||||
public interface NotifyService {
|
||||
/**
|
||||
* 查询用户个人的通告
|
||||
*
|
||||
* @param cluster 消息所在的群组
|
||||
* @param state 消息状态
|
||||
* @return 消息信息
|
||||
*/
|
||||
TableDataInfo<NotifyVo> searchList(String cluster, String state);
|
||||
|
||||
/**
|
||||
* 根据id查询当前通告
|
||||
*
|
||||
* @param noticeId 通告id
|
||||
* @return 消息信息
|
||||
*/
|
||||
NotifyVo searchById(Long noticeId);
|
||||
|
||||
/**
|
||||
* 阅读消息
|
||||
*
|
||||
* @param noticeId 消息di
|
||||
*/
|
||||
void read(Long noticeId);
|
||||
|
||||
/**
|
||||
* 一键阅读全部消息
|
||||
*/
|
||||
void readAll();
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
*
|
||||
* @param noticeId 消息id
|
||||
*/
|
||||
void remove(Long noticeId);
|
||||
|
||||
/**
|
||||
* 删除所有的消息
|
||||
*/
|
||||
void removeAll();
|
||||
|
||||
/**
|
||||
* 批量删除通知消息
|
||||
*
|
||||
* @param noticeIds 需要批量删除的消息
|
||||
*/
|
||||
void batchRemove(List<Long> noticeIds);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
package cn.fateverse.notice.service.impl;
|
||||
|
||||
import cn.fateverse.admin.dubbo.DubboDeptService;
|
||||
import cn.fateverse.admin.dubbo.DubboRoleService;
|
||||
import cn.fateverse.admin.dubbo.DubboUserService;
|
||||
import cn.fateverse.admin.vo.DeptVo;
|
||||
import cn.fateverse.admin.vo.UserVo;
|
||||
import cn.fateverse.common.core.exception.CustomException;
|
||||
import cn.fateverse.common.core.result.page.TableDataInfo;
|
||||
import cn.fateverse.common.core.utils.ObjectUtils;
|
||||
import cn.fateverse.common.redis.constant.RedisConstant;
|
||||
import cn.fateverse.common.mybatis.utils.PageUtils;
|
||||
import cn.fateverse.notice.config.NoticeProperties;
|
||||
import cn.fateverse.notice.constant.NoticeConstant;
|
||||
import cn.fateverse.notice.dto.NoticeDto;
|
||||
import cn.fateverse.notice.entity.Notice;
|
||||
import cn.fateverse.notice.entity.NoticeMq;
|
||||
import cn.fateverse.notice.entity.UserInfo;
|
||||
import cn.fateverse.notice.entity.UserNotice;
|
||||
import cn.fateverse.notice.entity.query.NoticeQuery;
|
||||
import cn.fateverse.notice.entity.vo.NoticeVo;
|
||||
import cn.fateverse.notice.enums.ActionEnums;
|
||||
import cn.fateverse.notice.mapper.NoticeMapper;
|
||||
import cn.fateverse.notice.mapper.UserNoticeMapper;
|
||||
import cn.fateverse.notice.handler.ChannelHandlerPool;
|
||||
import cn.fateverse.notice.service.NoticeService;
|
||||
import cn.fateverse.common.security.utils.SecurityUtils;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.fateverse.notice.constant.NoticeConstant.*;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-04
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class NoticeServiceImpl implements NoticeService {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, UserInfo> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private NoticeProperties properties;
|
||||
|
||||
private final RabbitTemplate rabbitTemplate;
|
||||
|
||||
private final NoticeMapper noticeMapper;
|
||||
|
||||
private final UserNoticeMapper userNoticeMapper;
|
||||
|
||||
@DubboReference
|
||||
private DubboUserService userService;
|
||||
|
||||
@DubboReference
|
||||
private DubboRoleService roleService;
|
||||
|
||||
@DubboReference
|
||||
private DubboDeptService deptService;
|
||||
|
||||
|
||||
public NoticeServiceImpl(RabbitTemplate rabbitTemplate, NoticeMapper noticeMapper,
|
||||
UserNoticeMapper userNoticeMapper) {
|
||||
this.rabbitTemplate = rabbitTemplate;
|
||||
this.noticeMapper = noticeMapper;
|
||||
this.userNoticeMapper = userNoticeMapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public NoticeVo searchById(Long noticeId) {
|
||||
Long publishId = adminOrUser();
|
||||
Notice notice = noticeMapper.selectById(noticeId, publishId);
|
||||
if (ObjectUtils.isEmpty(notice)) {
|
||||
return null;
|
||||
}
|
||||
NoticeVo vo = NoticeVo.toNoticeVo(notice);
|
||||
switch (vo.getSendType()) {
|
||||
case USER:
|
||||
userNotice(vo, notice);
|
||||
break;
|
||||
case ROLE:
|
||||
roleNotice(vo, notice);
|
||||
break;
|
||||
case DEPT:
|
||||
deptNotice(vo, notice);
|
||||
break;
|
||||
case ALL:
|
||||
vo.setSenders(Collections.singletonList("所有人"));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
private Long adminOrUser() {
|
||||
return SecurityUtils.isAdmin() ? null : SecurityUtils.getUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户通知
|
||||
*
|
||||
* @param vo 返回对象
|
||||
* @param notice 公告
|
||||
*/
|
||||
private void userNotice(NoticeVo vo, Notice notice) {
|
||||
List<Long> senderIds = checkSenderIds(notice);
|
||||
if (senderIds == null) {
|
||||
return;
|
||||
}
|
||||
List<UserVo> userList = userService.searchUserListByUserIds(senderIds);
|
||||
if (null == userList || userList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> userInfo = userList.stream().map(UserVo::getUserName).collect(Collectors.toList());
|
||||
vo.setSenders(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色通知
|
||||
*
|
||||
* @param vo 返回对象
|
||||
* @param notice 公告
|
||||
*/
|
||||
private void roleNotice(NoticeVo vo, Notice notice) {
|
||||
List<Long> senderIds = checkSenderIds(notice);
|
||||
if (senderIds == null) {
|
||||
return;
|
||||
}
|
||||
List<String> roleNames = roleService.searchRoleNameByIds(senderIds);
|
||||
vo.setSenders(roleNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门通知
|
||||
*
|
||||
* @param vo 返回对象
|
||||
* @param notice 公告
|
||||
*/
|
||||
private void deptNotice(NoticeVo vo, Notice notice) {
|
||||
List<Long> senderIds = checkSenderIds(notice);
|
||||
if (senderIds == null) {
|
||||
return;
|
||||
}
|
||||
List<DeptVo> deptList = deptService.searchDeptByDeptId(senderIds);
|
||||
if (null == deptList || deptList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> deptInfo = deptList.stream().map(DeptVo::getDeptName).collect(Collectors.toList());
|
||||
vo.setSenders(deptInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查发送人是否为空
|
||||
*
|
||||
* @param notice 公告
|
||||
* @return 发送对象的id
|
||||
*/
|
||||
private List<Long> checkSenderIds(Notice notice) {
|
||||
List<Long> senderIds = JSON.parseArray(notice.getSenderIds(), Long.class);
|
||||
if (null == senderIds || senderIds.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return senderIds;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TableDataInfo<NoticeVo> searchList(NoticeQuery query) {
|
||||
PageUtils.startPage();
|
||||
query.setPublishId(adminOrUser());
|
||||
List<Notice> list = noticeMapper.selectList(query);
|
||||
return PageUtils.convertDataTable(list, NoticeVo::toNoticeVo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NoticeVo> exportList(NoticeQuery query) {
|
||||
List<Notice> list = noticeMapper.selectList(query);
|
||||
return list.stream().map(NoticeVo::toNoticeVo).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void save(NoticeDto dto) {
|
||||
List<Long> userIds = null;
|
||||
//根据发送类型进行判断,然后获取到对应的用户id
|
||||
switch (dto.getSendType()) {
|
||||
case USER:
|
||||
userIds = dto.getSenderIds();
|
||||
break;
|
||||
case ROLE:
|
||||
case DEPT:
|
||||
List<UserVo> userVoList = ROLE.equals(dto.getSendType())
|
||||
? userService.searchUserListByRoleIds(dto.getSenderIds())
|
||||
: userService.searchUserByDeptIds(dto.getSenderIds());
|
||||
userIds = userVoList.stream().map(UserVo::getUserId).collect(Collectors.toList());
|
||||
break;
|
||||
case ALL:
|
||||
userIds = userService.searchAllUserIds();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
//如果用户id列表为空,则说明发送异常
|
||||
if (null == userIds || userIds.isEmpty()) {
|
||||
throw new CustomException("获取到用户id为空");
|
||||
}
|
||||
//将dto转换为数据库映射对象
|
||||
Notice notice = Notice.toNoticeMq(dto);
|
||||
Date date = new Date();
|
||||
//数据入库
|
||||
noticeMapper.insert(notice);
|
||||
//组装用户和消息的映射表
|
||||
List<UserNotice> userNoticeList = userIds.stream().map(userId ->
|
||||
UserNotice.builder()
|
||||
.userId(userId)
|
||||
.noticeId(notice.getNoticeId())
|
||||
.state(NoticeConstant.NOT_READ)
|
||||
.createTime(date)
|
||||
.build()
|
||||
).collect(Collectors.toList());
|
||||
//创建mq发送对象
|
||||
NoticeMq mq = new NoticeMq();
|
||||
BeanUtils.copyProperties(dto, mq);
|
||||
//mq中id回传
|
||||
mq.setNoticeId(notice.getNoticeId());
|
||||
//设置mq的动作为发送
|
||||
mq.setAction(ActionEnums.SEND);
|
||||
//判断当前需要发送的用户映射信息是否只有一条数据
|
||||
if (userNoticeList.size() == 1) {
|
||||
//获取到当前映射数据
|
||||
UserNotice userNotice = userNoticeList.get(0);
|
||||
//入库
|
||||
userNoticeMapper.insert(userNotice);
|
||||
//"userId:ip:time"
|
||||
//在Redis中过滤当前用户
|
||||
List<String> keys = new ArrayList<>();
|
||||
try (Cursor<String> cursor = redisTemplate.scan(ScanOptions.scanOptions().match(ChannelHandlerPool.getRedisKey(mq.getCluster()) + RedisConstant.REDIS_SEPARATOR + userNotice.getUserId() + RedisConstant.REDIS_SEPARATOR + "*").build());) {
|
||||
while (cursor.hasNext()) {
|
||||
keys.add(cursor.next());
|
||||
}
|
||||
}
|
||||
//如果的当前用户只存在一个连接,直接发送当当前用户的队列之中
|
||||
if (keys.size() == 1) {
|
||||
String userKey = keys.get(0);
|
||||
UserInfo userInfo = redisTemplate.opsForValue().get(userKey);
|
||||
if (null != userInfo) {
|
||||
boolean state = Boolean.TRUE.equals(rabbitTemplate.invoke(operations -> {
|
||||
rabbitTemplate.convertAndSend(properties.getExchangeChatRanch(), userInfo.getRoutingKey(), mq);
|
||||
return rabbitTemplate.waitForConfirms(5000);
|
||||
}));
|
||||
if (!state) {
|
||||
log.error("mq消息发送失败!");
|
||||
throw new CustomException("消息推送失败");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendNoticeBroadMq(mq);
|
||||
}
|
||||
} else {
|
||||
//多个用户则使用广播的方式进行发送,并且将映射关系批量添加到数据库
|
||||
userNoticeMapper.batchInsert(userNoticeList);
|
||||
sendNoticeBroadMq(mq);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void removeById(Long noticeId) {
|
||||
Notice notice = noticeMapper.selectById(noticeId, adminOrUser());
|
||||
if (ObjectUtils.isEmpty(notice)) {
|
||||
throw new CustomException("公告不存在");
|
||||
}
|
||||
//数据库中删除信息
|
||||
noticeMapper.deleteById(notice.getNoticeId());
|
||||
userNoticeMapper.deleteByNoticeId(noticeId);
|
||||
//整理mq消息
|
||||
NoticeMq mq = notice.toNoticeMq();
|
||||
//设置动作为删除
|
||||
mq.setAction(ActionEnums.REMOVE);
|
||||
//发送删除消息到mq
|
||||
sendNoticeBroadMq(mq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播的方式发送公告
|
||||
*
|
||||
* @param mq 消息内容
|
||||
*/
|
||||
private void sendNoticeBroadMq(NoticeMq mq) {
|
||||
rabbitTemplate.setMandatory(true);
|
||||
boolean sendStart = Boolean.TRUE.equals(rabbitTemplate.invoke(operations -> {
|
||||
rabbitTemplate.convertAndSend(properties.getExchangeChatRanch(), properties.getRoutingKey() + "broad", mq);
|
||||
return rabbitTemplate.waitForConfirms(5000);
|
||||
}));
|
||||
if (!sendStart) {
|
||||
log.error("mq消息发送失败!");
|
||||
throw new CustomException("消息推送失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.fateverse.notice.service.impl;
|
||||
|
||||
import cn.fateverse.common.core.result.page.TableDataInfo;
|
||||
import cn.fateverse.common.mybatis.utils.PageUtils;
|
||||
import cn.fateverse.notice.constant.NoticeConstant;
|
||||
import cn.fateverse.notice.entity.Notice;
|
||||
import cn.fateverse.notice.entity.vo.NotifyVo;
|
||||
import cn.fateverse.notice.mapper.NotifyMapper;
|
||||
import cn.fateverse.notice.mapper.UserNoticeMapper;
|
||||
import cn.fateverse.notice.service.NotifyService;
|
||||
import cn.fateverse.common.security.utils.SecurityUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-07
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class NotifyServiceImpl implements NotifyService {
|
||||
|
||||
private final UserNoticeMapper userNoticeMapper;
|
||||
|
||||
private final NotifyMapper notifyMapper;
|
||||
|
||||
public NotifyServiceImpl(NotifyMapper notifyMapper,
|
||||
UserNoticeMapper userNoticeMapper) {
|
||||
this.notifyMapper = notifyMapper;
|
||||
this.userNoticeMapper = userNoticeMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableDataInfo<NotifyVo> searchList(String cluster, String state) {
|
||||
PageUtils.startPage();
|
||||
List<Notice> list = notifyMapper.selectNoticeList(cluster, state, SecurityUtils.getUserId());
|
||||
return PageUtils.convertDataTable(list, NotifyVo::toNotifyVo);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public NotifyVo searchById(Long noticeId) {
|
||||
Notice notice = notifyMapper.selectNoticeByNoticeId(noticeId, SecurityUtils.getUserId());
|
||||
if (null == notice) {
|
||||
return null;
|
||||
}
|
||||
if (NoticeConstant.NOT_READ.equals(notice.getState())) {
|
||||
userNoticeMapper.read(SecurityUtils.getUserId(), noticeId);
|
||||
}
|
||||
return NotifyVo.toNotifyVo(notice);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void read(Long noticeId) {
|
||||
userNoticeMapper.read(SecurityUtils.getUserId(), noticeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void readAll() {
|
||||
userNoticeMapper.batchRead(SecurityUtils.getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void remove(Long noticeId) {
|
||||
userNoticeMapper.delete(SecurityUtils.getUserId(), noticeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void removeAll() {
|
||||
userNoticeMapper.deleteAll(SecurityUtils.getUserId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
|
||||
public void batchRemove(List<Long> noticeIds) {
|
||||
userNoticeMapper.batchDelete(SecurityUtils.getUserId(), noticeIds);
|
||||
}
|
||||
|
||||
}
|
||||
13
notice/notice-biz/src/main/resources/bootstrap-dev.yml
Normal file
13
notice/notice-biz/src/main/resources/bootstrap-dev.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
# Spring
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
# 服务注册地址
|
||||
server-addr: 10.7.127.185:38848
|
||||
namespace: clay
|
||||
|
||||
dubbo:
|
||||
registry:
|
||||
parameters:
|
||||
namespace: dubbo-clay
|
||||
11
notice/notice-biz/src/main/resources/bootstrap-pro.yml
Normal file
11
notice/notice-biz/src/main/resources/bootstrap-pro.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Spring
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
# 服务注册地址
|
||||
server-addr: nacos.fateverse.svc.cluster.local:8848
|
||||
|
||||
management:
|
||||
server:
|
||||
port: 9595
|
||||
41
notice/notice-biz/src/main/resources/bootstrap.yml
Normal file
41
notice/notice-biz/src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
# Tomcat
|
||||
server:
|
||||
port: 8090
|
||||
|
||||
# Spring
|
||||
spring:
|
||||
application:
|
||||
# 应用名称
|
||||
name: notice
|
||||
profiles:
|
||||
# 环境配置
|
||||
active: dev
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
# 服务注册地址
|
||||
server-addr: 192.168.101.108:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
namespace: ${spring.profiles.active}
|
||||
config:
|
||||
# 配置中心地址
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: ${spring.profiles.active}
|
||||
shared-configs:
|
||||
- data-id: application-${spring.profiles.active}.yaml
|
||||
refresh: true
|
||||
|
||||
dubbo:
|
||||
application:
|
||||
name: dubbo-${spring.application.name}
|
||||
protocol:
|
||||
name: dubbo
|
||||
port: -1
|
||||
registry:
|
||||
address: nacos://${spring.cloud.nacos.discovery.server-addr}
|
||||
username: ${spring.cloud.nacos.discovery.username}
|
||||
password: ${spring.cloud.nacos.discovery.password}
|
||||
parameters:
|
||||
namespace: dubbo-${spring.profiles.active}
|
||||
107
notice/notice-biz/src/main/resources/mapper/NoticeMapper.xml
Normal file
107
notice/notice-biz/src/main/resources/mapper/NoticeMapper.xml
Normal file
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.fateverse.notice.mapper.NoticeMapper">
|
||||
|
||||
<sql id="noticeDetails">
|
||||
select notice_id, notice_title, notice_type, send_type, sender_ids, notice_content, content_type, state, cluster, create_by, create_time, update_by, update_time, remark
|
||||
from sys_notice
|
||||
</sql>
|
||||
|
||||
<sql id="noticeVo">
|
||||
select notice_id, notice_title, notice_type, send_type, sender_ids, content_type, state, cluster, create_by, create_time, update_by, update_time, remark
|
||||
from sys_notice
|
||||
</sql>
|
||||
|
||||
<select id="selectById" resultType="cn.fateverse.notice.entity.Notice">
|
||||
<include refid="noticeDetails"/>
|
||||
<where>
|
||||
<if test="publishId != null">and publish_id = #{publishId}</if>
|
||||
<if test="noticeId != null">and notice_id = #{noticeId}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectSimpleById" resultType="cn.fateverse.notice.entity.Notice">
|
||||
<include refid="noticeVo"/>
|
||||
<where>
|
||||
<if test="publishId != null">and publish_id = #{publishId}</if>
|
||||
<if test="noticeId != null">and notice_id = #{noticeId}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectList" resultType="cn.fateverse.notice.entity.Notice">
|
||||
<include refid="noticeVo"/>
|
||||
<where>
|
||||
<if test="noticeTitle != null and noticeTitle != ''">and notice_title like concat('%',#{noticeTitle},'%')</if>
|
||||
<if test="noticeType != null and noticeType != ''">and notice_type like concat('%',#{noticeType},'%')</if>
|
||||
<if test="sendType != null and sendType != ''">and send_type = #{sendType}</if>
|
||||
<if test="contentType != null and contentType != ''">and content_type = #{contentType}</if>
|
||||
<if test="state != null and state != ''">and state = #{state}</if>
|
||||
<if test="cluster != null and cluster != ''">and cluster = #{cluster}</if>
|
||||
<if test="publishId != null">and publish_id = #{publishId}</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="noticeId">
|
||||
insert into sys_notice
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="noticeId != null"> notice_id,</if>
|
||||
<if test="noticeTitle != null and noticeTitle != ''"> notice_title,</if>
|
||||
<if test="noticeType != null and noticeType != ''"> notice_type,</if>
|
||||
<if test="sendType != null and sendType != ''"> send_type,</if>
|
||||
<if test="senderIds != null and senderIds != ''"> sender_ids,</if>
|
||||
<if test="publishId != null and publishId != ''"> publish_id,</if>
|
||||
<if test="noticeContent != null and noticeContent != ''"> notice_content,</if>
|
||||
<if test="contentType != null and contentType != ''"> content_type,</if>
|
||||
<if test="state != null">state,</if>
|
||||
<if test="cluster != null">cluster,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="noticeId != null"> #{noticeId},</if>
|
||||
<if test="noticeTitle != null and noticeTitle != ''"> #{noticeTitle},</if>
|
||||
<if test="noticeType != null and noticeType != ''"> #{noticeType},</if>
|
||||
<if test="sendType != null and sendType != ''"> #{sendType},</if>
|
||||
<if test="senderIds != null and senderIds != ''"> #{senderIds},</if>
|
||||
<if test="publishId != null and publishId != ''"> #{publishId},</if>
|
||||
<if test="noticeContent != null and noticeContent != ''"> #{noticeContent},</if>
|
||||
<if test="contentType != null and contentType != ''"> #{contentType},</if>
|
||||
<if test="state != null">#{state},</if>
|
||||
<if test="cluster != null">#{cluster},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
update sys_user_notice
|
||||
<set>
|
||||
<if test="state != null">state = #{state},</if>
|
||||
</set>
|
||||
where notice_id = #{noticeId}
|
||||
</update>
|
||||
|
||||
<update id="changeState">
|
||||
update sys_user_notice
|
||||
<set>
|
||||
<if test="state != null">state = #{state},</if>
|
||||
</set>
|
||||
where notice_id = #{noticeId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteById">
|
||||
delete from sys_notice where notice_id = #{noticeId}
|
||||
</delete>
|
||||
|
||||
<delete id="batchDeleteByIdList">
|
||||
delete from sys_notice where notice_id in
|
||||
<foreach collection="list" open="(" separator="," close=")" item="noticeId">
|
||||
#{noticeId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
|
||||
</mapper>
|
||||
28
notice/notice-biz/src/main/resources/mapper/NotifyMapper.xml
Normal file
28
notice/notice-biz/src/main/resources/mapper/NotifyMapper.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.fateverse.notice.mapper.NotifyMapper">
|
||||
|
||||
<select id="selectNoticeList" resultType="cn.fateverse.notice.entity.Notice">
|
||||
select sn.notice_id, sn.notice_title, sn.notice_type, sn.send_type, sn.sender_ids, sn.content_type, sn.cluster, sn.remark,
|
||||
sun.state
|
||||
from sys_notice sn
|
||||
left join sys_user_notice sun on sn.notice_id = sun.notice_id
|
||||
<where>
|
||||
and sn.state = '1'
|
||||
<if test="userId !=null"> and sun.user_id = #{userId}</if>
|
||||
<if test="cluster !=null"> and sn.cluster = #{cluster}</if>
|
||||
<if test="state !=null"> and sun.state = #{state}</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="selectNoticeByNoticeId" resultType="cn.fateverse.notice.entity.Notice">
|
||||
select sn.notice_id, sn.notice_title, sn.notice_type, sn.notice_content, sn.send_type, sn.sender_ids, sn.content_type, sn.cluster, sn.remark,
|
||||
sun.state
|
||||
from sys_notice sn
|
||||
left join sys_user_notice sun on sn.notice_id = sun.notice_id
|
||||
where sn.state = '1' and sn.notice_id = #{noticeId} and sun.user_id = #{userId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.fateverse.notice.mapper.UserNoticeMapper">
|
||||
|
||||
<insert id="insert">
|
||||
insert into sys_user_notice
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="noticeId != null">notice_id,</if>
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="state != null">state,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="noticeId != null">#{noticeId},</if>
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="state != null">#{state},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
<insert id="batchInsert">
|
||||
insert into sys_user_notice (notice_id,
|
||||
user_id,
|
||||
state,
|
||||
create_time)
|
||||
values
|
||||
<foreach collection="list" index="index" separator="," item="notice">
|
||||
(#{notice.noticeId},#{notice.userId},#{notice.state},#{notice.createTime})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<update id="batchRead">
|
||||
update sys_user_notice
|
||||
set state = '1'
|
||||
where user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<update id="read">
|
||||
update sys_user_notice
|
||||
set state = '1'
|
||||
where user_id = #{userId}
|
||||
and notice_id = #{noticeId}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
delete
|
||||
from sys_user_notice
|
||||
where user_id = #{userId}
|
||||
and notice_id = #{noticeId}
|
||||
</delete>
|
||||
|
||||
<delete id="batchDelete">
|
||||
delete from sys_user_notice where user_id = #{userId} and notice_id in
|
||||
<foreach collection="noticeIdList" open="(" separator="," close=")" item="noticeId">
|
||||
#{noticeId}
|
||||
</foreach>
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByNoticeId">
|
||||
delete
|
||||
from sys_user_notice
|
||||
where notice_id = #{noticeId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteAll">
|
||||
delete
|
||||
from sys_user_notice
|
||||
where user_id = #{userId}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
25
notice/pom.xml
Normal file
25
notice/pom.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>fateverse</artifactId>
|
||||
<groupId>cn.fateverse</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>notice</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>notice-api</module>
|
||||
<module>notice-biz</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user