首页消息

This commit is contained in:
2026-01-06 17:01:35 +08:00
parent 1b31780136
commit 26742060e6
17 changed files with 403 additions and 13 deletions

View File

@@ -56,6 +56,13 @@
<version>2.1.0</version>
</dependency>
<!-- 配置属性元数据支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.honeycombis</groupId>
<artifactId>honeycom-flow-task-api</artifactId>

View File

@@ -4,6 +4,7 @@ package com.honeycombis.honeycom.spdm;
import com.honeycombis.honeycom.common.feign.annotation.EnableHoneycomFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -18,6 +19,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableAsync
@EnableConfigurationProperties
public class HoneycomSpdmApplication {
public static void main(String[] args) {
SpringApplication.run(HoneycomSpdmApplication.class, args);

View File

@@ -0,0 +1,34 @@
package com.honeycombis.honeycom.spdm.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.*;
@Data
@ConfigurationProperties(prefix = "proxy")
@Component
public class RouteConfig {
@Data
public static class Route {
private String baseUrl;
private String prefix;
private String targetPrefix = "";
private int timeout;
private boolean enabled;
private List<String> allowedMethods = Arrays.asList("GET", "POST", "PUT", "DELETE");
private boolean forwardHeaders = true;
private List<String> excludeHeaders = Arrays.asList("host", "content-length");
}
private Map<String, Route> routes = new HashMap<>();
public Optional<Route> findRouteByPath(String path) {
return routes.entrySet().stream()
.filter(entry -> path.startsWith(entry.getValue().getPrefix()))
.map(Map.Entry::getValue)
.findFirst();
}
}

View File

@@ -0,0 +1,73 @@
package com.honeycombis.honeycom.spdm.controller;
import com.honeycombis.honeycom.spdm.config.RouteConfig;
import com.honeycombis.honeycom.spdm.service.ProxyService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
@Slf4j
public class ProxyController {
@Autowired
private ProxyService proxyService;
@Autowired
private RouteConfig routeConfig;
/**
* 统一的代理入口
* 匹配所有以/api开头的请求
*/
@RequestMapping("/api/**")
public ResponseEntity<byte[]> proxy(HttpServletRequest request,
HttpServletResponse response) {
// 1. 查找匹配的路由配置
String requestPath = request.getRequestURI();
Optional<RouteConfig.Route> routeOpt = routeConfig.findRouteByPath(requestPath);
if (!routeOpt.isPresent()) {
return ResponseEntity.notFound().build();
}
RouteConfig.Route route = routeOpt.get();
// 2. 检查路由是否启用
if (!route.isEnabled()) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body("该路由暂不可用".getBytes());
}
// 3. 检查HTTP方法是否允许
if (!route.getAllowedMethods().contains(request.getMethod())) {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
.body("不支持该HTTP方法".getBytes());
}
// 4. 执行转发
ResponseEntity<byte[]> proxyResponse = proxyService.forward(request, route);
// 5. 复制响应头到原始响应(可选)
copyHeadersToResponse(proxyResponse, response);
return proxyResponse;
}
private void copyHeadersToResponse(ResponseEntity<?> source,
HttpServletResponse target) {
source.getHeaders().forEach((key, values) -> {
if (!key.toLowerCase().equals("transfer-encoding")) { // 排除分块传输编码
values.forEach(value -> target.addHeader(key, value));
}
});
}
}

View File

@@ -19,18 +19,32 @@
package com.honeycombis.honeycom.spdm.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.honeycombis.honeycom.common.core.util.R;
import com.honeycombis.honeycom.msg.api.dto.MessageListItemUpdateDto;
import com.honeycombis.honeycom.msg.api.dto.MessageListUserQueryDto;
import com.honeycombis.honeycom.msg.api.dto.MessageOpenApiDTO;
import com.honeycombis.honeycom.msg.api.entity.MessageListItemEntity;
import com.honeycombis.honeycom.msg.api.vo.MessageListItemDetailVo;
import com.honeycombis.honeycom.spdm.dto.*;
import com.honeycombis.honeycom.spdm.feign.RemoteMsgServiceFeign;
import com.honeycombis.honeycom.spdm.util.PageResult;
import com.honeycombis.honeycom.spdm.util.ResponseR;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@@ -43,6 +57,9 @@ public class SpdmMsgController {
@Resource
private RemoteMsgServiceFeign remoteMsgServiceFeign;
@Autowired
private ObjectMapper objectMapper;
@Operation(summary = "发送消息通知")
@PostMapping(value = "/sendMessage")
public ResponseR sendMessage(@RequestBody MessageDto messageDto) {
@@ -55,4 +72,20 @@ public class SpdmMsgController {
return ResponseR.ok();
}
@Operation(summary = "获取消息记录")
@GetMapping(value = "/getUserMsgPageList")
public ResponseR getUserMsgPageList(@ParameterObject MessageListUserQueryDto query, @RequestHeader String userId, @RequestHeader String tenantId) {
query.setUserId(Long.valueOf(userId));
R<Page<MessageListItemDetailVo>> iPageR = remoteMsgServiceFeign.getUserMsgPageList(query, tenantId);
Page<MessageListItemDetailVo> pageList = iPageR.getData();
return ResponseR.ok(PageResult.of(pageList.getTotal(), pageList.getCurrent(), pageList.getSize(), pageList.getRecords()));
}
@Operation(summary = "消息已读")
@PostMapping(value = "/setMsgReadStatus")
public ResponseR setItemReadStatus(@RequestBody MessageListItemUpdateDto dto, @RequestHeader String tenantId) {
R<Object> objectR = remoteMsgServiceFeign.setItemReadStatus(dto, tenantId);
return ResponseR.ok(objectR.getData());
}
}

View File

@@ -1,11 +1,17 @@
package com.honeycombis.honeycom.spdm.feign;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.honeycombis.honeycom.common.core.constant.CommonConstants;
import com.honeycombis.honeycom.common.core.constant.ServiceNameConstants;
import com.honeycombis.honeycom.common.core.util.R;
import com.honeycombis.honeycom.common.feign.config.FeignConfig;
import com.honeycombis.honeycom.msg.api.dto.MessageListItemUpdateDto;
import com.honeycombis.honeycom.msg.api.dto.MessageListUserQueryDto;
import com.honeycombis.honeycom.msg.api.dto.MessageOpenApiDTO;
import com.honeycombis.honeycom.msg.api.vo.MessageListItemDetailVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.SpringQueryMap;
import org.springframework.web.bind.annotation.*;
@@ -15,5 +21,9 @@ public interface RemoteMsgServiceFeign {
@PostMapping(value = "/openapi/push")
R<Object> pushMessage(@RequestBody MessageOpenApiDTO dto, @RequestHeader(CommonConstants.TENANT_ID) String tenantId);
@GetMapping("/message/list/user/page")
R<Page<MessageListItemDetailVo>> getUserMsgPageList(@SpringQueryMap MessageListUserQueryDto query, @RequestHeader(CommonConstants.TENANT_ID) String tenantId);
@PostMapping("/message/list/item/isRead")
R<Object> setItemReadStatus(@RequestBody MessageListItemUpdateDto dto, @RequestHeader(CommonConstants.TENANT_ID) String tenantId);
}

View File

@@ -0,0 +1,14 @@
package com.honeycombis.honeycom.spdm.service;
import com.honeycombis.honeycom.spdm.config.RouteConfig;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
public interface ProxyService {
ResponseEntity<byte[]> forward(HttpServletRequest request, RouteConfig.Route route);
String buildTargetUrl(HttpServletRequest request, RouteConfig.Route route);
HttpHeaders extractHeaders(HttpServletRequest request, RouteConfig.Route route);
}

View File

@@ -0,0 +1,163 @@
package com.honeycombis.honeycom.spdm.service.impl;
import com.alibaba.nacos.common.utils.StringUtils;
import com.honeycombis.honeycom.spdm.config.RouteConfig;
import com.honeycombis.honeycom.spdm.service.ProxyService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
@Service
@Slf4j
public class ProxyServiceImpl implements ProxyService {
@Autowired
private RestTemplate restTemplate;
@Override
public ResponseEntity<byte[]> forward(HttpServletRequest request, RouteConfig.Route route) {
try {
// 1. 构建目标URL
String targetUrl = buildTargetUrl(request, route);
// 2. 提取请求头
HttpHeaders headers = extractHeaders(request, route);
// 3. 提取请求体
byte[] body = extractRequestBody(request);
// 4. 构建HttpEntity
HttpEntity<byte[]> entity = new HttpEntity<>(body, headers);
// 5. 执行请求
HttpMethod method = HttpMethod.valueOf(request.getMethod());
log.debug("Forwarding request: {} {} -> {}",
method, request.getRequestURI(), targetUrl);
return restTemplate.exchange(
targetUrl,
method,
entity,
byte[].class
);
} catch (ResourceAccessException e) {
log.error("Connection failed to target system: {}", route.getBaseUrl(), e);
throw new RuntimeException("无法连接到目标系统", e);
} catch (Exception e) {
log.error("Forward request failed", e);
throw new RuntimeException("请求转发失败", e);
}
}
@Override
public String buildTargetUrl(HttpServletRequest request, RouteConfig.Route route) {
// 1. 获取请求路径
String requestPath = request.getRequestURI();
// 2. 移除路由配置中的prefix
String targetPath = requestPath.substring(route.getPrefix().length());
// 3. 确保targetPath以/开头
if (!targetPath.startsWith("/")) {
targetPath = "/" + targetPath;
}
// 4. 构建目标URL
StringBuilder urlBuilder = new StringBuilder();
// 添加baseUrl
urlBuilder.append(route.getBaseUrl());
// 添加目标系统的前缀(如果配置了)
if (StringUtils.hasText(route.getTargetPrefix())) {
String targetPrefix = route.getTargetPrefix();
// 确保targetPrefix以/开头且不以/结尾
if (!targetPrefix.startsWith("/")) {
targetPrefix = "/" + targetPrefix;
}
if (targetPrefix.endsWith("/")) {
targetPrefix = targetPrefix.substring(0, targetPrefix.length() - 1);
}
urlBuilder.append(targetPrefix);
}
// 添加请求路径
urlBuilder.append(targetPath);
// 5. 添加查询参数
String queryString = request.getQueryString();
if (StringUtils.hasText(queryString)) {
urlBuilder.append("?").append(queryString);
}
String targetUrl = urlBuilder.toString();
log.debug("构建目标URL: {} -> {}", requestPath, targetUrl);
return targetUrl;
}
@Override
public HttpHeaders extractHeaders(HttpServletRequest request,
RouteConfig.Route route) {
HttpHeaders headers = new HttpHeaders();
if (!route.isForwardHeaders()) {
return headers;
}
// 复制所有请求头
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement().toLowerCase();
// 排除不需要转发的头
if (route.getExcludeHeaders().contains(headerName)) {
continue;
}
Enumeration<String> headerValues = request.getHeaders(headerName);
while (headerValues.hasMoreElements()) {
headers.add(headerName, headerValues.nextElement());
}
}
// 添加自定义头(可选)
headers.add("X-Forwarded-For", request.getRemoteAddr());
headers.add("X-Forwarded-Host", request.getServerName());
headers.add("X-Forwarded-Proto", request.getScheme());
return headers;
}
private byte[] extractRequestBody(HttpServletRequest request) throws IOException {
if (request.getContentLength() <= 0) {
return new byte[0];
}
try (InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
}
}
}

View File

@@ -5,22 +5,22 @@ server:
spring:
application:
name: @artifactId@
name: honeycom-spdm
cloud:
nacos:
username: @nacos.username@
password: @nacos.password@
username: nacos
password: N@c0s_2025_Honey!
discovery:
server-addr: ${NACOS_HOST:honeycom-register}:${NACOS_PORT:8848}
namespace: @nacos.namespace@
namespace:
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: @nacos.namespace@
namespace:
config:
import:
- optional:classpath:/application-local.yml
- optional:nacos:application-@profiles.active@.yml
- optional:nacos:${spring.application.name}-@profiles.active@.yml
- optional:nacos:application-dev.yml
- optional:nacos:${spring.application.name}-dev.yml
springdoc:
api-docs:
@@ -33,4 +33,20 @@ springdoc:
tags-sorter: alpha
default-flat-param-object: false
cache:
disabled: true
disabled: true
proxy:
routes:
spdm-system:
base-url: http://192.168.65.161:7103
prefix: /honeycom-spdm/api/simulation/system
targetPrefix:
timeout: 5000
enabled: true
spdm-project:
base-url: http://192.168.65.161:7101
prefix: /honeycom-spdm/api/simulation/project
targetPrefix:
timeout: 5000
enabled: true