修复Rooming List生成并导入0718需求包

This commit is contained in:
andy
2026-07-18 13:15:35 +07:00
parent 0715e8334f
commit 0e94cc0722
22 changed files with 1367 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ import java.time.LocalDate;
* @param arrival 入住日期,酒店本地业务日期
* @param departure 离店日期,酒店本地业务日期
* @param roomType 目标 Excel 的 Room Type
* @param title 目标 Excel 的 Title
* @param rateCode 目标 Excel 的 Rate Code
* @param adults 目标 Excel 的 Adults为空时按当前分房人数派生
* @param children 目标 Excel 的 Children为空时默认 0
@@ -26,6 +27,7 @@ public record ReservationRoomingListGenerationRequest(
LocalDate arrival,
LocalDate departure,
String roomType,
String title,
String rateCode,
Integer adults,
Integer children,

View File

@@ -0,0 +1,124 @@
package cn.nianxx.thhotel.workflows.reservation.control;
import cn.nianxx.thhotel.platform.access.common.enums.PlatformPermissionCode;
import cn.nianxx.thhotel.platform.identity.service.AuthService;
import cn.nianxx.thhotel.platform.security.common.dto.AuthenticatedUserContext;
import cn.nianxx.thhotel.workflows.reservation.common.result.ReservationWorkflowErrorResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import org.springframework.core.Ordered;
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;
/**
* Rooming List 上传接口前置鉴权过滤器。先校验登录和权限,再允许 multipart 参数绑定。
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ReservationRoomingListGenerationAuthorizationFilter extends OncePerRequestFilter {
private static final String ENDPOINT = "/api/reservation/rooming-lists/generations";
private final AuthService authService;
private final ObjectMapper objectMapper;
/**
* 注入认证服务和 JSON 序列化器。
*/
public ReservationRoomingListGenerationAuthorizationFilter(
AuthService authService,
ObjectMapper objectMapper) {
this.authService = authService;
this.objectMapper = objectMapper;
}
/**
* 对 Rooming List 上传接口提前执行登录和权限校验,避免未登录请求进入 multipart 绑定。
*/
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!matchesRoomingListGeneration(request)) {
filterChain.doFilter(request, response);
return;
}
String authorizationHeader = request.getHeader("Authorization");
Optional<AuthenticatedUserContext> context = authService.resolveOptionalContext(authorizationHeader);
if (context.isEmpty()) {
writeError(
response,
HttpStatus.UNAUTHORIZED,
hasBearerToken(authorizationHeader) ? "AUTH_SESSION_INVALID" : "AUTH_TOKEN_REQUIRED",
hasBearerToken(authorizationHeader) ? "登录已失效,请重新登录。" : "请先登录后再访问该业务能力。");
return;
}
if (!context.get().permissionCodes()
.contains(PlatformPermissionCode.RESERVATION_ROOMING_LIST_GENERATE.name())) {
writeError(
response,
HttpStatus.FORBIDDEN,
"FRONTEND_PERMISSION_DENIED",
"当前用户没有访问该业务能力的权限。");
return;
}
filterChain.doFilter(request, response);
}
/**
* 判断当前请求是否为 Rooming List 生成接口。
*/
private boolean matchesRoomingListGeneration(HttpServletRequest request) {
if (!"POST".equalsIgnoreCase(request.getMethod())) {
return false;
}
String contextPath = request.getContextPath();
String requestUri = request.getRequestURI();
String path = requestUri;
if (contextPath != null && !contextPath.isBlank() && requestUri.startsWith(contextPath)) {
path = requestUri.substring(contextPath.length());
}
return ENDPOINT.equals(path);
}
/**
* 写入前端业务接口统一错误响应。
*/
private void writeError(
HttpServletResponse response,
HttpStatus status,
String errorCode,
String message) throws IOException {
response.setStatus(status.value());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(
response.getWriter(),
new ReservationWorkflowErrorResponse(errorCode, message, List.of()));
}
/**
* 判断请求头中是否携带非空 Bearer token。
*/
private boolean hasBearerToken(String authorizationHeader) {
if (authorizationHeader == null || authorizationHeader.isBlank()) {
return false;
}
String prefix = "Bearer ";
return authorizationHeader.regionMatches(true, 0, prefix, 0, prefix.length())
&& !authorizationHeader.substring(prefix.length()).trim().isBlank();
}
}

View File

@@ -49,6 +49,7 @@ public class ReservationRoomingListGenerationController {
@RequestParam("arrival") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate arrival,
@RequestParam("departure") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate departure,
@RequestParam("room_type") String roomType,
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "rate_code", required = false) String rateCode,
@RequestParam(value = "adults", required = false) Integer adults,
@RequestParam(value = "children", required = false) Integer children,
@@ -68,6 +69,7 @@ public class ReservationRoomingListGenerationController {
arrival,
departure,
roomType,
title,
rateCode,
adults,
children,

View File

@@ -113,6 +113,7 @@ public class ReservationRoomingListGenerationServiceImpl implements ReservationR
request.arrival(),
request.departure(),
trimToEmpty(request.roomType()),
trimToEmpty(request.title()),
trimToEmpty(request.rateCode()),
request.adults(),
request.children(),

View File

@@ -90,7 +90,7 @@ public class RoomingListExcelRenderer {
row.createCell(0).setCellValue(room.lineNumber());
row.createCell(1).setCellValue(room.primaryGuest().lastName());
row.createCell(2).setCellValue(room.primaryGuest().firstName());
row.createCell(3).setCellValue("");
row.createCell(3).setCellValue(defaultString(request.title()));
row.createCell(4).setCellValue(request.arrival().format(DateTimeFormatter.ISO_LOCAL_DATE));
row.createCell(5).setCellValue(request.departure().format(DateTimeFormatter.ISO_LOCAL_DATE));
row.createCell(6).setCellValue(defaultString(request.roomType()));