消息通知系统
This commit is contained in:
@@ -1,133 +1,134 @@
|
||||
package com.ai.da.common.security.filter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.ai.da.common.context.UserContext;
|
||||
import com.ai.da.common.security.config.SecurityProperties;
|
||||
import com.ai.da.common.security.jwt.JWTTokenHelper;
|
||||
import com.ai.da.common.utils.LocalCacheUtils;
|
||||
import com.ai.da.common.utils.MultiReadHttpServletRequest;
|
||||
import com.ai.da.common.utils.MultiReadHttpServletResponse;
|
||||
import com.ai.da.common.utils.RequestInfoUtil;
|
||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.security.sasl.AuthenticationException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: dangweijian
|
||||
* @description: 认证拦截器
|
||||
* @create: 2020-07-10 16:50
|
||||
**/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class AuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Resource
|
||||
private JWTTokenHelper jwtTokenHelper;
|
||||
@Resource
|
||||
private SecurityProperties properties;
|
||||
|
||||
private static final List<String> FILTER_URL =
|
||||
Arrays.asList("/favicon.ico", "/doc.html", "api/account/login", "api/account/preLogin", "api/account/sendEmail","api/account/noLoginRequired",
|
||||
"/webjars/", "/swagger-resources", "/v2/api-docs", "api/account/resetPwd",
|
||||
"/api/python/saveGeneratePicture", "/api/python/getLibraryByUserId",
|
||||
"/api/third/party/addUser","/api/third/party/addTrialUser", "/api/third/party/editUser", "/api/element/initDefaultSysFile",
|
||||
"/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew",
|
||||
"/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl",
|
||||
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back","/api/alipay-hk/trade/notify",
|
||||
"/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease",
|
||||
"/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify"
|
||||
);
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
String requestURI = httpServletRequest.getRequestURI();
|
||||
|
||||
if (calculateUrl(requestURI) || hasAuthorizationToken(httpServletRequest)) {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
HttpServletRequest wrappedRequest = httpServletRequest;
|
||||
HttpServletResponse wrappedResponse = httpServletResponse;
|
||||
try {
|
||||
stopWatch.start();
|
||||
if ((httpServletRequest.getContentType() == null && httpServletRequest.getContentLength() > 0) || (httpServletRequest.getContentType() != null && !httpServletRequest.getContentType().contains("application/json"))) {
|
||||
extracted(wrappedRequest);
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
} else {
|
||||
wrappedRequest = new MultiReadHttpServletRequest(httpServletRequest);
|
||||
wrappedResponse = new MultiReadHttpServletResponse(httpServletResponse);
|
||||
extracted(wrappedRequest);
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SecurityContextHolder.clearContext();
|
||||
throw e;
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
}
|
||||
} else {
|
||||
filterChain.doFilter(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean calculateUrl(String requestURI) {
|
||||
String filterUrl = FILTER_URL.stream().filter(url -> requestURI.contains(url)).findFirst().orElse(null);
|
||||
return null == filterUrl ? Boolean.TRUE : Boolean.FALSE;
|
||||
}
|
||||
|
||||
private boolean hasAuthorizationToken(HttpServletRequest request) {
|
||||
String authorizationHeader = request.getHeader("Authorization");
|
||||
return authorizationHeader != null && authorizationHeader.startsWith("Bearer");
|
||||
}
|
||||
|
||||
private void extracted(HttpServletRequest request) throws AuthenticationException {
|
||||
String jwtToken = request.getHeader(properties.getJwtTokenHeader());
|
||||
// log.debug("后台检查令牌:{}", jwtToken);
|
||||
|
||||
if (StrUtil.isBlank(jwtToken)) {
|
||||
String ipAddress = RequestInfoUtil.getIpAddress(request);
|
||||
log.info("本次请求的ip为 : " + ipAddress);
|
||||
throw new RuntimeException("请传入token!");
|
||||
}
|
||||
if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
|
||||
//写死 暂时放行
|
||||
return;
|
||||
}
|
||||
// 检查token
|
||||
boolean validate = jwtTokenHelper.validateToken(jwtToken);
|
||||
if (validate) {
|
||||
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(jwtToken);
|
||||
if (principal == null) {
|
||||
throw new RuntimeException("TOKEN已过期,请重新登录!");
|
||||
}
|
||||
//先清空当前线程变量,防止上一个线程遗留
|
||||
UserContext.delete();
|
||||
//存取用户信息到缓存
|
||||
UserContext.setUserHolder(principal);
|
||||
//校验token
|
||||
String cacheToken = LocalCacheUtils.getTokenCache(String.valueOf(principal.getId()));
|
||||
|
||||
if(StringUtils.isEmpty(cacheToken)){
|
||||
throw new RuntimeException("TOKEN已过期,请重新登录!");
|
||||
}
|
||||
if(!cacheToken.equals(jwtToken) ){
|
||||
throw new RuntimeException("TOKEN已过期,请重新登录!");
|
||||
}
|
||||
// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(null, null);
|
||||
// SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.ai.da.common.security.filter;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.ai.da.common.context.UserContext;
|
||||
import com.ai.da.common.security.config.SecurityProperties;
|
||||
import com.ai.da.common.security.jwt.JWTTokenHelper;
|
||||
import com.ai.da.common.utils.LocalCacheUtils;
|
||||
import com.ai.da.common.utils.MultiReadHttpServletRequest;
|
||||
import com.ai.da.common.utils.MultiReadHttpServletResponse;
|
||||
import com.ai.da.common.utils.RequestInfoUtil;
|
||||
import com.ai.da.model.vo.AuthPrincipalVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.security.sasl.AuthenticationException;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author: dangweijian
|
||||
* @description: 认证拦截器
|
||||
* @create: 2020-07-10 16:50
|
||||
**/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class AuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Resource
|
||||
private JWTTokenHelper jwtTokenHelper;
|
||||
@Resource
|
||||
private SecurityProperties properties;
|
||||
|
||||
private static final List<String> FILTER_URL =
|
||||
Arrays.asList("/favicon.ico", "/doc.html", "api/account/login", "api/account/preLogin", "api/account/sendEmail","api/account/noLoginRequired",
|
||||
"/webjars/", "/swagger-resources", "/v2/api-docs", "api/account/resetPwd",
|
||||
"/api/python/saveGeneratePicture", "/api/python/getLibraryByUserId",
|
||||
"/api/third/party/addUser","/api/third/party/addTrialUser", "/api/third/party/editUser", "/api/element/initDefaultSysFile",
|
||||
"/api/third/party/addNoLoginRequiredNew","/api/third/party/deleteNoLoginRequiredNew",
|
||||
"/api/third/party/existNoLoginRequired","/api/third/party/getRedirectUrl",
|
||||
"/api/python/flush","/api/account/healthy","/api/ali-pay/trade/notify","/api/paypal/ipn/back","/api/alipay-hk/trade/notify",
|
||||
"/api/portfolio/page", "/api/portfolio/detail", "/api/portfolio/commentPage", "/api/portfolio/viewsIncrease",
|
||||
"/api/account/designWorksRegister","/api/account/questionnaire","/api/stripe/trade/notify",
|
||||
"/notification"
|
||||
);
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
String requestURI = httpServletRequest.getRequestURI();
|
||||
|
||||
if (calculateUrl(requestURI) || hasAuthorizationToken(httpServletRequest)) {
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
HttpServletRequest wrappedRequest = httpServletRequest;
|
||||
HttpServletResponse wrappedResponse = httpServletResponse;
|
||||
try {
|
||||
stopWatch.start();
|
||||
if ((httpServletRequest.getContentType() == null && httpServletRequest.getContentLength() > 0) || (httpServletRequest.getContentType() != null && !httpServletRequest.getContentType().contains("application/json"))) {
|
||||
extracted(wrappedRequest);
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
} else {
|
||||
wrappedRequest = new MultiReadHttpServletRequest(httpServletRequest);
|
||||
wrappedResponse = new MultiReadHttpServletResponse(httpServletResponse);
|
||||
extracted(wrappedRequest);
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SecurityContextHolder.clearContext();
|
||||
throw e;
|
||||
} finally {
|
||||
stopWatch.stop();
|
||||
}
|
||||
} else {
|
||||
filterChain.doFilter(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean calculateUrl(String requestURI) {
|
||||
String filterUrl = FILTER_URL.stream().filter(url -> requestURI.contains(url)).findFirst().orElse(null);
|
||||
return null == filterUrl ? Boolean.TRUE : Boolean.FALSE;
|
||||
}
|
||||
|
||||
private boolean hasAuthorizationToken(HttpServletRequest request) {
|
||||
String authorizationHeader = request.getHeader("Authorization");
|
||||
return authorizationHeader != null && authorizationHeader.startsWith("Bearer");
|
||||
}
|
||||
|
||||
private void extracted(HttpServletRequest request) throws AuthenticationException {
|
||||
String jwtToken = request.getHeader(properties.getJwtTokenHeader());
|
||||
// log.debug("后台检查令牌:{}", jwtToken);
|
||||
|
||||
if (StrUtil.isBlank(jwtToken)) {
|
||||
String ipAddress = RequestInfoUtil.getIpAddress(request);
|
||||
log.info("本次请求的ip为 : " + ipAddress);
|
||||
throw new RuntimeException("请传入token!");
|
||||
}
|
||||
if(jwtToken.equals("Bearer-eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiIyIiwic3ViIjoie1wiaWRcIjoyLFwidXNlcm5hbWVcIjpcImxpcnNcIn0iLCJpYXQiOjE2NjU3NDEwODcsImlzcyI6IkRXSiIsImF1dGhvcml0aWVzIjoiW10iLCJleHAiOjE2NzQzODEwODd9.ShM9R_NNFD7oo1OvxrEgg7PFeWinOuAKkuInUCMQupp66s64Hhv8tN0Wwr83nIN4rHPqtn95wmd4msWcvaFYJA")){
|
||||
//写死 暂时放行
|
||||
return;
|
||||
}
|
||||
// 检查token
|
||||
boolean validate = jwtTokenHelper.validateToken(jwtToken);
|
||||
if (validate) {
|
||||
AuthPrincipalVo principal = jwtTokenHelper.parserToUser(jwtToken);
|
||||
if (principal == null) {
|
||||
throw new RuntimeException("TOKEN已过期,请重新登录!");
|
||||
}
|
||||
//先清空当前线程变量,防止上一个线程遗留
|
||||
UserContext.delete();
|
||||
//存取用户信息到缓存
|
||||
UserContext.setUserHolder(principal);
|
||||
//校验token
|
||||
String cacheToken = LocalCacheUtils.getTokenCache(String.valueOf(principal.getId()));
|
||||
|
||||
if(StringUtils.isEmpty(cacheToken)){
|
||||
throw new RuntimeException("TOKEN已过期,请重新登录!");
|
||||
}
|
||||
if(!cacheToken.equals(jwtToken) ){
|
||||
throw new RuntimeException("TOKEN已过期,请重新登录!");
|
||||
}
|
||||
// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(null, null);
|
||||
// SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ai.da.common.websocket;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.websocket.*;
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
@ServerEndpoint(value = "/notification")
|
||||
@Component
|
||||
@Slf4j
|
||||
public class NotificationConnection {
|
||||
|
||||
static Map<String,Session> sessionMap = new ConcurrentHashMap<>();
|
||||
//连接建立时执行的操作
|
||||
@OnOpen
|
||||
public void onOpen(Session session){
|
||||
sessionMap.put(session.getId(),session);
|
||||
log.info("websocket is open");
|
||||
}
|
||||
//收到了客户端消息执行的操作
|
||||
@OnMessage
|
||||
public void onMessage(String text){
|
||||
log.info("收到了一条消息:"+text);
|
||||
// return "已收到你的消息";
|
||||
}
|
||||
//连接关闭的时候执行的操作
|
||||
@OnClose
|
||||
public void onClose(Session session){
|
||||
sessionMap.remove(session.getId());
|
||||
log.info("websocket is close");
|
||||
}
|
||||
|
||||
public void sendMsg(String message) throws IOException {
|
||||
for(String key:sessionMap.keySet()){
|
||||
sessionMap.get(key).getBasicRemote().sendText(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.ai.da.common.websocket.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
|
||||
|
||||
/**
|
||||
* Configuration of WebSocket
|
||||
*
|
||||
* @author db1995
|
||||
*/
|
||||
@Configuration
|
||||
public class WebSocketConfig {
|
||||
@Bean
|
||||
public ServerEndpointExporter serverEndpointExporter() {
|
||||
return new ServerEndpointExporter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ai.da.controller;
|
||||
|
||||
import com.ai.da.common.response.PageBaseResponse;
|
||||
import com.ai.da.common.response.Response;
|
||||
import com.ai.da.model.vo.GetNotificationVO;
|
||||
import com.ai.da.model.vo.NotificationVO;
|
||||
import com.ai.da.model.vo.PublishSysNotificationVO;
|
||||
import com.ai.da.service.MessageCenterService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Api(tags = "消息中心模块")
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/message")
|
||||
public class MessageCenterController {
|
||||
|
||||
@Resource
|
||||
private MessageCenterService messageCenterService;
|
||||
|
||||
// 获取未读消息总数
|
||||
@ApiOperation(value = "获取未读消息数")
|
||||
@GetMapping("/getUnreadCount")
|
||||
public Response<Map<String, Long>> getUnreadMessage(){
|
||||
return Response.success(messageCenterService.getAllTypeMessageUnreadCount());
|
||||
}
|
||||
|
||||
// 获取历史消息
|
||||
@ApiOperation(value = "获取历史消息")
|
||||
@PostMapping("/getHistoryNotification")
|
||||
public Response<PageBaseResponse<NotificationVO>> getHistoryNotification(@Valid @RequestBody GetNotificationVO getNotificationVO) {
|
||||
return Response.success(messageCenterService.getHistoryNotification(getNotificationVO));
|
||||
}
|
||||
|
||||
// 已读消息
|
||||
@ApiOperation(value = "设置消息状态为已读")
|
||||
@PostMapping("/setReadStatus")
|
||||
public Response<Boolean> setReadStatus(@RequestParam("notificationIdList") List<Long> notificationIdList, @RequestParam("type") String type) {
|
||||
return Response.success(messageCenterService.setReadStatus(notificationIdList, type));
|
||||
}
|
||||
|
||||
// 发布系统消息
|
||||
@ApiOperation(value = "发布系统消息")
|
||||
@PostMapping("/publishSysMessage")
|
||||
public Response<String> publishSysMessage(@Valid @RequestBody PublishSysNotificationVO message) {
|
||||
messageCenterService.publishSystemNotification(message);
|
||||
return Response.success("success");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.ai.da.mapper.primary;
|
||||
|
||||
import com.ai.da.common.config.mybatis.plus.CommonMapper;
|
||||
import com.ai.da.mapper.primary.entity.Notification;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface NotificationMapper extends CommonMapper<Notification> {
|
||||
|
||||
List<Map<String, Object>> getTypeCount(Long receiverId);
|
||||
|
||||
/** 解决mybatis-plus自动过滤 is_deleted为1的数据 问题 */
|
||||
Notification getUniqueLikeAndFollow(String type, Long senderId, Long receiverId, Long portfolioId);
|
||||
|
||||
void updateUniqueLikeAndFollow(Long id, LocalDateTime time);
|
||||
|
||||
void deleteNotification(Long id, LocalDateTime time);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ai.da.mapper.primary;
|
||||
|
||||
import com.ai.da.common.config.mybatis.plus.CommonMapper;
|
||||
import com.ai.da.mapper.primary.entity.SysNotificationReadStatus;
|
||||
|
||||
public interface SysNotificationReadStatusMapper extends CommonMapper<SysNotificationReadStatus> {
|
||||
}
|
||||
@@ -99,4 +99,9 @@ public class Account implements Serializable {
|
||||
* 4 : 参加活动获取30天有效期和6000个积分的用户
|
||||
*/
|
||||
private Integer systemUser;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.ai.da.mapper.primary.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_notification")
|
||||
@Data
|
||||
public class Notification extends BaseEntity{
|
||||
/**
|
||||
* 操作类型 system/like/comment/follow
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* 发起操作者用户id
|
||||
*/
|
||||
private Long senderId;
|
||||
/**
|
||||
* 被操作对象用户id
|
||||
*/
|
||||
private Long receiverId;
|
||||
/**
|
||||
* 点赞评论时的作品id
|
||||
*/
|
||||
private Long portfolioId;
|
||||
/**
|
||||
* 消息内容
|
||||
*/
|
||||
private String content;
|
||||
/**
|
||||
* 评论id
|
||||
*/
|
||||
private Long commentId;
|
||||
/**
|
||||
* 个人消息已读状态
|
||||
*/
|
||||
private Integer isRead;
|
||||
/**
|
||||
* 系统消息发布状态
|
||||
*/
|
||||
private Integer publishFlag;
|
||||
|
||||
/**
|
||||
* 是否被删除
|
||||
*/
|
||||
private Integer isDeleted;
|
||||
|
||||
public Notification() {
|
||||
}
|
||||
|
||||
public Notification(String type, Long senderId, Long receiverId, Long portfolioId) {
|
||||
this.type = type;
|
||||
this.senderId = senderId;
|
||||
this.receiverId = receiverId;
|
||||
this.portfolioId = portfolioId;
|
||||
}
|
||||
|
||||
public Notification(String type, Long senderId, Long receiverId, Long portfolioId, String content, Long commentId) {
|
||||
this.type = type;
|
||||
this.senderId = senderId;
|
||||
this.receiverId = receiverId;
|
||||
this.portfolioId = portfolioId;
|
||||
this.content = content;
|
||||
this.commentId = commentId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ai.da.mapper.primary.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("t_sys_notification_read_status")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SysNotificationReadStatus extends BaseEntity{
|
||||
/**
|
||||
* 系统消息id
|
||||
*/
|
||||
private Long system_notification_id;
|
||||
|
||||
/**
|
||||
* 已读当前消息的用户id将被存储
|
||||
*/
|
||||
private Long account_id;
|
||||
}
|
||||
15
src/main/java/com/ai/da/model/vo/GetNotificationVO.java
Normal file
15
src/main/java/com/ai/da/model/vo/GetNotificationVO.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.ai.da.model.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ApiModel
|
||||
@Data
|
||||
public class GetNotificationVO extends PageQueryBaseVo{
|
||||
|
||||
@ApiModelProperty("system/like/comment/follow")
|
||||
private String type;
|
||||
}
|
||||
17
src/main/java/com/ai/da/model/vo/NotificationVO.java
Normal file
17
src/main/java/com/ai/da/model/vo/NotificationVO.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.ai.da.model.vo;
|
||||
|
||||
import com.ai.da.mapper.primary.entity.Notification;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class NotificationVO extends Notification {
|
||||
|
||||
private String senderUserName;
|
||||
|
||||
private String senderUserAvatar;
|
||||
|
||||
private String portfolioName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ai.da.model.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ApiModel("发布系统消息")
|
||||
public class PublishSysNotificationVO {
|
||||
|
||||
@ApiModelProperty("系统消息标题")
|
||||
private String title;
|
||||
|
||||
@ApiModelProperty("系统消息内容")
|
||||
private String content;
|
||||
|
||||
@ApiModelProperty("系统消息 活动链接")
|
||||
private String link;
|
||||
}
|
||||
@@ -3349,6 +3349,7 @@ public class PythonService {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("promptTranslation 用户输入翻译失败; error message => " + e.getMessage());
|
||||
response.close();
|
||||
throw new RuntimeException(e);
|
||||
|
||||
} finally {
|
||||
|
||||
26
src/main/java/com/ai/da/service/MessageCenterService.java
Normal file
26
src/main/java/com/ai/da/service/MessageCenterService.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.ai.da.service;
|
||||
|
||||
import com.ai.da.common.response.PageBaseResponse;
|
||||
import com.ai.da.mapper.primary.entity.Notification;
|
||||
import com.ai.da.model.vo.GetNotificationVO;
|
||||
import com.ai.da.model.vo.NotificationVO;
|
||||
import com.ai.da.model.vo.PublishSysNotificationVO;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface MessageCenterService extends IService<Notification> {
|
||||
|
||||
Map<String, Long> getAllTypeMessageUnreadCount();
|
||||
|
||||
PageBaseResponse<NotificationVO> getHistoryNotification(GetNotificationVO getNotificationVO);
|
||||
|
||||
void prePushMessage(Notification notification);
|
||||
|
||||
void cancelPushMessage(String type, Long senderId, Long receiverId, Long portfolioId, Long commentId);
|
||||
|
||||
Boolean setReadStatus(List<Long> notificationIdList, String type);
|
||||
|
||||
void publishSystemNotification(PublishSysNotificationVO message);
|
||||
}
|
||||
@@ -1024,7 +1024,7 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
||||
account.setIsTrial(1);
|
||||
account.setIsBeginner(1);
|
||||
account.setCreateDate(new Date());
|
||||
account.setCredits(BigDecimal.valueOf(500));
|
||||
account.setCredits(BigDecimal.valueOf(100));
|
||||
accountMapper.insert(account);
|
||||
AccountLoginVO response = CopyUtil.copyObject(account, AccountLoginVO.class);
|
||||
response.setEmail(account.getUserEmail());
|
||||
|
||||
@@ -744,7 +744,7 @@ public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> i
|
||||
redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
|
||||
|
||||
Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1));
|
||||
if (!status.equals("Invalid")){
|
||||
if (!status.equals("Invalid")) {
|
||||
// 4、扣除积分
|
||||
Boolean b = creditsService.taskCreditsDeduction(accountId, taskId);
|
||||
// 3、记录积分变更
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.ai.da.service.impl;
|
||||
|
||||
import com.ai.da.common.constant.CommonConstant;
|
||||
import com.ai.da.common.context.UserContext;
|
||||
import com.ai.da.common.response.PageBaseResponse;
|
||||
import com.ai.da.common.utils.CopyUtil;
|
||||
import com.ai.da.common.utils.MinioUtil;
|
||||
import com.ai.da.common.websocket.NotificationConnection;
|
||||
import com.ai.da.mapper.primary.NotificationMapper;
|
||||
import com.ai.da.mapper.primary.SysNotificationReadStatusMapper;
|
||||
import com.ai.da.mapper.primary.entity.Account;
|
||||
import com.ai.da.mapper.primary.entity.Notification;
|
||||
import com.ai.da.mapper.primary.entity.SysNotificationReadStatus;
|
||||
import com.ai.da.model.vo.GetNotificationVO;
|
||||
import com.ai.da.model.vo.NotificationVO;
|
||||
import com.ai.da.model.vo.PublishSysNotificationVO;
|
||||
import com.ai.da.service.AccountService;
|
||||
import com.ai.da.service.MessageCenterService;
|
||||
import com.ai.da.service.PortfolioService;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.google.gson.Gson;
|
||||
import com.mysql.cj.util.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MessageCenterServiceImpl extends ServiceImpl<NotificationMapper, Notification> implements MessageCenterService {
|
||||
|
||||
@Resource
|
||||
private NotificationConnection notificationConnection;
|
||||
@Resource
|
||||
private SysNotificationReadStatusMapper sysNotificationReadStatusMapper;
|
||||
@Resource
|
||||
private AccountService accountService;
|
||||
@Resource
|
||||
private MinioUtil minioUtil;
|
||||
@Resource
|
||||
private PortfolioService portfolioService;
|
||||
|
||||
|
||||
@Override
|
||||
public Map<String, Long> getAllTypeMessageUnreadCount() {
|
||||
List<Map<String, Object>> typeCount = baseMapper.getTypeCount(UserContext.getUserHolder().getId());
|
||||
Map<String, Long> msgTypeCount = typeCount.stream()
|
||||
.collect(Collectors.toMap(
|
||||
map -> (String) map.get("type"),
|
||||
map -> Objects.isNull(map.get("count")) ? 0L : (Long) map.get("count")));
|
||||
msgTypeCount.put("system", getUnreadSystemNotification());
|
||||
log.info(msgTypeCount.toString());
|
||||
// 整理数据 加上系统消息未读数
|
||||
return msgTypeCount;
|
||||
}
|
||||
|
||||
|
||||
// 获取历史消息 可指定消息类型 分页查询
|
||||
@Override
|
||||
public PageBaseResponse<NotificationVO> getHistoryNotification(GetNotificationVO getNotificationVO) {
|
||||
QueryWrapper<Notification> queryWrapper = new QueryWrapper<>();
|
||||
if (!StringUtils.isNullOrEmpty(getNotificationVO.getType())) {
|
||||
queryWrapper.eq("type", getNotificationVO.getType());
|
||||
}
|
||||
queryWrapper.eq("receiver_id", UserContext.getUserHolder().getId());
|
||||
Page<Notification> notificationPage = baseMapper.selectPage(new Page<>(getNotificationVO.getPage(), getNotificationVO.getSize()), queryWrapper);
|
||||
IPage<NotificationVO> convert = notificationPage.convert(o -> {
|
||||
NotificationVO notificationVO = CopyUtil.copyObject(o, NotificationVO.class);
|
||||
Account account = accountService.getById(notificationVO.getSenderId());
|
||||
notificationVO.setSenderUserName(account.getUserName());
|
||||
notificationVO.setSenderUserAvatar(StringUtils.isNullOrEmpty(account.getAvatar()) ? null : minioUtil.getPreSignedUrl(account.getAvatar(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
|
||||
notificationVO.setPortfolioName(portfolioService.getById(notificationVO.getPortfolioId()).getPortfolioName());
|
||||
return notificationVO;
|
||||
});
|
||||
|
||||
return PageBaseResponse.success(convert);
|
||||
}
|
||||
|
||||
public void prePushMessage(Notification notification){
|
||||
// 先判断点赞、关注记录是不是唯一
|
||||
if (!getUniqueLikeAndFollow(notification)){
|
||||
// 1、将数据存数据库
|
||||
if (!notification.getType().equals("system")) {
|
||||
notification.setIsRead(0);
|
||||
}
|
||||
notification.setIsDeleted(0);
|
||||
notification.setCreateTime(LocalDateTime.now());
|
||||
save(notification);
|
||||
}
|
||||
// 推送消息
|
||||
pushMessage(notification.getType(), notification.getSenderId());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 只记录唯一点赞和关注
|
||||
* 重复点赞、关注只会记录最新的一次操作
|
||||
* @param notification
|
||||
* @return
|
||||
*/
|
||||
public Boolean getUniqueLikeAndFollow(Notification notification){
|
||||
Notification existNotification= null;
|
||||
// 对单个作品的点赞和对某个人的关注 具有唯一性
|
||||
if (notification.getType().equals("like") || notification.getType().equals("follow")){
|
||||
existNotification = baseMapper.getUniqueLikeAndFollow(notification.getType(), notification.getSenderId(),
|
||||
notification.getReceiverId(), notification.getPortfolioId());
|
||||
}
|
||||
|
||||
if (!Objects.isNull(existNotification)){
|
||||
// 有记录,则更新 1、删除状态 为0 2、已读状态 为0 3、创建时间 为最新时间 4、更新时间 为最新时间
|
||||
baseMapper.updateUniqueLikeAndFollow(existNotification.getId(), LocalDateTime.now());
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
|
||||
// 消息推送 只需要返回当前操作类型和该操作未读消息数量
|
||||
public void pushMessage(String type, Long senderId) {
|
||||
// 推送消息到前端
|
||||
ArrayList<Map<String, Object>> resp = new ArrayList<>();
|
||||
HashMap<String, Object> data = new HashMap<>();
|
||||
Long count;
|
||||
if (!type.equals("system")) {
|
||||
// 个人未读消息
|
||||
count = getUnreadCountByType(type, senderId);
|
||||
} else {
|
||||
// 系统未读消息
|
||||
count = getUnreadSystemNotification();
|
||||
}
|
||||
|
||||
data.put(type, count);
|
||||
resp.add(data);
|
||||
String jsonString = JSON.toJSONString(resp);
|
||||
log.info("消息推送 : {}", jsonString);
|
||||
|
||||
try {
|
||||
notificationConnection.sendMsg(jsonString);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 取消点赞、删除评论、取消关注
|
||||
public void cancelPushMessage(String type, Long senderId, Long receiverId, Long portfolioId, Long commentId) {
|
||||
QueryWrapper<Notification> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", type)
|
||||
.eq("sender_id", senderId)
|
||||
.eq("receiver_id", receiverId)
|
||||
.eq("is_deleted", 0);
|
||||
|
||||
if (!type.equals("follow")){
|
||||
queryWrapper.eq("portfolio_id", portfolioId);
|
||||
}
|
||||
if (type.equals("comment")) {
|
||||
queryWrapper.eq("comment_id", commentId);
|
||||
}
|
||||
Notification notification = baseMapper.selectOne(queryWrapper);
|
||||
if (!Objects.isNull(notification)) {
|
||||
baseMapper.deleteNotification(notification.getId(), LocalDateTime.now());
|
||||
}
|
||||
// 推送消息
|
||||
pushMessage(type, senderId);
|
||||
}
|
||||
|
||||
// 获取个人指定消息类型未读消息数量
|
||||
private Long getUnreadCountByType(String type, Long receiverId) {
|
||||
QueryWrapper<Notification> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", type)
|
||||
.eq("is_read", 0)
|
||||
.eq("receiver_id", receiverId);
|
||||
|
||||
return baseMapper.selectCount(queryWrapper);
|
||||
}
|
||||
|
||||
private Long getUnreadSystemNotification() {
|
||||
// 计算总的系统通知数量
|
||||
QueryWrapper<Notification> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("type", "system");
|
||||
Long totalSysCount = baseMapper.selectCount(queryWrapper);
|
||||
|
||||
// 计算单个用户读了多少条系统数据
|
||||
QueryWrapper<SysNotificationReadStatus> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("account_id", UserContext.getUserHolder().getId());
|
||||
Long readCount = sysNotificationReadStatusMapper.selectCount(wrapper);
|
||||
|
||||
// 计算差
|
||||
return totalSysCount - readCount;
|
||||
}
|
||||
|
||||
// 设置个人消息的已读状态 (允许一次已读多条个人消息)
|
||||
public Boolean setReadStatus(List<Long> notificationIdList, String type) {
|
||||
|
||||
if (type.equals("system")) {
|
||||
setReadStatusSystem(notificationIdList);
|
||||
} else {
|
||||
UpdateWrapper<Notification> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.in("id", notificationIdList)
|
||||
// .eq("type", type)
|
||||
.set("is_read", 1)
|
||||
.set("update_time", LocalDateTime.now());
|
||||
|
||||
baseMapper.update(null, updateWrapper);
|
||||
}
|
||||
return Boolean.TRUE;
|
||||
|
||||
}
|
||||
|
||||
// 设置系统消息的已读状态 (允许一次已读多条系统消息)
|
||||
public void setReadStatusSystem(List<Long> notificationIdList) {
|
||||
Long id = UserContext.getUserHolder().getId();
|
||||
for (Long notificationId : notificationIdList) {
|
||||
SysNotificationReadStatus sysNotificationReadStatus = new SysNotificationReadStatus(notificationId, id);
|
||||
sysNotificationReadStatus.setCreateTime(LocalDateTime.now());
|
||||
sysNotificationReadStatusMapper.insert(sysNotificationReadStatus);
|
||||
}
|
||||
}
|
||||
|
||||
// todo 全部已读
|
||||
|
||||
// 发布系统消息
|
||||
public void publishSystemNotification(PublishSysNotificationVO message) {
|
||||
Notification notification = new Notification();
|
||||
notification.setType("system");
|
||||
notification.setSenderId(UserContext.getUserHolder().getId());
|
||||
notification.setContent(new Gson().toJson(message));
|
||||
// todo 是否需要定时发送系统通知
|
||||
prePushMessage(notification);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import sun.security.krb5.internal.crypto.Des;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
@@ -795,10 +794,16 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Resource
|
||||
private MessageCenterService messageCenterService;
|
||||
|
||||
@Override
|
||||
public Boolean like(Long id) {
|
||||
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||
redisUtil.likePost(id, userHolder.getId());
|
||||
Portfolio byIdAll = baseMapper.getByIdAll(id);
|
||||
messageCenterService.prePushMessage(new Notification("like", userHolder.getId(), byIdAll.getAccountId(), id));
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@@ -811,6 +816,8 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
comment.setCreateTime(LocalDateTime.now());
|
||||
comment.setAccountId(userHolder.getId());
|
||||
commentMapper.insert(comment);
|
||||
Portfolio portfolio = baseMapper.selectById(comment.getPortfolioId());
|
||||
messageCenterService.prePushMessage(new Notification("comment", userHolder.getId(), portfolio.getAccountId(), portfolio.getId(), commentDTO.getComment(), comment.getId()));
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@@ -837,6 +844,8 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
public Boolean unlike(Long id) {
|
||||
AuthPrincipalVo userHolder = UserContext.getUserHolder();
|
||||
redisUtil.unLikePost(id, userHolder.getId());
|
||||
Portfolio portfolio = baseMapper.selectById(id);
|
||||
messageCenterService.cancelPushMessage("like", userHolder.getId(), portfolio.getAccountId(), portfolio.getId(), null);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
@@ -869,6 +878,15 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
throw new BusinessException("You do not have the permission to delete this comment");
|
||||
}
|
||||
}
|
||||
|
||||
// 获取消息接收者id(即该操作的接收者)
|
||||
Long receiverId;
|
||||
if (!comment.getParentLevel1Id().equals(0L)){
|
||||
receiverId = commentMapper.selectById(comment.getParentLevel1Id()).getAccountId();
|
||||
}else {
|
||||
receiverId = portfolio.getAccountId();
|
||||
}
|
||||
|
||||
// 删除主评论
|
||||
commentMapper.deleteById(id);
|
||||
|
||||
@@ -879,6 +897,7 @@ public class PortfolioServiceImpl extends ServiceImpl<PortfolioMapper, Portfolio
|
||||
.eq(Comment::getParentLevel2Id, id);
|
||||
commentMapper.delete(lambdaQueryWrapper);
|
||||
|
||||
messageCenterService.cancelPushMessage("comment", userHolder.getId(), receiverId, portfolio.getId(), id);
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user