This commit is contained in:
clay
2024-03-06 17:44:09 +08:00
commit adaec0eadd
1493 changed files with 219939 additions and 0 deletions

26
notice/notice-api/pom.xml Normal file
View 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>

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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
View 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>

View File

@@ -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 服务启动成功");
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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启动失败!");
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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("消息推送失败");
}
}
}

View File

@@ -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);
}
}

View 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

View File

@@ -0,0 +1,11 @@
# Spring
spring:
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: nacos.fateverse.svc.cluster.local:8848
management:
server:
port: 9595

View 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}

View 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>

View 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>

View File

@@ -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
View 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>