feat(filter): 添加应用版本过滤器并优化异步任务配置
- 新增AppVersionFilter用于验证客户端版本号 - 添加MdcTaskDecorator确保异步任务中的MDC上下文传递 - 在多个模块的TaskSchedulerConfig中配置MDC装饰器 - 修复Redis键值格式统一使用"-uid-"分隔符 - 调整TicketAddRequest中type字段默认值为0 - 优化TraceIdFilter执行顺序为最高优先级 - 将UniPushService的send方法改为异步执行并返回CompletableFuture
This commit is contained in:
parent
3a0facbd9e
commit
7db9d6ef8e
|
|
@ -1,5 +1,6 @@
|
|||
package com.nflg.mobilebroken.admin.config;
|
||||
|
||||
import com.nflg.mobilebroken.starter.decorator.MdcTaskDecorator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
|
|
@ -34,6 +35,19 @@ public class TaskSchedulerConfig {
|
|||
executor.setThreadNamePrefix("sse-send-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
executor.setTaskDecorator(new MdcTaskDecorator());
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(name = "httpExecutor")
|
||||
public Executor httpExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("http-async-");
|
||||
executor.initialize();
|
||||
executor.setTaskDecorator(new MdcTaskDecorator());
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1387,7 +1387,6 @@ public class TicketController extends ControllerBase {
|
|||
*/
|
||||
@GetMapping("call")
|
||||
public ApiResult<Void> call(@Valid @RequestParam @NotNull Integer ticketId) {
|
||||
|
||||
Ticket ticket = ticketService.getById(ticketId);
|
||||
VUtils.trueThrowBusinessError(Objects.isNull(ticket)).throwMessage("工单不存在");
|
||||
VUtils.trueThrowBusinessError(!Objects.equals(ticket.getState(), TicketState.Processing.getState()))
|
||||
|
|
@ -1446,7 +1445,7 @@ public class TicketController extends ControllerBase {
|
|||
ssePushService.sendTicketCallToAdmin(adminUser, receiveUserId, Long.valueOf(ticketId));
|
||||
}
|
||||
ticketEventPublisher.publishTicketCallBeginEvent(ticketId, adminUser.getUserName());
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, receiveUserFrom + "-" + receiveUserId);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, receiveUserFrom + "-uid-" + receiveUserId);
|
||||
stringRedisTemplate.expire(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, 1, TimeUnit.MINUTES);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
|
@ -1493,7 +1492,7 @@ public class TicketController extends ControllerBase {
|
|||
);
|
||||
ssePushService.sendTicketCallToAdmin(adminUser, userId, request.getTicketId());
|
||||
// ticketCallJoinService.add(ticketCall.getId(), userId, Constant.FROM_ADMIN);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_ADMIN + "-" + userId);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_ADMIN + "-uid-" + userId);
|
||||
stringRedisTemplate.expire(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), 1, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
|
@ -1561,7 +1560,7 @@ public class TicketController extends ControllerBase {
|
|||
.setTitle("视频通话结束")
|
||||
.setTicketId(String.valueOf(request.getTicketId()))
|
||||
.setTicketType(ticket.getType())
|
||||
.setUserId(Integer.valueOf(StrUtil.split(uid, "-").get(1)))
|
||||
.setUserId(Integer.valueOf(StrUtil.split(uid, "-").get(2)))
|
||||
.setCategory("ticketCallEnd")
|
||||
.setFrom("admin")
|
||||
)
|
||||
|
|
@ -1589,7 +1588,7 @@ public class TicketController extends ControllerBase {
|
|||
)
|
||||
);
|
||||
}
|
||||
stringRedisTemplate.opsForSet().remove(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_ADMIN + "-" + AdminUserUtil.getUserId());
|
||||
stringRedisTemplate.opsForSet().remove(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_ADMIN + "-uid-" + AdminUserUtil.getUserId());
|
||||
}
|
||||
taskScheduler.schedule(() -> {
|
||||
ticketEventPublisher.publishTicketCallEndEvent(ticket);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package com.nflg.mobilebroken.cfs.config;
|
||||
|
||||
import com.nflg.mobilebroken.starter.decorator.MdcTaskDecorator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@EnableAsync
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class TaskSchedulerConfig {
|
||||
|
||||
@Bean(name = "httpExecutor")
|
||||
public Executor httpExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("http-async-");
|
||||
executor.initialize();
|
||||
executor.setTaskDecorator(new MdcTaskDecorator());
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
|
@ -655,7 +655,7 @@ public class TicketController extends ControllerBase {
|
|||
ssePushService.sendTicketCallToAdmin(appUser, handlerUserId, ticketId);
|
||||
// ticketCallService.add(ticketId, AppUserUtil.getUserId(),Constant.FROM_APP, handlerUserId, Constant.FROM_ADMIN);
|
||||
ticketEventPublisher.publishTicketCallBeginEvent(ticketId, appUser.getName());
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, Constant.FROM_ADMIN + "-" + handlerUserId);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, Constant.FROM_ADMIN + "-uid-" + handlerUserId);
|
||||
stringRedisTemplate.expire(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, 1, TimeUnit.MINUTES);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
|
@ -716,7 +716,7 @@ public class TicketController extends ControllerBase {
|
|||
.setTitle("视频通话结束")
|
||||
.setTicketId(String.valueOf(request.getTicketId()))
|
||||
.setTicketType(ticket.getType())
|
||||
.setUserId(Integer.valueOf(StrUtil.split(uid, "-").get(1)))
|
||||
.setUserId(Integer.valueOf(StrUtil.split(uid, "-").get(2)))
|
||||
.setCategory("ticketCallEnd")
|
||||
.setFrom("app")
|
||||
)
|
||||
|
|
@ -742,7 +742,7 @@ public class TicketController extends ControllerBase {
|
|||
)
|
||||
)
|
||||
);
|
||||
stringRedisTemplate.opsForSet().remove(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_APP + "-" + AppUserUtil.getUserId());
|
||||
stringRedisTemplate.opsForSet().remove(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_APP + "-uid-" + AppUserUtil.getUserId());
|
||||
}
|
||||
taskScheduler.schedule(() -> {
|
||||
ticketEventPublisher.publishTicketCallEndEvent(ticket);
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ public class TicketAddRequest {
|
|||
/**
|
||||
* 类型,0:移动破;1:工服
|
||||
*/
|
||||
@NotNull
|
||||
private Integer type;
|
||||
private Integer type = 0;
|
||||
|
||||
/**
|
||||
* 紧急程度,0:非紧急;1:普通;2:紧急
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nflg.mobilebroken.gongfu.config;
|
||||
|
||||
import com.nflg.mobilebroken.starter.decorator.MdcTaskDecorator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
|
|
@ -34,6 +35,19 @@ public class TaskSchedulerConfig {
|
|||
executor.setThreadNamePrefix("sse-send-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
executor.setTaskDecorator(new MdcTaskDecorator());
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Bean(name = "httpExecutor")
|
||||
public Executor httpExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("http-async-");
|
||||
executor.initialize();
|
||||
executor.setTaskDecorator(new MdcTaskDecorator());
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1374,7 +1374,7 @@ public class TicketController extends ControllerBase {
|
|||
ssePushService.sendTicketCallToAdmin(adminUser, receiveUserId, ticketId);
|
||||
}
|
||||
ticketEventPublisher.publishTicketCallBeginEvent(ticketId, adminUser.getUserName());
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, receiveUserFrom + "-" + receiveUserId);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, receiveUserFrom + "-uid-" + receiveUserId);
|
||||
stringRedisTemplate.expire(Constant.REDIS_KEY_TICKET_CALL_WAIT + ticketId, 1, TimeUnit.MINUTES);
|
||||
return ApiResult.success();
|
||||
}
|
||||
|
|
@ -1421,7 +1421,7 @@ public class TicketController extends ControllerBase {
|
|||
);
|
||||
ssePushService.sendTicketCallToAdmin(adminUser, userId, request.getTicketId());
|
||||
// ticketCallJoinService.add(ticketCall.getId(), userId, Constant.FROM_ADMIN);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_ADMIN + "-" + userId);
|
||||
stringRedisTemplate.opsForSet().add(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), Constant.FROM_ADMIN + "-uid-" + userId);
|
||||
stringRedisTemplate.expire(Constant.REDIS_KEY_TICKET_CALL_WAIT + request.getTicketId(), 1, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
|
@ -1483,7 +1483,7 @@ public class TicketController extends ControllerBase {
|
|||
// .setPayload(new UniPushMessageCallPayload()
|
||||
// .setTitle("视频通话结束")
|
||||
// .setTicketId(request.getTicketId())
|
||||
// .setUserId(Integer.valueOf(StrUtil.split(uid, "-").get(1)))
|
||||
// .setUserId(Integer.valueOf(StrUtil.split(uid, "-").get(2)))
|
||||
// .setCategory("ticketCallEnd")
|
||||
// .setFrom("admin")
|
||||
// )
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.nflg.mobilebroken.starter.decorator;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.core.task.TaskDecorator;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MdcTaskDecorator implements TaskDecorator {
|
||||
|
||||
@Override
|
||||
public Runnable decorate(@NonNull Runnable runnable) {
|
||||
// 1. 获取主线程(父线程)当前的 MDC 上下文副本
|
||||
Map<String, String> contextMap = MDC.getCopyOfContextMap();
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
// 2. 将父线程的上下文 设置到子线程中
|
||||
if (contextMap != null) {
|
||||
MDC.setContextMap(contextMap);
|
||||
}
|
||||
// 3. 执行真正的任务
|
||||
runnable.run();
|
||||
} finally {
|
||||
// 4. 务必清除 MDC,避免线程复用时内存泄漏或数据污染
|
||||
MDC.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.nflg.mobilebroken.starter.filter;
|
||||
|
||||
import cn.hutool.core.comparator.VersionComparator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nflg.mobilebroken.common.constant.STATE;
|
||||
import com.nflg.mobilebroken.common.pojo.ApiResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@Slf4j
|
||||
@Order(0)
|
||||
@Component
|
||||
public class AppVersionFilter extends OncePerRequestFilter {
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final String MIN_SUPPER_VERSION = "1.0.9";
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||
String appPlatform = request.getHeader("App-Platform");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
if (StrUtil.isBlank(appPlatform)) {
|
||||
log.error("请求头中未找到App-Platform");
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
out(response, ApiResult.error(STATE.ServiceConnectRefused, "请更新版本!"));
|
||||
} else {
|
||||
if (appPlatform.startsWith("pc")) {
|
||||
filterChain.doFilter(request, response);
|
||||
} else {
|
||||
String appVersion = request.getHeader("App-Version");
|
||||
if (StrUtil.isBlank(appVersion)) {
|
||||
log.error("请求头中未找到App-Version");
|
||||
out(response, ApiResult.error(STATE.ServiceConnectRefused, "请更新版本!"));
|
||||
} else if (VersionComparator.INSTANCE.compare(appVersion, MIN_SUPPER_VERSION) < 0) {
|
||||
out(response, ApiResult.error(STATE.ServiceConnectRefused, "版本太低,请更新版本!"));
|
||||
} else {
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void out(HttpServletResponse response, ApiResult result) throws IOException {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(objectMapper.writeValueAsString(result));
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.nflg.mobilebroken.common.util.SaTokenAdminUtil;
|
|||
import com.nflg.mobilebroken.common.util.SaTokenAppUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
|
@ -26,6 +27,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Order(1)
|
||||
@Component
|
||||
public class TraceIdFilter extends OncePerRequestFilter {
|
||||
|
||||
|
|
@ -48,13 +50,11 @@ public class TraceIdFilter extends OncePerRequestFilter {
|
|||
// 存入MDC和响应头
|
||||
MDC.put(Constant.TRACE_ID, traceId);
|
||||
responseWrapper.addHeader(TRACE_ID_HEADER, traceId);
|
||||
|
||||
filterChain.doFilter(requestWrapper, responseWrapper);
|
||||
} finally {
|
||||
logRequest(requestWrapper);
|
||||
logResponse(responseWrapper);
|
||||
responseWrapper.copyBodyToResponse();
|
||||
|
||||
// 请求结束时清除MDC
|
||||
MDC.remove(Constant.TRACE_ID);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@ import com.nflg.mobilebroken.common.util.AppUserUtil;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UniPushService {
|
||||
|
|
@ -21,11 +24,13 @@ public class UniPushService {
|
|||
@Value("${uniapp.cloud.push.url}")
|
||||
private String url;
|
||||
|
||||
public void send(UniPushMessage message) {
|
||||
@Async("httpExecutor")
|
||||
public CompletableFuture<Void> send(UniPushMessage message) {
|
||||
log.info("发送uniapp消息:{}", JSONUtil.toJsonStr(message));
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
ResponseEntity<String> response = restTemplate.postForEntity(url, message, String.class);
|
||||
log.info("发送uniapp消息结果:{}", response.getBody());
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public void sendTicketMessage(String from, String to, ChatMessageDTO data) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue