This commit is contained in:
2026-08-08 14:19:45 +08:00
parent 87c1bfbb71
commit 13a2b90e98
6 changed files with 304 additions and 5 deletions
@@ -197,6 +197,36 @@ BEGIN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='订舱状态历史不可删除';
DELIMITER ;
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_ocean_booking_permission_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_ocean_booking_permission_ids_available()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id BETWEEN 5102140 AND 5102149
AND permission_code <> CASE id
WHEN 5102140 THEN 'platform:firstleg:ocean-booking:list'
WHEN 5102141 THEN 'platform:firstleg:ocean-booking:view'
WHEN 5102142 THEN 'platform:firstleg:ocean-booking:create'
WHEN 5102143 THEN 'platform:firstleg:ocean-booking:edit'
WHEN 5102144 THEN 'platform:firstleg:ocean-booking:submit'
WHEN 5102145 THEN 'platform:firstleg:ocean-booking:confirm'
WHEN 5102146 THEN 'platform:firstleg:ocean-booking:release'
WHEN 5102147 THEN 'platform:firstleg:ocean-booking:cancel'
WHEN 5102148 THEN 'platform:firstleg:ocean-booking:change'
WHEN 5102149 THEN 'platform:firstleg:ocean-booking:allocate'
END)
OR (permission_code LIKE 'platform:firstleg:ocean-booking:%'
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102140 AND 5102149))
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '三期订舱权限迁移失败:5102140-5102149 ID 或权限码被其他语义占用';
END IF;
END$$
DELIMITER ;
CALL assert_ocean_booking_permission_ids_available();
DROP PROCEDURE IF EXISTS assert_ocean_booking_permission_ids_available;
INSERT INTO iam_permission
(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name,
route_path,http_method,sort_no,status,built_in,deleted)
@@ -211,7 +241,8 @@ VALUES
(5102147,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:cancel','取消订舱',NULL,'POST',70,'ACTIVE',1,0),
(5102148,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:change','变更船期',NULL,'POST',80,'ACTIVE',1,0),
(5102149,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:allocate','分配柜次',NULL,'POST',90,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name),parent_id=VALUES(parent_id),
ON DUPLICATE KEY UPDATE tenant_id=0,app_code='PLATFORM',permission_type=VALUES(permission_type),
permission_code=VALUES(permission_code),permission_name=VALUES(permission_name),parent_id=VALUES(parent_id),
route_path=VALUES(route_path),http_method=VALUES(http_method),sort_no=VALUES(sort_no),status='ACTIVE',built_in=1,deleted=0;
INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id)
@@ -29,3 +29,70 @@ CREATE TABLE IF NOT EXISTS fl_truck_appointment_operation (
id BIGINT UNSIGNED NOT NULL, appointment_id BIGINT UNSIGNED NOT NULL, idempotency_key VARCHAR(128) NOT NULL, request_hash CHAR(64) NOT NULL,
action VARCHAR(32) NOT NULL, response_snapshot JSON NULL, created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), PRIMARY KEY(id), UNIQUE KEY uk_truck_operation(idempotency_key), KEY idx_truck_operation_appointment(appointment_id)
) ENGINE=InnoDB COMMENT='拖车操作幂等记录';
USE baiyue_iam;
DROP PROCEDURE IF EXISTS assert_truck_appointment_permission_ids_available;
DELIMITER $$
CREATE PROCEDURE assert_truck_appointment_permission_ids_available()
BEGIN
IF EXISTS (
SELECT 1 FROM iam_permission
WHERE (id BETWEEN 5102150 AND 5102154
AND permission_code <> CASE id
WHEN 5102150 THEN 'platform:firstleg:truck-appointment:list'
WHEN 5102151 THEN 'platform:firstleg:truck-appointment:view'
WHEN 5102152 THEN 'platform:firstleg:truck-appointment:create'
WHEN 5102153 THEN 'platform:firstleg:truck-appointment:edit'
WHEN 5102154 THEN 'platform:firstleg:truck-appointment:operate'
END)
OR (permission_code LIKE 'platform:firstleg:truck-appointment:%'
AND (tenant_id <> 0 OR app_code <> 'PLATFORM' OR id NOT BETWEEN 5102150 AND 5102154))
) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '三期拖车权限迁移失败:5102150-5102154 ID 或权限码被其他语义占用';
END IF;
END$$
DELIMITER ;
CALL assert_truck_appointment_permission_ids_available();
DROP PROCEDURE IF EXISTS assert_truck_appointment_permission_ids_available;
INSERT INTO iam_permission
(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name,
route_path,http_method,sort_no,status,built_in,deleted)
VALUES
(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list','拖车预约','/first-leg/truck-appointments','GET',90,'ACTIVE',1,0),
(5102151,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:view','查看拖车预约',NULL,'GET',10,'ACTIVE',1,0),
(5102152,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:create','创建拖车预约',NULL,'POST',20,'ACTIVE',1,0),
(5102153,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:edit','编辑拖车预约',NULL,'PUT',30,'ACTIVE',1,0),
(5102154,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:operate','操作拖车预约',NULL,'POST',40,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE tenant_id=0,app_code='PLATFORM',permission_type=VALUES(permission_type),
permission_code=VALUES(permission_code),permission_name=VALUES(permission_name),parent_id=VALUES(parent_id),
route_path=VALUES(route_path),http_method=VALUES(http_method),sort_no=VALUES(sort_no),status='ACTIVE',built_in=1,deleted=0;
INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id)
SELECT role.id,permission.id,0 FROM iam_role role JOIN iam_permission permission
ON permission.tenant_id=0 AND permission.app_code='PLATFORM'
AND permission.permission_code LIKE 'platform:firstleg:truck-appointment:%'
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
USE baiyue_firstleg;
USE baiyue_iam;
INSERT INTO iam_permission
(id,tenant_id,app_code,permission_type,parent_id,permission_code,permission_name,
route_path,http_method,sort_no,status,built_in,deleted)
VALUES
(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list','拖车预约','/trucking','GET',90,'ACTIVE',1,0),
(5102151,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:view','查看拖车预约',NULL,'GET',10,'ACTIVE',1,0),
(5102152,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:create','创建拖车预约',NULL,'POST',20,'ACTIVE',1,0),
(5102153,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:edit','编辑拖车预约',NULL,'PUT',30,'ACTIVE',1,0),
(5102154,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:operate','操作拖车状态',NULL,'POST',40,'ACTIVE',1,0)
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name),parent_id=VALUES(parent_id),
route_path=VALUES(route_path),http_method=VALUES(http_method),sort_no=VALUES(sort_no),status='ACTIVE',built_in=1,deleted=0;
INSERT IGNORE INTO iam_role_permission(role_id,permission_id,tenant_id)
SELECT role.id,permission.id,0 FROM iam_role role JOIN iam_permission permission
ON permission.tenant_id=0 AND permission.permission_code LIKE 'platform:firstleg:truck-appointment:%'
WHERE role.tenant_id=0 AND role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR');
USE baiyue_firstleg;
@@ -1,6 +1,31 @@
package com.baiyue.firstleg.controller;
import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.security.access.prepost.PreAuthorize; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.TruckAppointmentDtos.ActionRequest;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.RequestHeader;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class TruckAppointmentControllerContractTest {
@Test void exposesDedicatedEndpointAndPermissions() {String p=PlatformTruckAppointmentController.class.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class).value()[0];assertThat(p).isEqualTo("/api/platform/v1/truck-appointments"); for(String n:new String[]{"page","detail","create","edit","action","reschedule"}) assertThat(java.util.Arrays.stream(PlatformTruckAppointmentController.class.getDeclaredMethods()).anyMatch(m->m.getName().equals(n))).isTrue();}
@Test void schemaHasAggregateTablesAndStates() throws Exception {String s=new ClassPathResource("db/phase3-truck-appointment-schema.sql").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("fl_truck_appointment","fl_truck_appointment_history","fl_truck_appointment_attachment","fl_truck_appointment_operation","PENDING_DISPATCH","PICKUP_IN_PROGRESS","ARRIVED_WAREHOUSE","RESCHEDULED");}
@Test void methodsHavePlatformPermissionsAndAuthenticatedScope() {
Map<String,String> expected=Map.of("page","platform:firstleg:truck-appointment:list","detail","platform:firstleg:truck-appointment:view","create","platform:firstleg:truck-appointment:create","edit","platform:firstleg:truck-appointment:edit","action","platform:firstleg:truck-appointment:operate","reschedule","platform:firstleg:truck-appointment:operate");
for (Method m: PlatformTruckAppointmentController.class.getDeclaredMethods()) { if (!expected.containsKey(m.getName())) continue; assertThat(m.getAnnotation(PreAuthorize.class).value()).isEqualTo("hasAuthority('"+expected.get(m.getName())+"')"); assertThat(java.util.Arrays.stream(m.getParameterAnnotations()).anyMatch(a -> java.util.Arrays.stream(a).anyMatch(x -> x.annotationType()==AuthenticationPrincipal.class))).isTrue(); }
}
@Test void actionKeepsOptionalIdempotencyHeaders() throws Exception {
Method action=PlatformTruckAppointmentController.class.getDeclaredMethod("action", AuthenticatedUser.class,long.class,String.class,String.class,String.class,ActionRequest.class);
RequestHeader[] headers=java.util.Arrays.stream(action.getParameterAnnotations()).flatMap(java.util.Arrays::stream).filter(a -> a instanceof RequestHeader).map(a -> (RequestHeader)a).toArray(RequestHeader[]::new);
assertThat(java.util.Arrays.stream(headers).map(RequestHeader::value)).contains("Idempotency-Key","X-Request-Hash"); assertThat(java.util.Arrays.stream(headers).allMatch(h -> !h.required())).isTrue();
assertThat(PlatformTruckAppointmentController.class.getDeclaredMethod("reschedule", AuthenticatedUser.class,long.class,LocalDateTime.class,ActionRequest.class)).isNotNull();
}
}
@@ -14,11 +14,11 @@ class TruckAppointmentMapperContractTest {
assertThat(TruckAppointmentEntity.class.getAnnotation(TableName.class).value()).isEqualTo("fl_truck_appointment");
String page = TruckAppointmentMapper.class.getMethod("page", com.baomidou.mybatisplus.extension.plugins.pagination.Page.class, com.baiyue.firstleg.dto.TruckAppointmentDtos.Query.class).getAnnotation(Select.class).value()[0];
assertThat(page).contains("fl_truck_appointment", "appointment_no", "vehicle_no", "status=#{q.status}", "container_booking_id=#{q.containerBookingId}");
String transition = TruckAppointmentMapper.class.getMethod("transition", long.class, String.class, String.class, int.class).getAnnotation(Update.class).value();
String transition = TruckAppointmentMapper.class.getMethod("transition", long.class, String.class, String.class, int.class).getAnnotation(Update.class).value()[0];
assertThat(transition).contains("status=#{toStatus}", "status=#{fromStatus}", "version=#{version}", "revision=revision+1", "version=version+1");
String reschedule = TruckAppointmentMapper.class.getMethod("reschedule", long.class, java.time.LocalDateTime.class, String.class, int.class).getAnnotation(Update.class).value();
String reschedule = TruckAppointmentMapper.class.getMethod("reschedule", long.class, java.time.LocalDateTime.class, String.class, int.class).getAnnotation(Update.class).value()[0];
assertThat(reschedule).contains("status='RESCHEDULED'", "version=#{version}", "revision=revision+1");
String edit = TruckAppointmentMapper.class.getMethod("updateEdit", TruckAppointmentEntity.class, int.class).getAnnotation(Update.class).value();
String edit = TruckAppointmentMapper.class.getMethod("updateEdit", TruckAppointmentEntity.class, int.class).getAnnotation(Update.class).value()[0];
assertThat(edit).contains("WHERE id=#{e.id} AND version=#{version}", "version=version+1", "revision=revision+1");
}
}