优化
This commit is contained in:
@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baiyue.common.
|
||||
public PageResult<TruckAppointmentEntity> page(Query q){return repo.page(q);} public TruckAppointmentEntity detail(long id){return required(id);}
|
||||
@Transactional public TruckAppointmentEntity create(long uid,String name,SaveRequest r){if(r.expectedVersion()!=0)throw bad("新增 expectedVersion 必须为0"); TruckAppointmentEntity e=new TruckAppointmentEntity(); e.setId(IdWorker.getId()); e.setAppointmentNo("TA"+e.getId()); e.setStatus(TruckAppointmentStatus.DRAFT); e.setRevision(0); e.setVersion(0); e.setCreatedAt(LocalDateTime.now()); e.setUpdatedAt(e.getCreatedAt()); copy(e,r); repo.insert(e); record(e,null,e.getStatus().name(),"CREATE",null,uid,name); return e;}
|
||||
@Transactional public TruckAppointmentEntity edit(long id,long uid,String name,SaveRequest r){TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if(!(e.getStatus()==TruckAppointmentStatus.DRAFT||e.getStatus()==TruckAppointmentStatus.PENDING_DISPATCH||e.getStatus()==TruckAppointmentStatus.RESCHEDULED))throw conflict("当前状态不可编辑"); copy(e,r); if(repo.updateEdit(e,r.expectedVersion())!=1)throw conflict("版本已变化,请刷新后重试"); record(e,e.getStatus().name(),e.getStatus().name(),"EDIT",null,uid,name); return required(id);}
|
||||
@Transactional public TruckAppointmentEntity action(long id,String action,ActionRequest r,String idem,String hash,long uid,String name){TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); TruckAppointmentStatus from=e.getStatus(),to=next(from,action); if("RESCHEDULE".equals(action)){throw conflict("改约请使用专用接口");} if(repo.transition(id,from.name(),to.name(),e.getVersion())!=1)throw conflict("状态或版本已变化,请刷新后重试"); e.setStatus(to);e.setVersion(e.getVersion()+1);e.setRevision(e.getRevision()+1); record(e,from.name(),to.name(),action,r.reason(),uid,name); operation(e,action,idem,hash); return e;}
|
||||
@Transactional public TruckAppointmentEntity action(long id,String action,ActionRequest r,String idem,String hash,long uid,String name){TruckAppointmentEntity e=required(id); checkVersion(e,r.expectedVersion()); if("RESCHEDULE".equals(action)){throw conflict("改约请使用专用接口");} TruckAppointmentStatus from=e.getStatus(),to=next(from,action); if(repo.transition(id,from.name(),to.name(),e.getVersion())!=1)throw conflict("状态或版本已变化,请刷新后重试"); e.setStatus(to);e.setVersion(e.getVersion()+1);e.setRevision(e.getRevision()+1); record(e,from.name(),to.name(),action,r.reason(),uid,name); operation(e,action,idem,hash); return e;}
|
||||
@Transactional public TruckAppointmentEntity reschedule(long id,LocalDateTime at,ActionRequest r,long uid,String name){TruckAppointmentEntity e=required(id);checkVersion(e,r.expectedVersion()); if(e.getStatus()==TruckAppointmentStatus.COMPLETED||e.getStatus()==TruckAppointmentStatus.CANCELLED)throw conflict("当前状态不可改约"); String from=e.getStatus().name(); if(repo.reschedule(id,at,r.reason(),e.getVersion())!=1)throw conflict("状态或版本已变化,请刷新后重试");e.setStatus(TruckAppointmentStatus.RESCHEDULED);e.setPlannedLoadingAt(at);e.setVersion(e.getVersion()+1);e.setRevision(e.getRevision()+1);record(e,from,"RESCHEDULED","RESCHEDULE",r.reason(),uid,name);return e;}
|
||||
private TruckAppointmentStatus next(TruckAppointmentStatus s,String a){return switch(a){case"SUBMIT"->s==TruckAppointmentStatus.DRAFT?TruckAppointmentStatus.PENDING_DISPATCH:badStatus();case"DISPATCH"->s==TruckAppointmentStatus.PENDING_DISPATCH||s==TruckAppointmentStatus.RESCHEDULED?TruckAppointmentStatus.DISPATCHED:badStatus();case"PICKUP"->s==TruckAppointmentStatus.DISPATCHED?TruckAppointmentStatus.PICKUP_IN_PROGRESS:badStatus();case"ARRIVE"->s==TruckAppointmentStatus.PICKUP_IN_PROGRESS?TruckAppointmentStatus.ARRIVED_WAREHOUSE:badStatus();case"LOAD"->s==TruckAppointmentStatus.ARRIVED_WAREHOUSE?TruckAppointmentStatus.LOADING:badStatus();case"DEPART"->s==TruckAppointmentStatus.LOADING?TruckAppointmentStatus.DEPARTED:badStatus();case"COMPLETE"->s==TruckAppointmentStatus.DEPARTED?TruckAppointmentStatus.COMPLETED:badStatus();case"EXCEPTION"->s!=TruckAppointmentStatus.COMPLETED&&s!=TruckAppointmentStatus.CANCELLED?TruckAppointmentStatus.EXCEPTION:badStatus();case"CANCEL"->s!=TruckAppointmentStatus.COMPLETED?TruckAppointmentStatus.CANCELLED:badStatus();default->badStatus();};}
|
||||
private TruckAppointmentStatus badStatus(){throw conflict("不允许的状态动作");} private TruckAppointmentEntity required(long id){TruckAppointmentEntity e=repo.find(id);if(e==null)throw new BizException(ResultCode.NOT_FOUND,"拖车预约不存在");return e;} private void checkVersion(TruckAppointmentEntity e,int v){if(e.getVersion()!=v)throw conflict("版本已变化,请刷新后重试");} private static BizException conflict(String m){return new BizException(ResultCode.CONFLICT,m);} private static BizException bad(String m){return new BizException(ResultCode.BAD_REQUEST,m);}
|
||||
|
||||
@@ -76,23 +76,3 @@ SELECT role.id,permission.id,0 FROM iam_role role JOIN iam_permission permission
|
||||
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;
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import com.baiyue.firstleg.controller.PlatformTruckAppointmentController;
|
||||
import com.baiyue.firstleg.oceanbooking.controller.PlatformOceanBookingController;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class Phase3BookingTruckPermissionSqlContractTest {
|
||||
private static final Pattern AUTHORITY = Pattern.compile("hasAuthority\\('([^']+)'\\)");
|
||||
private static final Pattern OCEAN_CODE = Pattern.compile("platform:firstleg:ocean-booking:[a-z-]+");
|
||||
private static final Pattern TRUCK_CODE = Pattern.compile("platform:firstleg:truck-appointment:[a-z-]+");
|
||||
|
||||
private static final Set<String> OCEAN_PERMISSIONS = Set.of(
|
||||
"platform:firstleg:ocean-booking:list",
|
||||
"platform:firstleg:ocean-booking:view",
|
||||
"platform:firstleg:ocean-booking:create",
|
||||
"platform:firstleg:ocean-booking:edit",
|
||||
"platform:firstleg:ocean-booking:submit",
|
||||
"platform:firstleg:ocean-booking:confirm",
|
||||
"platform:firstleg:ocean-booking:release",
|
||||
"platform:firstleg:ocean-booking:cancel",
|
||||
"platform:firstleg:ocean-booking:change",
|
||||
"platform:firstleg:ocean-booking:allocate");
|
||||
|
||||
private static final Set<String> TRUCK_PERMISSIONS = Set.of(
|
||||
"platform:firstleg:truck-appointment:list",
|
||||
"platform:firstleg:truck-appointment:view",
|
||||
"platform:firstleg:truck-appointment:create",
|
||||
"platform:firstleg:truck-appointment:edit",
|
||||
"platform:firstleg:truck-appointment:operate");
|
||||
|
||||
@Test
|
||||
void controllerAuthoritiesExactlyMatchModuleAndUnifiedPermissionCodes() throws Exception {
|
||||
assertThat(authorities(PlatformOceanBookingController.class)).isEqualTo(OCEAN_PERMISSIONS);
|
||||
assertThat(authorities(PlatformTruckAppointmentController.class)).isEqualTo(TRUCK_PERMISSIONS);
|
||||
|
||||
String oceanModule = resource("db/phase3-ocean-booking-schema.sql");
|
||||
String truckModule = resource("db/phase3-truck-appointment-schema.sql");
|
||||
String unified = unifiedSql();
|
||||
assertThat(codes(oceanModule, OCEAN_CODE)).isEqualTo(OCEAN_PERMISSIONS);
|
||||
assertThat(codes(truckModule, TRUCK_CODE)).isEqualTo(TRUCK_PERMISSIONS);
|
||||
assertThat(codes(unified, OCEAN_CODE)).isEqualTo(OCEAN_PERMISSIONS);
|
||||
assertThat(codes(unified, TRUCK_CODE)).isEqualTo(TRUCK_PERMISSIONS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unifiedSqlLocksCanonicalIdsScopeParentsAndRoleGrants() throws Exception {
|
||||
String sql = unifiedSql();
|
||||
assertThat(sql)
|
||||
.contains("UNIQUE KEY uk_permission_scope_code (tenant_id, app_code, permission_code)")
|
||||
.contains("CREATE PROCEDURE assert_phase3_booking_truck_permission_ids_available()")
|
||||
.contains("三期订舱/拖车权限迁移失败:5102140-5102154 ID 或权限码被其他语义占用")
|
||||
.contains("(5102140,0,'PLATFORM','MENU',5102000,'platform:firstleg:ocean-booking:list'")
|
||||
.contains("(5102149,0,'PLATFORM','BUTTON',5102140,'platform:firstleg:ocean-booking:allocate'")
|
||||
.contains("(5102150,0,'PLATFORM','MENU',5102000,'platform:firstleg:truck-appointment:list'")
|
||||
.contains("(5102154,0,'PLATFORM','BUTTON',5102150,'platform:firstleg:truck-appointment:operate'")
|
||||
.contains("role.role_code IN ('PLATFORM_ADMIN','ORDER_OPERATOR')")
|
||||
.contains("CREATE PROCEDURE assert_phase3_booking_truck_permissions()")
|
||||
.contains("统一迁移失败:三期订舱/拖车 15 项权限映射不完整")
|
||||
.contains("统一迁移失败:三期订舱/拖车角色授权不完整")
|
||||
.contains("CALL assert_phase3_booking_truck_permissions()")
|
||||
.contains("DROP PROCEDURE IF EXISTS assert_phase3_booking_truck_permissions;");
|
||||
}
|
||||
|
||||
private static Set<String> authorities(Class<?> controller) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
Arrays.stream(controller.getDeclaredMethods())
|
||||
.map(method -> method.getAnnotation(PreAuthorize.class))
|
||||
.filter(annotation -> annotation != null)
|
||||
.map(PreAuthorize::value)
|
||||
.forEach(expression -> {
|
||||
Matcher matcher = AUTHORITY.matcher(expression);
|
||||
assertThat(matcher.find()).as(expression).isTrue();
|
||||
result.add(matcher.group(1));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Set<String> codes(String sql, Pattern pattern) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
Matcher matcher = pattern.matcher(sql);
|
||||
while (matcher.find()) {
|
||||
result.add(matcher.group());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String resource(String path) throws Exception {
|
||||
return new ClassPathResource(path).getContentAsString(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static String unifiedSql() throws Exception {
|
||||
Path path = Path.of("deploy/sql/头程数据库统一增量-MySQL8.sql");
|
||||
if (!Files.exists(path)) {
|
||||
path = Path.of("../deploy/sql/头程数据库统一增量-MySQL8.sql").normalize();
|
||||
}
|
||||
return Files.readString(path, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.baiyue.firstleg.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ServiceCenterUnifiedSqlContractTest {
|
||||
@Test
|
||||
void unifiedMigrationContainsServiceCenterSchemaAndGrants() throws Exception {
|
||||
Path working = Path.of(System.getProperty("user.dir"));
|
||||
Path path = working.resolve("deploy/sql/头程数据库统一增量-MySQL8.sql");
|
||||
if (!Files.exists(path)) {
|
||||
path = working.resolve("../deploy/sql/头程数据库统一增量-MySQL8.sql").normalize();
|
||||
}
|
||||
String sql = Files.readString(path, StandardCharsets.UTF_8);
|
||||
|
||||
assertThat(sql)
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_party")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_requirement")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_container_allocation")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_status_history")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_ocean_booking_operation")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment_history")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment_attachment")
|
||||
.contains("CREATE TABLE IF NOT EXISTS fl_truck_appointment_operation")
|
||||
.contains("5102140")
|
||||
.contains("5102149")
|
||||
.contains("5102150")
|
||||
.contains("5102154")
|
||||
.contains("PLATFORM_ADMIN")
|
||||
.contains("ORDER_OPERATOR")
|
||||
.contains("assert_phase3_service_center_schema()")
|
||||
.contains("CALL assert_phase3_service_center_schema();");
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class TruckAppointmentServiceTest {
|
||||
when(repository.find(1L)).thenReturn(e);
|
||||
when(repository.transition(1L, "DRAFT", "PENDING_DISPATCH", 0)).thenReturn(1);
|
||||
|
||||
var result = service.action(1L, "submit", new ActionRequest(0, "提交"), "idem-1", "hash-1", 7L, "admin");
|
||||
var result = service.action(1L, "SUBMIT", new ActionRequest(0, "提交"), "idem-1", "hash-1", 7L, "admin");
|
||||
|
||||
assertThat(result.getStatus()).isEqualTo(TruckAppointmentStatus.PENDING_DISPATCH);
|
||||
assertThat(result.getVersion()).isEqualTo(1);
|
||||
|
||||
Reference in New Issue
Block a user