feat(appointment): integrate work calendar and stable cargo batches
This commit is contained in:
@@ -15,6 +15,10 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import com.baiyue.iam.service.PlatformOwnerDirectoryService;
|
||||
import com.baiyue.iam.dto.PlatformOwner;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
@@ -27,6 +31,7 @@ import java.security.MessageDigest;
|
||||
public class InternalAuthorizationController {
|
||||
@Resource
|
||||
private AuthorizationSnapshotService service;
|
||||
@Resource private PlatformOwnerDirectoryService owners;
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalTokenValue;
|
||||
private byte[] internalToken;
|
||||
@@ -49,4 +54,13 @@ public class InternalAuthorizationController {
|
||||
}
|
||||
return ApiResponse.success(service.load(request));
|
||||
}
|
||||
|
||||
@GetMapping("/platform-owners/{authUserId}")
|
||||
public ApiResponse<PlatformOwner> owner(@RequestHeader(value="X-Internal-Service-Token",required=false) String token,
|
||||
@PathVariable long authUserId) {
|
||||
byte[] supplied=token==null?new byte[0]:token.getBytes(StandardCharsets.UTF_8);
|
||||
if(internalToken==null || !MessageDigest.isEqual(internalToken,supplied))
|
||||
throw new BizException(ResultCode.FORBIDDEN,"无效的服务间凭据");
|
||||
return ApiResponse.success(owners.require(authUserId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.baiyue.iam.dto;
|
||||
|
||||
/** Auth subject identity, not the IAM profile or membership primary key. */
|
||||
public record PlatformOwner(String id,String name) { }
|
||||
@@ -11,6 +11,18 @@ import java.util.List;
|
||||
*/
|
||||
@Mapper
|
||||
public interface AuthorizationQueryMapper {
|
||||
@Select("""
|
||||
SELECT CONCAT('',u.auth_user_id) id,u.display_name name
|
||||
FROM iam_user u
|
||||
WHERE u.auth_user_id=#{authUserId} AND u.status='ACTIVE' AND u.deleted=0
|
||||
AND u.display_name IS NOT NULL AND TRIM(u.display_name)<>''
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM iam_membership m JOIN iam_membership_role mr ON mr.membership_id=m.id AND mr.tenant_id=0
|
||||
JOIN iam_role r ON r.id=mr.role_id AND r.tenant_id=0 AND r.app_code='PLATFORM' AND r.status='ACTIVE' AND r.deleted=0
|
||||
WHERE m.user_id=u.id AND m.tenant_id=0 AND m.status='ACTIVE' AND m.deleted=0)
|
||||
""")
|
||||
com.baiyue.iam.dto.PlatformOwner findActivePlatformOwner(@Param("authUserId") long authUserId);
|
||||
|
||||
@Select("""
|
||||
SELECT r.role_code AS roleCode, p.permission_code AS permissionCode,
|
||||
COALESCE(sv.permission_version,0) AS permissionVersion,
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.iam.service;
|
||||
|
||||
import com.baiyue.iam.dto.PlatformOwner;
|
||||
import com.baiyue.iam.mapper.AuthorizationQueryMapper;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PlatformOwnerDirectoryService {
|
||||
private final AuthorizationQueryMapper mapper;
|
||||
public PlatformOwnerDirectoryService(AuthorizationQueryMapper mapper){this.mapper=mapper;}
|
||||
public PlatformOwner require(long authUserId) {
|
||||
if(authUserId<=0) throw new BizException(ResultCode.BAD_REQUEST,"平台操作人员标识无效");
|
||||
var owner=mapper.findActivePlatformOwner(authUserId);
|
||||
if(owner==null) throw new BizException(ResultCode.NOT_FOUND,"未找到有效平台操作人员");
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
USE baiyue_iam;
|
||||
|
||||
-- Reject identity collisions before any permission or grant is changed.
|
||||
DROP PROCEDURE IF EXISTS check_appointment_work_permission_identity;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE check_appointment_work_permission_identity()
|
||||
BEGIN
|
||||
IF EXISTS(SELECT 1 FROM iam_permission WHERE
|
||||
(id=5107283 AND permission_code<>'platform:appointment:manage-work')
|
||||
OR (id=5102213 AND permission_code<>'platform:firstleg:terminal-drayage:direct-unload:confirm')
|
||||
OR (permission_code='platform:appointment:manage-work' AND id<>5107283)
|
||||
OR (permission_code='platform:firstleg:terminal-drayage:direct-unload:confirm' AND id<>5102213)) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Appointment work permission identity conflict';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL check_appointment_work_permission_identity();
|
||||
DROP PROCEDURE check_appointment_work_permission_identity;
|
||||
|
||||
-- Two independent write capabilities; appointment/list remains read-only.
|
||||
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
|
||||
(5107283,0,'PLATFORM','BUTTON',5107280,'platform:appointment:manage-work','维护预约工作安排',NULL,'POST',743,'ACTIVE',1,0),
|
||||
(5102213,0,'PLATFORM','BUTTON',5102200,'platform:firstleg:terminal-drayage:direct-unload:confirm','登记直送目的仓实际卸货',NULL,'POST',60,'ACTIVE',1,0)
|
||||
ON DUPLICATE KEY UPDATE permission_name=VALUES(permission_name),parent_id=VALUES(parent_id),status='ACTIVE',deleted=0;
|
||||
|
||||
INSERT IGNORE INTO iam_role_permission(tenant_id,role_id,permission_id,created_at)
|
||||
SELECT 0,r.id,p.id,CURRENT_TIMESTAMP(3)
|
||||
FROM iam_role r JOIN iam_permission p ON p.tenant_id=0 AND p.id IN (5107283,5102213)
|
||||
WHERE r.tenant_id=0 AND r.app_code='PLATFORM' AND r.role_code='PLATFORM_ADMIN' AND r.status='ACTIVE' AND r.deleted=0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_appointment_work_permissions;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_appointment_work_permissions()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM iam_permission WHERE tenant_id=0 AND app_code='PLATFORM' AND status='ACTIVE' AND deleted=0
|
||||
AND ((id=5107283 AND permission_code='platform:appointment:manage-work' AND parent_id=5107280)
|
||||
OR (id=5102213 AND permission_code='platform:firstleg:terminal-drayage:direct-unload:confirm' AND parent_id=5102200))) <> 2 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='Appointment work permission identity conflict';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_appointment_work_permissions();
|
||||
DROP PROCEDURE assert_appointment_work_permissions;
|
||||
@@ -17,6 +17,28 @@ tags:
|
||||
- name: RBAC
|
||||
description: Platform and merchant role-permission management / 平台端与商户端角色权限管理
|
||||
paths:
|
||||
/internal/iam/platform-owners/{authUserId}:
|
||||
get:
|
||||
summary: Read active platform operator identity and authoritative display name
|
||||
description: Internal service identity only. Auth subject ID is distinct from IAM profile and membership IDs. Requires active undeleted user, platform membership and PLATFORM role; no caller-supplied name fallback.
|
||||
security: []
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/InternalServiceToken'}
|
||||
- {name: authUserId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
responses:
|
||||
'200':
|
||||
description: ApiResponse
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
required: [id, name]
|
||||
properties: {id: {type: string}, name: {type: string}}
|
||||
'403': {description: Invalid internal service credential}
|
||||
'404': {description: No eligible active platform operator}
|
||||
/api/merchant/v1/iam/profile:
|
||||
get:
|
||||
tags: [Merchant Tenant / 商户企业]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.iam.controller;
|
||||
|
||||
import com.baiyue.iam.service.PlatformOwnerDirectoryService;
|
||||
import com.baiyue.iam.dto.PlatformOwner;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PlatformOwnerDirectoryControllerTest {
|
||||
@Test void internalIdentityIsRequiredBeforeReadingDirectory() {
|
||||
var service=mock(PlatformOwnerDirectoryService.class);var controller=new InternalAuthorizationController();
|
||||
String token="test-internal-token-at-least-32-characters";
|
||||
ReflectionTestUtils.setField(controller,"owners",service);ReflectionTestUtils.setField(controller,"internalTokenValue",token);
|
||||
controller.initializeInternalToken();
|
||||
assertThrows(BizException.class,()->controller.owner(null,101));verifyNoInteractions(service);
|
||||
when(service.require(101)).thenReturn(new PlatformOwner("101","运营甲"));
|
||||
assertEquals("运营甲",controller.owner(token,101).data().name());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.baiyue.iam.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
|
||||
import org.apache.ibatis.mapping.Environment;
|
||||
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
|
||||
import org.h2.jdbcx.JdbcDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.UUID;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PlatformOwnerDirectoryMapperTest {
|
||||
@Test void returnsAuthSubjectAndNameOnlyForActivePlatformMembershipAndRole() throws Exception {
|
||||
var ds=new JdbcDataSource();ds.setURL("jdbc:h2:mem:"+UUID.randomUUID()+";MODE=MySQL;DB_CLOSE_DELAY=-1");
|
||||
try(var c=ds.getConnection();var s=c.createStatement()) {
|
||||
s.execute("CREATE TABLE iam_user(id BIGINT,auth_user_id BIGINT,display_name VARCHAR,status VARCHAR,deleted INT)");
|
||||
s.execute("CREATE TABLE iam_membership(id BIGINT,user_id BIGINT,tenant_id BIGINT,status VARCHAR,deleted INT)");
|
||||
s.execute("CREATE TABLE iam_membership_role(membership_id BIGINT,tenant_id BIGINT,role_id BIGINT)");
|
||||
s.execute("CREATE TABLE iam_role(id BIGINT,tenant_id BIGINT,app_code VARCHAR,status VARCHAR,deleted INT)");
|
||||
s.execute("INSERT INTO iam_user VALUES(1,101,'运营甲','ACTIVE',0),(2,102,'客户乙','ACTIVE',0),(3,103,'停用丙','DISABLED',0),(4,104,'失效成员','ACTIVE',0)");
|
||||
s.execute("INSERT INTO iam_membership VALUES(11,1,0,'ACTIVE',0),(12,2,88,'ACTIVE',0),(13,3,0,'ACTIVE',0),(14,4,0,'DISABLED',0)");
|
||||
s.execute("INSERT INTO iam_membership_role VALUES(11,0,21),(12,88,21),(13,0,21),(14,0,21)");
|
||||
s.execute("INSERT INTO iam_role VALUES(21,0,'PLATFORM','ACTIVE',0)");
|
||||
}
|
||||
var config=new MybatisConfiguration();config.setEnvironment(new Environment("test",new JdbcTransactionFactory(),ds));config.addMapper(AuthorizationQueryMapper.class);
|
||||
try(var session=new MybatisSqlSessionFactoryBuilder().build(config).openSession()) {
|
||||
var mapper=session.getMapper(AuthorizationQueryMapper.class);
|
||||
assertEquals("101",mapper.findActivePlatformOwner(101).id());assertEquals("运营甲",mapper.findActivePlatformOwner(101).name());
|
||||
assertNull(mapper.findActivePlatformOwner(1));assertNull(mapper.findActivePlatformOwner(102));
|
||||
assertNull(mapper.findActivePlatformOwner(103));assertNull(mapper.findActivePlatformOwner(104));
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -150,6 +150,8 @@ class OverseasPhase4PermissionSqlContractTest {
|
||||
.getContentAsString(StandardCharsets.UTF_8)).stream()
|
||||
.filter(code -> code.startsWith("platform:"))
|
||||
.forEach(moduleCatalog::add);
|
||||
codes(new ClassPathResource("db/procurement-permissions.sql")
|
||||
.getContentAsString(StandardCharsets.UTF_8)).forEach(moduleCatalog::add);
|
||||
Set<String> serviceCatalog = new LinkedHashSet<>();
|
||||
serviceCatalog.addAll(codes(repositoryFile(
|
||||
"lastleg-service/src/main/java/com/baiyue/lastleg/security/OverseasPermissions.java")));
|
||||
|
||||
Reference in New Issue
Block a user