Compare commits
12 Commits
5e1e50479a
...
c5eea01e0c
| Author | SHA1 | Date |
|---|---|---|
|
|
c5eea01e0c | |
|
|
201181ca27 | |
|
|
95a7473f66 | |
|
|
8778f6f107 | |
|
|
10d2846b98 | |
|
|
f1a33d65b0 | |
|
|
35b8a56feb | |
|
|
d1853c9b27 | |
|
|
d268b32dda | |
|
|
3fd3530f55 | |
|
|
2da8637d4b | |
|
|
9b5adb3794 |
|
|
@ -5,6 +5,7 @@ import com.pms.audit.dto.AuditEventMessage;
|
|||
import com.pms.audit.service.AuditLogService;
|
||||
import com.pms.common.entity.MqConsumeLog;
|
||||
import com.pms.common.mapper.MqConsumeLogMapper;
|
||||
import com.pms.common.security.SystemUserSecurityContext;
|
||||
import com.pms.common.util.JsonUtils;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -34,7 +35,33 @@ public class AuditConsumer {
|
|||
private final MqConsumeLogMapper mqConsumeLogMapper;
|
||||
|
||||
/**
|
||||
* 消费操作审计日志
|
||||
* 消费操作审计日志。
|
||||
* <p>
|
||||
* <b>Trust Boundary(P2 #12 文档化)</b>:本方法是 MQ ingress 信任边界。
|
||||
* 消息来源为各服务 AuditLogAspect AOP 切面({@code com.pms.common.aspect.AuditLogAspect}),
|
||||
* 通过 RabbitMQ 内部集群({@code spring.rabbitmq.host},不对外暴露)发送。
|
||||
* <p>
|
||||
* 信任假设(不在此处校验,由 AOP 切面 + MQ 网络隔离保证):
|
||||
* <ul>
|
||||
* <li>{@code operatorId}:来自 {@link com.pms.common.security.UserContext},由
|
||||
* {@code HeaderAuthenticationFilter} 从 JWT claims 解析,已通过认证</li>
|
||||
* <li>{@code module}/{@code operation}:由 {@code @AuditLog} 注解静态指定,编译期确定</li>
|
||||
* <li>{@code operationTime}:由 AOP 切面在请求线程生成,非用户输入</li>
|
||||
* <li>{@code requestId}:由 MDC 生成,非用户输入</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 不在此处做 payload 校验的原因:
|
||||
* <ol>
|
||||
* <li>MQ 集群不对外暴露,外部无法注入恶意 payload</li>
|
||||
* <li>AuditLogAspect 是唯一生产者,payload 结构由代码保证</li>
|
||||
* <li>校验逻辑会与 AuditLogAspect 重复,违反 DRY</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* 升级路径:若未来 MQ 集群对外暴露(如跨集群联邦),需在此处增加 payload 校验。
|
||||
* 当前依赖 {@code spring.rabbitmq.host} 配置为内网地址 + 防火墙规则隔离。
|
||||
* <p>
|
||||
* 异常路径:JSON 解析失败 → ACK 丢弃(消息格式错误,重试无意义);
|
||||
* 业务异常 → NACK 不重试(避免毒消息循环)。
|
||||
*/
|
||||
@RabbitListener(queues = AuditConstants.QUEUE_AUDIT_OPERATION)
|
||||
public void onAuditOperation(String message, Channel channel,
|
||||
|
|
@ -70,7 +97,12 @@ public class AuditConsumer {
|
|||
return;
|
||||
}
|
||||
|
||||
auditLogService.save(event);
|
||||
// R4 缓解策略 (1)(2)(3):MQ 消费者入口建立 system-user SecurityContext
|
||||
// 使 AuditLogServiceImpl.save() 的 @PreAuthorize admin 旁路生效
|
||||
// try-with-resources 自动清理,避免 RabbitMQ 线程池线程复用导致权限泄漏
|
||||
try (SystemUserSecurityContext ctx = SystemUserSecurityContext.system()) {
|
||||
auditLogService.save(event);
|
||||
}
|
||||
|
||||
// 3. 消费成功后记录到 mq_consume_log(持久化保障)
|
||||
MqConsumeLog consumeLog = new MqConsumeLog();
|
||||
|
|
|
|||
|
|
@ -17,14 +17,13 @@ import java.util.List;
|
|||
* 审计日志控制器
|
||||
* 路径前缀: /api/v1/audit/logs
|
||||
* <p>
|
||||
* 复用 pms-audit 现有 AuditLogService 查询能力,仅补充 @PreAuthorize 方法级鉴权。
|
||||
* 查询权限 system:audit:view 由 V8 migration 注入,关联至 ROLE_ADMIN。
|
||||
* PR2-PR4 改造:类级 @PreAuthorize 拆分为方法级 hasFunction('system_management'),
|
||||
* 对齐 PR1 charge 模式(KTD1:方法级标注,类级不加)。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/audit/logs")
|
||||
@RequiredArgsConstructor
|
||||
@PreAuthorize("hasAuthority('system:audit:view')")
|
||||
public class AuditLogController {
|
||||
|
||||
private final AuditLogService auditLogService;
|
||||
|
|
@ -33,6 +32,7 @@ public class AuditLogController {
|
|||
* 审计日志列表(分页,多条件筛选)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<PageResult<AuditLogDTO>> list(AuditLogQueryRequest request) {
|
||||
return Result.success(auditLogService.page(request));
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ public class AuditLogController {
|
|||
* 审计日志详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<AuditLogDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(auditLogService.getById(id));
|
||||
}
|
||||
|
|
@ -49,6 +50,7 @@ public class AuditLogController {
|
|||
* 导出审计日志
|
||||
*/
|
||||
@GetMapping("/export")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<List<AuditLogDTO>> export(AuditLogQueryRequest request) {
|
||||
return Result.success(auditLogService.export(request));
|
||||
}
|
||||
|
|
@ -57,6 +59,7 @@ public class AuditLogController {
|
|||
* 操作统计
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<AuditLogStatisticsDTO> statistics() {
|
||||
return Result.success(auditLogService.statistics());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,14 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.response.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 登录日志控制器
|
||||
* 路径前缀: /api/v1/audit/login-logs
|
||||
* <p>
|
||||
* PR2-PR4 改造:方法级 hasFunction('system_management'),对齐 PR1 charge 模式(KTD1)。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -25,6 +28,7 @@ public class LoginLogController {
|
|||
* 登录日志列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<PageResult<LoginLogDTO>> list(LoginLogQueryRequest request) {
|
||||
return Result.success(loginLogService.page(request));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,15 @@ import com.pms.common.constant.CommonConstants;
|
|||
import com.pms.common.exception.BusinessException;
|
||||
import com.pms.common.exception.ErrorCode;
|
||||
import com.pms.common.response.PageResult;
|
||||
import com.pms.common.security.CustomMethodSecurityExpressionRoot;
|
||||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -146,6 +149,8 @@ public class AuditLogServiceImpl implements AuditLogService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:audit:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void save(AuditEventMessage message) {
|
||||
AuditLog auditLog = new AuditLog();
|
||||
auditLog.setLogNo(generateLogNo());
|
||||
|
|
@ -186,13 +191,17 @@ public class AuditLogServiceImpl implements AuditLogService {
|
|||
|
||||
/**
|
||||
* U10: 检查当前用户是否拥有 system:audit:view:all 权限(跨项目查询)
|
||||
* <p>
|
||||
* PR2 改造(OQ1 决策):admin 旁路 — 与 hasAuthorityAndAdmin 语义一致,
|
||||
* admin 用户(principal.getFunctions() 含 "admin")直接返回 true,便于跨项目调查安全事件。
|
||||
*/
|
||||
private boolean canViewAllProjects() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (auth == null) {
|
||||
return false;
|
||||
// admin 旁路(OQ1 决策):复用 CustomMethodSecurityExpressionRoot.isAdmin,与 SpEL 路径同源
|
||||
if (CustomMethodSecurityExpressionRoot.isAdmin(auth)) {
|
||||
return true;
|
||||
}
|
||||
return auth.getAuthorities().stream()
|
||||
return auth != null && auth.getAuthorities().stream()
|
||||
.anyMatch(a -> "system:audit:view:all".equals(a.getAuthority()));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
package com.pms.audit.consumer;
|
||||
|
||||
import com.pms.audit.dto.AuditEventMessage;
|
||||
import com.pms.audit.service.AuditLogService;
|
||||
import com.pms.common.entity.MqConsumeLog;
|
||||
import com.pms.common.mapper.MqConsumeLogMapper;
|
||||
import com.pms.common.util.JsonUtils;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessageProperties;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* AuditConsumer 单元测试(ce-debug P1 #4)
|
||||
* <p>
|
||||
* 覆盖场景:
|
||||
* <ul>
|
||||
* <li>Happy path: 新事件 → save() 调用 + consumeLog 插入 + ACK</li>
|
||||
* <li>幂等命中(Redis): SETNX false → ACK 不处理</li>
|
||||
* <li>幂等命中(DB): count > 0 → ACK 不处理</li>
|
||||
* <li>异常: JSON 解析 null → ACK 丢弃</li>
|
||||
* <li>异常: save() 抛异常 → NACK + SecurityContext 清理(try-with-resources 验证)</li>
|
||||
* <li>eventId 缺失:fallback 到 operatorId:operationTime 拼装 → save() 调用</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 设计依据:ce-code-review P1 #4 — AuditConsumer 为 R4 system-user admin 旁路核心入口,
|
||||
* 0 测试覆盖。此测试为 ponytail runnable check,验证 MQ 消费 → SystemUserSecurityContext →
|
||||
* save() → 幂等/异常路径完整闭环。
|
||||
*/
|
||||
@DisplayName("AuditConsumer 单元测试(R4 system-user admin 旁路入口)")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class AuditConsumerTest {
|
||||
|
||||
@Mock
|
||||
private AuditLogService auditLogService;
|
||||
@Mock
|
||||
private StringRedisTemplate redisTemplate;
|
||||
@Mock
|
||||
private ValueOperations<String, String> valueOperations;
|
||||
@Mock
|
||||
private MqConsumeLogMapper mqConsumeLogMapper;
|
||||
@Mock
|
||||
private Channel channel;
|
||||
@Mock
|
||||
private Message amqpMessage;
|
||||
|
||||
@InjectMocks
|
||||
private AuditConsumer consumer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
MessageProperties props = new MessageProperties();
|
||||
props.setDeliveryTag(1L);
|
||||
when(amqpMessage.getMessageProperties()).thenReturn(props);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Happy path: 新事件 → save() 调用 + consumeLog 插入 + ACK")
|
||||
void shouldSaveAndAckWhenNewEvent() throws Exception {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
|
||||
.thenReturn(true);
|
||||
when(mqConsumeLogMapper.countByEventIdAndGroup(anyString(), anyString())).thenReturn(0);
|
||||
|
||||
AuditEventMessage event = buildEvent("evt-001", 1001L, 1001L);
|
||||
String message = JsonUtils.toJson(event);
|
||||
|
||||
consumer.onAuditOperation(message, channel, amqpMessage);
|
||||
|
||||
verify(auditLogService).save(any(AuditEventMessage.class));
|
||||
verify(mqConsumeLogMapper).insert(any(MqConsumeLog.class));
|
||||
verify(channel).basicAck(1L, false);
|
||||
verify(channel, never()).basicNack(anyLong(), anyBoolean(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("幂等命中(Redis): SETNX false → ACK 不处理")
|
||||
void shouldAckWhenRedisIdempotentHit() throws Exception {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
|
||||
.thenReturn(false);
|
||||
|
||||
AuditEventMessage event = buildEvent("evt-002", 1002L, 1002L);
|
||||
String message = JsonUtils.toJson(event);
|
||||
|
||||
consumer.onAuditOperation(message, channel, amqpMessage);
|
||||
|
||||
verify(auditLogService, never()).save(any());
|
||||
verify(mqConsumeLogMapper, never()).insert(any(MqConsumeLog.class));
|
||||
verify(channel).basicAck(1L, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("幂等命中(DB): count > 0 → ACK 不处理")
|
||||
void shouldAckWhenDbIdempotentHit() throws Exception {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
|
||||
.thenReturn(true);
|
||||
when(mqConsumeLogMapper.countByEventIdAndGroup(anyString(), anyString())).thenReturn(1);
|
||||
|
||||
AuditEventMessage event = buildEvent("evt-003", 1003L, 1003L);
|
||||
String message = JsonUtils.toJson(event);
|
||||
|
||||
consumer.onAuditOperation(message, channel, amqpMessage);
|
||||
|
||||
verify(auditLogService, never()).save(any());
|
||||
verify(mqConsumeLogMapper, never()).insert(any(MqConsumeLog.class));
|
||||
verify(channel).basicAck(1L, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("异常: JSON 解析抛异常 → NACK(JsonUtils.fromJson 对非法 JSON 抛异常,进入 catch 块)")
|
||||
void shouldNackWhenJsonParseThrows() throws Exception {
|
||||
// 传入非 JSON 字符串,JsonUtils.fromJson 抛异常 → 进入 catch → basicNack
|
||||
String message = "not-a-valid-json";
|
||||
|
||||
consumer.onAuditOperation(message, channel, amqpMessage);
|
||||
|
||||
verify(auditLogService, never()).save(any());
|
||||
verify(channel).basicNack(1L, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("异常: save() 抛异常 → NACK + SecurityContext 清理(try-with-resources 验证)")
|
||||
void shouldNackAndClearSecurityContextWhenSaveThrows() throws Exception {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
|
||||
.thenReturn(true);
|
||||
when(mqConsumeLogMapper.countByEventIdAndGroup(anyString(), anyString())).thenReturn(0);
|
||||
doThrow(new RuntimeException("DB connection lost"))
|
||||
.when(auditLogService).save(any(AuditEventMessage.class));
|
||||
|
||||
AuditEventMessage event = buildEvent("evt-004", 1004L, 1004L);
|
||||
String message = JsonUtils.toJson(event);
|
||||
|
||||
consumer.onAuditOperation(message, channel, amqpMessage);
|
||||
|
||||
// 验证 NACK
|
||||
verify(channel).basicNack(1L, false, false);
|
||||
verify(mqConsumeLogMapper, never()).insert(any(MqConsumeLog.class));
|
||||
|
||||
// 关键验证:try-with-resources 异常路径 SecurityContext 已清理
|
||||
// 防止 RabbitMQ 线程池复用继承 admin 权限(R4 缓解策略核心安全约束)
|
||||
assertThat(SecurityContextHolder.getContext().getAuthentication())
|
||||
.as("save() 异常后 SecurityContext 必须为空,防 RabbitMQ 线程池权限泄漏")
|
||||
.isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("eventId 缺失:fallback 到 operatorId:operationTime 拼装 → save() 调用")
|
||||
void shouldFallbackEventIdWhenMissing() throws Exception {
|
||||
when(valueOperations.setIfAbsent(anyString(), anyString(), anyLong(), any(TimeUnit.class)))
|
||||
.thenReturn(true);
|
||||
when(mqConsumeLogMapper.countByEventIdAndGroup(anyString(), anyString())).thenReturn(0);
|
||||
|
||||
// eventId=null,operatorId=2001, operationTime=1700000000000L
|
||||
AuditEventMessage event = buildEvent(null, 2001L, 1700000000000L);
|
||||
String message = JsonUtils.toJson(event);
|
||||
|
||||
consumer.onAuditOperation(message, channel, amqpMessage);
|
||||
|
||||
verify(auditLogService).save(any(AuditEventMessage.class));
|
||||
verify(channel).basicAck(1L, false);
|
||||
// 验证 fallback eventId 格式 "audit:operatorId:operationTime"
|
||||
verify(mqConsumeLogMapper).countByEventIdAndGroup(eq("audit:2001:1700000000000"), eq("audit-consumer"));
|
||||
}
|
||||
|
||||
private AuditEventMessage buildEvent(String eventId, Long operatorId, Long operationTime) {
|
||||
AuditEventMessage event = new AuditEventMessage();
|
||||
event.setEventId(eventId);
|
||||
event.setOperatorId(operatorId);
|
||||
event.setOperatorName("test-user");
|
||||
event.setOperatorAccount("test");
|
||||
event.setOperatorType(1);
|
||||
event.setOperationType("CREATE");
|
||||
event.setModule("USER");
|
||||
event.setOperationDesc("测试操作");
|
||||
event.setMethod("UserController.create");
|
||||
event.setRequestUrl("/api/v1/users");
|
||||
event.setRequestMethod("POST");
|
||||
event.setOperationResult("SUCCESS");
|
||||
event.setOperationTime(operationTime);
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
package com.pms.audit.controller;
|
||||
|
||||
import com.pms.audit.service.impl.AuditLogServiceImpl;
|
||||
import com.pms.audit.service.impl.LoginLogServiceImpl;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* PreAuthorize 覆盖率守卫测试(pms-audit 模块,KTD6 反射式静态检查)
|
||||
* <p>
|
||||
* 设计依据:docs/plans/2026-07-05-002-refactor-pre-authorize-alignment-pr2-pr4-plan.md KTD6
|
||||
* <p>
|
||||
* 守卫范围:
|
||||
* <ul>
|
||||
* <li>Controller 层:所有 @RestController/@Controller 公共方法必须有 @PreAuthorize(白名单:InternalController 类名)</li>
|
||||
* <li>Service 层写操作:@Service 类的 @Transactional 公共方法必须有 @PreAuthorize(@Transactional 作为写操作指示器)</li>
|
||||
* <li>func_code 合法性:hasFunction('xxx') 的 xxx 必须是 V12 已注册的合法值</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* ponytail: KTD6 表达式形式盲区 — 本测试只校验 @PreAuthorize 存在,不校验表达式形式
|
||||
* (hasAuthorityAndAdmin vs 裸 hasAuthority)。裸 hasAuthority 会导致 system-user admin 旁路失效。
|
||||
* 当前由 Service Security Test 抽样验证关键 ServiceImpl 的 admin 旁路(pms-audit 例外,由 AuditConsumer
|
||||
* 集成路径覆盖)。
|
||||
*/
|
||||
@DisplayName("PreAuthorize 覆盖率守卫测试 (pms-audit)")
|
||||
class PreAuthorizeCoverageTest {
|
||||
|
||||
/** OQ6 决策:硬编码 InternalController 类名白名单(Feign 内部接口) */
|
||||
private static final Set<String> WHITELISTED_CONTROLLERS = Set.of(
|
||||
"InternalLoginLogController",
|
||||
"InternalController"
|
||||
);
|
||||
|
||||
/** V12 已注册的合法 function_code 列表(与 V12 migration 同步) */
|
||||
private static final Set<String> VALID_FUNC_CODES = Set.of(
|
||||
"admin", "base", "charge", "operation",
|
||||
"user_management", "org_management", "role_management", "system_management",
|
||||
"viewer" // deprecated,但仍合法
|
||||
);
|
||||
|
||||
/** hasFunction('xxx') SpEL 表达式提取正则 */
|
||||
private static final Pattern HAS_FUNCTION_PATTERN = Pattern.compile("hasFunction\\(\\s*'([^']+)'\\s*\\)");
|
||||
|
||||
/** pms-audit 模块所有 @RestController/@Controller 类 */
|
||||
private static final List<Class<?>> CONTROLLERS = List.of(
|
||||
AuditLogController.class,
|
||||
LoginLogController.class,
|
||||
InternalLoginLogController.class
|
||||
);
|
||||
|
||||
/** pms-audit 模块所有 @Service 类(用于 Service 层写操作扫描) */
|
||||
private static final List<Class<?>> SERVICES = List.of(
|
||||
AuditLogServiceImpl.class,
|
||||
LoginLogServiceImpl.class
|
||||
);
|
||||
|
||||
static Stream<Arguments> publicMethods() {
|
||||
return CONTROLLERS.stream()
|
||||
.filter(c -> !WHITELISTED_CONTROLLERS.contains(c.getSimpleName()))
|
||||
.flatMap(c -> Arrays.stream(c.getDeclaredMethods())
|
||||
.filter(m -> Modifier.isPublic(m.getModifiers()))
|
||||
.filter(m -> !m.isBridge() && !m.isSynthetic())
|
||||
.map(m -> Arguments.of(c.getSimpleName(), m.getName(), m)));
|
||||
}
|
||||
|
||||
static Stream<Arguments> serviceWriteMethods() {
|
||||
return SERVICES.stream()
|
||||
.flatMap(c -> Arrays.stream(c.getDeclaredMethods())
|
||||
.filter(m -> Modifier.isPublic(m.getModifiers()))
|
||||
.filter(m -> !m.isBridge() && !m.isSynthetic())
|
||||
.filter(m -> m.isAnnotationPresent(Transactional.class))
|
||||
.map(m -> Arguments.of(c.getSimpleName(), m.getName(), m)));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Controller {0}#{1} 必须有 @PreAuthorize")
|
||||
@MethodSource("publicMethods")
|
||||
void controllerMethod_mustHavePreAuthorize(String className, String methodName, Method method) {
|
||||
assertThat(method.isAnnotationPresent(PreAuthorize.class))
|
||||
.as("%s#%s 缺少 @PreAuthorize 注解(白名单类已过滤)", className, methodName)
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Controller {0}#{1} 的 hasFunction func_code 必须合法")
|
||||
@MethodSource("publicMethods")
|
||||
void controllerMethod_funcCode_mustBeValid(String className, String methodName, Method method) {
|
||||
PreAuthorize ann = method.getAnnotation(PreAuthorize.class);
|
||||
if (ann == null) {
|
||||
return; // 由 controllerMethod_mustHavePreAuthorize 覆盖
|
||||
}
|
||||
String expr = ann.value();
|
||||
Matcher matcher = HAS_FUNCTION_PATTERN.matcher(expr);
|
||||
while (matcher.find()) {
|
||||
String funcCode = matcher.group(1);
|
||||
assertThat(VALID_FUNC_CODES)
|
||||
.as("%s#%s 的 hasFunction('%s') 不是 V12 合法 func_code", className, methodName, funcCode)
|
||||
.contains(funcCode);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Service 写操作 {0}#{1} 必须有 @PreAuthorize(@Transactional 指示写操作)")
|
||||
@MethodSource("serviceWriteMethods")
|
||||
void serviceWriteMethod_mustHavePreAuthorize(String className, String methodName, Method method) {
|
||||
assertThat(method.isAnnotationPresent(PreAuthorize.class))
|
||||
.as("%s#%s 是 @Transactional 写操作但缺少 @PreAuthorize(system-user admin 旁路需要 @PreAuthorize 才能触发)",
|
||||
className, methodName)
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("白名单机制自检:InternalController 类被正确豁免")
|
||||
void whitelist_selfCheck() {
|
||||
// InternalLoginLogController 在白名单中,应被过滤
|
||||
long internalCount = CONTROLLERS.stream()
|
||||
.filter(c -> WHITELISTED_CONTROLLERS.contains(c.getSimpleName()))
|
||||
.count();
|
||||
assertThat(internalCount).as("InternalController 应在白名单中").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V12 func_code 列表自检:包含 system_management(pms-audit 模块使用)")
|
||||
void validFuncCodes_selfCheck() {
|
||||
assertThat(VALID_FUNC_CODES).contains("system_management");
|
||||
}
|
||||
}
|
||||
|
|
@ -424,5 +424,44 @@ class AuditLogServiceImplTest {
|
|||
assertThat(stats.getTotalCount()).isEqualTo(500L);
|
||||
assertThat(stats.getSuccessCount()).isEqualTo(400L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("functions='admin' → 不按 projectId 过滤(admin 旁路,OQ1 决策)")
|
||||
void statistics_withAdminFunction_noProjectFilter() {
|
||||
// 重新设置 SecurityContext:principal 为 CurrentUser,functions='admin',
|
||||
// 不持有 system:audit:view:all 权限 — 验证 canViewAllProjects() admin 旁路分支
|
||||
UserContext.CurrentUser adminUser = new UserContext.CurrentUser(
|
||||
1001L, "admin", 2001L, "ROLE_ADMIN", "1",
|
||||
null, 200L, null, "admin");
|
||||
UserContext.set(adminUser);
|
||||
Authentication auth = new UsernamePasswordAuthenticationToken(
|
||||
adminUser, null, List.of(new SimpleGrantedAuthority("ROLE_ADMIN")));
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
|
||||
// 使用 ArgumentCaptor 捕获 wrapper,验证未应用 projectId 过滤(admin 旁路放行)
|
||||
ArgumentCaptor<LambdaQueryWrapper<AuditLog>> wrapperCaptor =
|
||||
ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
|
||||
when(auditLogMapper.selectCount(any(LambdaQueryWrapper.class)))
|
||||
.thenReturn(500L)
|
||||
.thenReturn(400L)
|
||||
.thenReturn(80L)
|
||||
.thenReturn(20L);
|
||||
when(auditLogMapper.selectMaps(any())).thenReturn(List.of());
|
||||
|
||||
AuditLogStatisticsDTO stats = auditLogService.statistics();
|
||||
|
||||
assertThat(stats.getTotalCount()).isEqualTo(500L);
|
||||
assertThat(stats.getSuccessCount()).isEqualTo(400L);
|
||||
// 验证 selectCount 被调用 4 次,且 wrapper 的 SQL 段不包含 project_id 过滤
|
||||
verify(auditLogMapper, times(4)).selectCount(wrapperCaptor.capture());
|
||||
// ponytail: LambdaQueryWrapper.getSqlSegment() 在无条件下返回空字符串;
|
||||
// admin 旁路生效时 wrapper 不带 projectId 条件,sqlSegment 应为空
|
||||
for (LambdaQueryWrapper<AuditLog> wrapper : wrapperCaptor.getAllValues()) {
|
||||
assertThat(wrapper.getSqlSegment())
|
||||
.as("admin 旁路生效时 wrapper 不应包含 project_id 过滤")
|
||||
.doesNotContain("project_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,13 @@ import org.springframework.web.bind.annotation.*;
|
|||
/**
|
||||
* 认证控制器
|
||||
* 路径前缀: /api/v1/auth
|
||||
* <p>
|
||||
* PR2-PR4 改造(KTD5 决策):14 个方法按接口类型分别标注 @PreAuthorize:
|
||||
* <ul>
|
||||
* <li>8 个公开接口(登录/刷新/验证码):permitAll()</li>
|
||||
* <li>5 个当前用户接口(登出/切换项目/修改密码/用户信息/权限检查):isAuthenticated()</li>
|
||||
* <li>1 个管理接口(重置密码):hasFunction('user_management') and hasAuthorityAndAdmin('system:user:manage')</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -30,6 +37,7 @@ public class AuthController {
|
|||
* 用户登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<LoginResponse> login(@Valid @RequestBody LoginRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
String clientIp = getClientIp(httpRequest);
|
||||
|
|
@ -43,6 +51,7 @@ public class AuthController {
|
|||
* 与 4 个平台特定端点(/login/wechat 等)功能等价,供小程序统一调用。
|
||||
*/
|
||||
@PostMapping("/login/platform")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<PlatformLoginResponse> loginByPlatform(@Valid @RequestBody PlatformLoginRequest request) {
|
||||
return Result.success(authService.loginByPlatform(request));
|
||||
}
|
||||
|
|
@ -52,6 +61,7 @@ public class AuthController {
|
|||
* 客户端传 wx.login 的 code + wx.getPhoneNumber 的 phoneCode
|
||||
*/
|
||||
@PostMapping("/login/wechat")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<PlatformLoginResponse> loginByWechat(@Valid @RequestBody PlatformLoginRequest request) {
|
||||
request.setPlatform(UserThirdAccount.PLATFORM_WECHAT);
|
||||
return Result.success(authService.loginByPlatform(request));
|
||||
|
|
@ -62,6 +72,7 @@ public class AuthController {
|
|||
* 客户端传 my.getAuthCode 的 code
|
||||
*/
|
||||
@PostMapping("/login/alipay")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<PlatformLoginResponse> loginByAlipay(@Valid @RequestBody PlatformLoginRequest request) {
|
||||
request.setPlatform(UserThirdAccount.PLATFORM_ALIPAY);
|
||||
return Result.success(authService.loginByPlatform(request));
|
||||
|
|
@ -72,6 +83,7 @@ public class AuthController {
|
|||
* 客户端传 dd.getLoginCode 的 loginCode
|
||||
*/
|
||||
@PostMapping("/login/dingtalk")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<PlatformLoginResponse> loginByDingtalk(@Valid @RequestBody PlatformLoginRequest request) {
|
||||
request.setPlatform(UserThirdAccount.PLATFORM_DINGTALK);
|
||||
return Result.success(authService.loginByPlatform(request));
|
||||
|
|
@ -82,6 +94,7 @@ public class AuthController {
|
|||
* 客户端传 wx.qy.login 的 code
|
||||
*/
|
||||
@PostMapping("/login/qywechat")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<PlatformLoginResponse> loginByQywechat(@Valid @RequestBody PlatformLoginRequest request) {
|
||||
request.setPlatform(UserThirdAccount.PLATFORM_QYWECHAT);
|
||||
return Result.success(authService.loginByPlatform(request));
|
||||
|
|
@ -91,6 +104,7 @@ public class AuthController {
|
|||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Result<Void> logout(@RequestHeader(value = "Authorization", required = false) String authHeader,
|
||||
@RequestBody(required = false) RefreshTokenRequest request) {
|
||||
String accessToken = null;
|
||||
|
|
@ -106,6 +120,7 @@ public class AuthController {
|
|||
* 刷新令牌
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<TokenRefreshResponse> refresh(@Valid @RequestBody RefreshTokenRequest request) {
|
||||
return Result.success(authService.refreshToken(request.getRefreshToken()));
|
||||
}
|
||||
|
|
@ -119,6 +134,7 @@ public class AuthController {
|
|||
* @param projectId 目标项目ID
|
||||
*/
|
||||
@PostMapping("/switch-project")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Result<LoginResponse> switchProject(@RequestParam Long projectId) {
|
||||
return Result.success(authService.switchProject(projectId));
|
||||
}
|
||||
|
|
@ -127,6 +143,7 @@ public class AuthController {
|
|||
* 获取验证码
|
||||
*/
|
||||
@GetMapping("/captcha")
|
||||
@PreAuthorize("permitAll()")
|
||||
public Result<CaptchaResponse> captcha() {
|
||||
return Result.success(authService.getCaptcha());
|
||||
}
|
||||
|
|
@ -135,6 +152,7 @@ public class AuthController {
|
|||
* 修改密码
|
||||
*/
|
||||
@PutMapping("/password")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Result<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request) {
|
||||
authService.changePassword(UserContext.getUserId(), request);
|
||||
return Result.success();
|
||||
|
|
@ -144,7 +162,7 @@ public class AuthController {
|
|||
* 重置密码(管理员)
|
||||
*/
|
||||
@PutMapping("/password/reset")
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management') and hasAuthorityAndAdmin('system:user:manage')")
|
||||
@AuditLog(module = "认证", type = "UPDATE", description = "重置用户密码", recordParams = false)
|
||||
public Result<Void> resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
|
||||
authService.resetPassword(request);
|
||||
|
|
@ -155,6 +173,7 @@ public class AuthController {
|
|||
* 获取当前用户信息
|
||||
*/
|
||||
@GetMapping("/userinfo")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Result<UserInfoDTO> userInfo() {
|
||||
return Result.success(authService.getCurrentUserInfo());
|
||||
}
|
||||
|
|
@ -163,6 +182,7 @@ public class AuthController {
|
|||
* 权限检查
|
||||
*/
|
||||
@PostMapping("/check-permission")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public Result<Boolean> checkPermission(@RequestParam Long userId,
|
||||
@RequestParam String permissionCode,
|
||||
@RequestParam(required = false) Long projectId) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 组织管理控制器
|
||||
* 路径前缀: /api/v1/auth/orgs
|
||||
* <p>
|
||||
* PR2-PR4 改造(U3):6 方法统一标注 `hasFunction('org_management')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('system:org:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -32,7 +35,7 @@ public class OrgController {
|
|||
* 组织架构树(支持 parentId/status/projectId/orgType 过滤)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('system:org:manage')")
|
||||
@PreAuthorize("hasFunction('org_management')")
|
||||
public Result<List<OrgDTO>> list(@RequestParam(required = false) Long parentId,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
|
|
@ -44,7 +47,7 @@ public class OrgController {
|
|||
* 完整组织树(含岗位节点,带类型中文名)
|
||||
*/
|
||||
@GetMapping("/tree")
|
||||
@PreAuthorize("hasAuthority('system:org:manage')")
|
||||
@PreAuthorize("hasFunction('org_management')")
|
||||
public Result<List<OrgTreeDTO>> tree() {
|
||||
return Result.success(orgService.getOrgTree());
|
||||
}
|
||||
|
|
@ -53,7 +56,7 @@ public class OrgController {
|
|||
* 指定父节点下的组织子树
|
||||
*/
|
||||
@GetMapping("/tree/{parentId}")
|
||||
@PreAuthorize("hasAuthority('system:org:manage')")
|
||||
@PreAuthorize("hasFunction('org_management')")
|
||||
public Result<List<OrgTreeDTO>> treeByParent(@PathVariable Long parentId) {
|
||||
return Result.success(orgService.getOrgTree(parentId));
|
||||
}
|
||||
|
|
@ -62,7 +65,7 @@ public class OrgController {
|
|||
* 创建组织节点
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('system:org:manage')")
|
||||
@PreAuthorize("hasFunction('org_management')")
|
||||
@AuditLog(module = "组织架构", type = "CREATE", description = "创建组织")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody OrgSaveRequest request) {
|
||||
Long id = orgService.create(request);
|
||||
|
|
@ -75,7 +78,7 @@ public class OrgController {
|
|||
* 更新组织节点
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:org:manage')")
|
||||
@PreAuthorize("hasFunction('org_management')")
|
||||
@AuditLog(module = "组织架构", type = "UPDATE", description = "更新组织")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody OrgSaveRequest request) {
|
||||
orgService.update(id, request);
|
||||
|
|
@ -86,7 +89,7 @@ public class OrgController {
|
|||
* 删除组织节点
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:org:manage')")
|
||||
@PreAuthorize("hasFunction('org_management')")
|
||||
@AuditLog(module = "组织架构", type = "DELETE", description = "删除组织")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
orgService.delete(id);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@ import com.pms.auth.service.PermissionService;
|
|||
import com.pms.common.response.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 权限管理控制器
|
||||
* 路径前缀: /api/v1/auth/permissions
|
||||
* <p>
|
||||
* PR2-PR4 改造(U3):2 方法补 `hasFunction('role_management')`(原裸奔,权限树供角色分配权限使用)。
|
||||
* 与 RoleController 共享 role_management 函数入口,保持权限语义一致。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -23,6 +27,7 @@ public class PermissionController {
|
|||
* 权限树查询
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
public Result<PermissionTreeResult> tree(@RequestParam(required = false) Long roleId,
|
||||
@RequestParam(required = false) Integer permType) {
|
||||
return Result.success(permissionService.getPermissionTree(roleId, permType));
|
||||
|
|
@ -32,6 +37,7 @@ public class PermissionController {
|
|||
* 权限树查询(/tree 别名)
|
||||
*/
|
||||
@GetMapping("/tree")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
public Result<PermissionTreeResult> treeAlias(@RequestParam(required = false) Long roleId,
|
||||
@RequestParam(required = false) Integer permType) {
|
||||
return Result.success(permissionService.getPermissionTree(roleId, permType));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ import java.util.List;
|
|||
/**
|
||||
* 岗位-角色映射管理控制器(U5 / KD5:岗位 = 角色映射)
|
||||
* 路径前缀: /api/v1/auth/positions
|
||||
* <p>
|
||||
* PR2-PR4 改造(U3):5 方法统一标注 `hasFunction('system_management')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('system:project:manage'|'system:role:manage')` 控制操作粒度
|
||||
* (mapPositionToRole 用 system:role:manage 保持 ROLE_ROLE_MANAGER 权限语义)。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +35,7 @@ public class PositionRoleController {
|
|||
* 为用户在项目中分配岗位
|
||||
*/
|
||||
@PostMapping("/assign")
|
||||
@PreAuthorize("hasAuthority('system:project:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
@AuditLog(module = "岗位管理", type = "ASSIGN", description = "分配岗位")
|
||||
public Result<Void> assignPosition(@RequestBody @Valid AssignPositionRequest request) {
|
||||
positionRoleService.assignPositionToUser(
|
||||
|
|
@ -43,7 +47,7 @@ public class PositionRoleController {
|
|||
* 查询用户在项目的岗位
|
||||
*/
|
||||
@GetMapping("/user/{userId}/project/{projectId}")
|
||||
@PreAuthorize("hasAuthority('system:project:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<List<PositionDTO>> getUserPositions(@PathVariable Long userId,
|
||||
@PathVariable Long projectId) {
|
||||
return Result.success(positionRoleService.getUserPositions(userId, projectId));
|
||||
|
|
@ -53,7 +57,7 @@ public class PositionRoleController {
|
|||
* 查询组织下的岗位列表
|
||||
*/
|
||||
@GetMapping("/org/{parentOrgId}")
|
||||
@PreAuthorize("hasAuthority('system:project:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<List<PositionDTO>> getPositionsByOrg(@PathVariable Long parentOrgId) {
|
||||
return Result.success(positionRoleService.getPositionsByOrg(parentOrgId));
|
||||
}
|
||||
|
|
@ -62,7 +66,7 @@ public class PositionRoleController {
|
|||
* 配置岗位-角色映射(管理员)
|
||||
*/
|
||||
@PostMapping("/mapping")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
@AuditLog(module = "岗位管理", type = "CREATE", description = "创建岗位-角色映射")
|
||||
public Result<Void> mapPositionToRole(@RequestBody @Valid PositionRoleMappingRequest request) {
|
||||
positionRoleService.mapPositionToRole(request.getPositionOrgId(), request.getRoleId());
|
||||
|
|
@ -73,7 +77,7 @@ public class PositionRoleController {
|
|||
* 列出所有岗位-角色映射
|
||||
*/
|
||||
@GetMapping("/mappings")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<List<PositionRoleMappingDTO>> listMappings() {
|
||||
return Result.success(positionRoleService.listPositionRoleMappings());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.List;
|
|||
* 路径前缀: /api/v1/auth/projects
|
||||
* <p>
|
||||
* 管理项目-用户绑定关系,含岗位选择(复用 U5 PositionRoleService)。
|
||||
* <p>
|
||||
* PR2-PR4 改造(U3):3 方法统一标注 `hasFunction('system_management')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('system:project:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class ProjectUserController {
|
|||
* 查询项目的用户列表(含岗位/角色信息)
|
||||
*/
|
||||
@GetMapping("/{projectId}/users")
|
||||
@PreAuthorize("hasAuthority('system:project:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
public Result<List<ProjectUserDTO>> getProjectUsers(@PathVariable Long projectId) {
|
||||
return Result.success(projectUserService.getProjectUsers(projectId));
|
||||
}
|
||||
|
|
@ -40,7 +43,7 @@ public class ProjectUserController {
|
|||
* 绑定用户到项目(含岗位选择)
|
||||
*/
|
||||
@PostMapping("/{projectId}/users")
|
||||
@PreAuthorize("hasAuthority('system:project:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
@AuditLog(module = "权限管理", type = "ASSIGN", description = "绑定用户到项目")
|
||||
public Result<Void> bindUserToProject(@PathVariable Long projectId,
|
||||
@RequestBody @Valid BindUserRequest request) {
|
||||
|
|
@ -52,7 +55,7 @@ public class ProjectUserController {
|
|||
* 解绑项目用户
|
||||
*/
|
||||
@DeleteMapping("/{projectId}/users/{userId}")
|
||||
@PreAuthorize("hasAuthority('system:project:manage')")
|
||||
@PreAuthorize("hasFunction('system_management')")
|
||||
@AuditLog(module = "权限管理", type = "DELETE", description = "解绑项目用户")
|
||||
public Result<Void> unbindUser(@PathVariable Long projectId, @PathVariable Long userId) {
|
||||
projectUserService.unbindUser(projectId, userId);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 角色管理控制器
|
||||
* 路径前缀: /api/v1/auth/roles
|
||||
* <p>
|
||||
* PR2-PR4 改造(U3):6 方法统一标注 `hasFunction('role_management')` 控制模块入口(含 view 只读方法)。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('system:role:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class RoleController {
|
|||
* 角色列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('system:role:view')")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
public Result<List<RoleDTO>> list(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
|
|
@ -43,7 +46,7 @@ public class RoleController {
|
|||
* 创建角色
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
@AuditLog(module = "权限管理", type = "CREATE", description = "创建角色")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody RoleSaveRequest request) {
|
||||
Long id = roleService.create(request);
|
||||
|
|
@ -56,7 +59,7 @@ public class RoleController {
|
|||
* 角色详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:role:view')")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
public Result<RoleDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(roleService.getById(id));
|
||||
}
|
||||
|
|
@ -65,7 +68,7 @@ public class RoleController {
|
|||
* 更新角色
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
@AuditLog(module = "权限管理", type = "UPDATE", description = "更新角色")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody RoleSaveRequest request) {
|
||||
roleService.update(id, request);
|
||||
|
|
@ -76,7 +79,7 @@ public class RoleController {
|
|||
* 删除角色
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
@AuditLog(module = "权限管理", type = "DELETE", description = "删除角色")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
roleService.delete(id);
|
||||
|
|
@ -87,7 +90,7 @@ public class RoleController {
|
|||
* 分配权限
|
||||
*/
|
||||
@PostMapping("/{id}/permissions")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('role_management')")
|
||||
@AuditLog(module = "权限管理", type = "UPDATE", description = "分配角色权限")
|
||||
public Result<Void> assignPermissions(@PathVariable Long id,
|
||||
@Valid @RequestBody AssignPermissionsRequest request) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import java.util.Map;
|
|||
* 用户管理控制器
|
||||
* 路径前缀: /api/v1/auth/users
|
||||
* <p>
|
||||
* PR2-PR4 改造(U3):9 方法统一标注 `hasFunction('user_management')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('system:user:manage'|'system:role:manage')` 控制操作粒度。
|
||||
* 含用户-角色分配接口(assignRoles / getUserRoles / removeRole),
|
||||
* 复用 pms-common @AuditLog 注解 + @PreAuthorize 方法级鉴权。
|
||||
*/
|
||||
|
|
@ -34,7 +36,7 @@ public class UserController {
|
|||
* 用户列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
public Result<PageResult<UserDTO>> list(UserQueryRequest request) {
|
||||
return Result.success(userService.page(request));
|
||||
}
|
||||
|
|
@ -43,7 +45,7 @@ public class UserController {
|
|||
* 创建用户
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
@AuditLog(module = "权限管理", type = "CREATE", description = "创建用户")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody UserSaveRequest request) {
|
||||
Long id = userService.create(request);
|
||||
|
|
@ -56,7 +58,7 @@ public class UserController {
|
|||
* 用户详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
public Result<UserDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(userService.getById(id));
|
||||
}
|
||||
|
|
@ -65,7 +67,7 @@ public class UserController {
|
|||
* 更新用户
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
@AuditLog(module = "权限管理", type = "UPDATE", description = "更新用户")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody UserUpdateRequest request) {
|
||||
userService.update(id, request);
|
||||
|
|
@ -76,7 +78,7 @@ public class UserController {
|
|||
* 删除用户
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
@AuditLog(module = "权限管理", type = "DELETE", description = "删除用户")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
userService.delete(id);
|
||||
|
|
@ -87,7 +89,7 @@ public class UserController {
|
|||
* 更新用户状态
|
||||
*/
|
||||
@PutMapping("/{id}/status")
|
||||
@PreAuthorize("hasAuthority('system:user:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
@AuditLog(module = "权限管理", type = "UPDATE", description = "更新用户状态")
|
||||
public Result<Void> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
|
||||
userService.updateStatus(id, status);
|
||||
|
|
@ -98,7 +100,7 @@ public class UserController {
|
|||
* 为用户分配角色(覆盖式:先清空旧角色再分配新角色)
|
||||
*/
|
||||
@PostMapping("/{id}/roles")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
@AuditLog(module = "权限管理", type = "ASSIGN", description = "分配用户角色")
|
||||
public Result<Void> assignRoles(@PathVariable Long id, @Valid @RequestBody AssignRolesRequest request) {
|
||||
userService.assignRoles(id, request);
|
||||
|
|
@ -109,7 +111,7 @@ public class UserController {
|
|||
* 查询用户的角色列表
|
||||
*/
|
||||
@GetMapping("/{id}/roles")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
public Result<List<RoleDTO>> getUserRoles(@PathVariable Long id) {
|
||||
return Result.success(userService.getUserRoles(id));
|
||||
}
|
||||
|
|
@ -118,7 +120,7 @@ public class UserController {
|
|||
* 删除用户的指定角色
|
||||
*/
|
||||
@DeleteMapping("/{id}/roles/{roleId}")
|
||||
@PreAuthorize("hasAuthority('system:role:manage')")
|
||||
@PreAuthorize("hasFunction('user_management')")
|
||||
@AuditLog(module = "权限管理", type = "DELETE", description = "删除用户角色")
|
||||
public Result<Void> removeRole(@PathVariable Long id, @PathVariable Long roleId) {
|
||||
userService.removeRole(id, roleId);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.pms.common.exception.BusinessException;
|
|||
import com.pms.common.exception.ErrorCode;
|
||||
import com.pms.common.datascope.DataScopeHelper;
|
||||
import com.pms.common.security.JwtUtils;
|
||||
import com.pms.common.security.SystemUserSecurityContext;
|
||||
import com.pms.common.security.UserContext;
|
||||
import com.pms.common.util.SensitiveDataUtils;
|
||||
import io.jsonwebtoken.Claims;
|
||||
|
|
@ -81,6 +82,15 @@ public class AuthServiceImpl implements AuthService {
|
|||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoginResponse login(LoginRequest request, String clientIp) {
|
||||
try {
|
||||
// 0. system 占位用户拒绝登录(R4 缓解策略 (7),defense-in-depth,防止攻击者通过 /api/v1/auth/login 以 system 身份认证)
|
||||
// system 用户仅供 MQ 消费者 SystemUserSecurityContext 使用,无认证路径
|
||||
// 信任边界校验:DB collate=utf8mb4_unicode_ci 大小写不敏感,必须用 equalsIgnoreCase + trim
|
||||
// 防止 "System"/"SYSTEM"/" system " + 公开占位密码绕过用户名拒绝
|
||||
String requestUsername = request.getUsername() == null ? null : request.getUsername().trim();
|
||||
if (requestUsername != null && requestUsername.equalsIgnoreCase(SystemUserSecurityContext.SYSTEM_USERNAME)) {
|
||||
throw new BusinessException(ErrorCode.ACCOUNT_DISABLED);
|
||||
}
|
||||
|
||||
// 1. 验证码校验
|
||||
validateCaptcha(request.getCaptchaKey(), request.getCaptchaCode());
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -68,6 +69,7 @@ public class OrgServiceImpl implements OrgService, OrgIdResolver {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:org:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(OrgSaveRequest request) {
|
||||
// 检查组织编码是否已存在
|
||||
|
|
@ -108,6 +110,7 @@ public class OrgServiceImpl implements OrgService, OrgIdResolver {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:org:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, OrgSaveRequest request) {
|
||||
Org org = getOrgEntity(id);
|
||||
|
|
@ -148,6 +151,7 @@ public class OrgServiceImpl implements OrgService, OrgIdResolver {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:org:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
Org org = getOrgEntity(id);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ public class PositionRoleServiceImpl implements PositionRoleService {
|
|||
private final ProjectUserMapper projectUserMapper;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignPositionToUser(Long userId, Long projectId, Long positionOrgId) {
|
||||
// 1. 校验岗位节点(org_type=5)
|
||||
|
|
@ -122,6 +124,7 @@ public class PositionRoleServiceImpl implements PositionRoleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void mapPositionToRole(Long positionOrgId, Long roleId) {
|
||||
// 校验岗位
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ public class ProjectUserServiceImpl implements ProjectUserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void bindUserToProject(Long projectId, BindUserRequest request) {
|
||||
Long userId = request.getUserId();
|
||||
|
|
@ -84,6 +86,7 @@ public class ProjectUserServiceImpl implements ProjectUserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void unbindUser(Long projectId, Long userId) {
|
||||
int deleted = projectUserMapper.deleteByProjectAndUser(projectId, userId);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -85,6 +86,7 @@ public class RoleServiceImpl implements RoleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(RoleSaveRequest request) {
|
||||
// 检查角色编码是否已存在
|
||||
|
|
@ -121,6 +123,7 @@ public class RoleServiceImpl implements RoleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, RoleSaveRequest request) {
|
||||
Role role = getRoleEntity(id);
|
||||
|
|
@ -148,6 +151,7 @@ public class RoleServiceImpl implements RoleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
Role role = getRoleEntity(id);
|
||||
|
|
@ -168,6 +172,7 @@ public class RoleServiceImpl implements RoleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignPermissions(Long roleId, AssignPermissionsRequest request) {
|
||||
// 检查角色存在
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ import com.pms.common.constant.CommonConstants;
|
|||
import com.pms.common.exception.BusinessException;
|
||||
import com.pms.common.exception.ErrorCode;
|
||||
import com.pms.common.response.PageResult;
|
||||
import com.pms.common.security.SystemUserSecurityContext;
|
||||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -77,6 +79,7 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:user:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(UserSaveRequest request) {
|
||||
// 检查用户名是否已存在
|
||||
|
|
@ -125,6 +128,7 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:user:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, UserUpdateRequest request) {
|
||||
User user = getUserEntity(id);
|
||||
|
|
@ -153,8 +157,14 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:user:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
// OQ5 决策:系统占位账户不可删除(R4 system-user 方案配套,防止误删 V13 id=0L system 用户)
|
||||
if (id != null && id.equals(SystemUserSecurityContext.SYSTEM_USER_ID)) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_FAILED, "系统账户不可删除");
|
||||
}
|
||||
|
||||
User user = getUserEntity(id);
|
||||
|
||||
// 系统管理员账号不可删除
|
||||
|
|
@ -175,6 +185,7 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:user:manage')")
|
||||
public void updateStatus(Long id, Integer status) {
|
||||
User user = getUserEntity(id);
|
||||
|
||||
|
|
@ -188,6 +199,7 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignRoles(Long userId, AssignRolesRequest request) {
|
||||
// 检查用户存在
|
||||
|
|
@ -222,6 +234,7 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:role:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeRole(Long userId, Long roleId) {
|
||||
// 检查用户存在
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
-- ============================================================
|
||||
-- V13: 新增 system 占位用户(OQ5 决策方案 a,R4 system-user 方案配套)
|
||||
-- ============================================================
|
||||
-- 背景:
|
||||
-- R4 MQ 消费者路径调用 Service 写操作,@PreAuthorize admin 旁路依赖
|
||||
-- principal.getFunctions() 含 "admin"(见 CustomMethodSecurityExpressionRoot.parseFunctionsToSet)。
|
||||
-- SystemUserSecurityContext.system() 构造 principal=CurrentUser(userId=0L, functions="admin"),
|
||||
-- userId=0L 必须在 t_user 表中存在(避免 setCreatedBy(null) 破坏 NOT NULL 约束)。
|
||||
--
|
||||
-- 关键约束:
|
||||
-- 1. password 仅满足 NOT NULL 约束,无任何认证路径可使用
|
||||
-- 2. AuthServiceImpl.login 增加 system 用户名拒绝(defense-in-depth,见 R4 缓解策略 (7))
|
||||
-- 3. UserServiceImpl.delete 增加 id=0L 防护(OQ5 实施清单,U3 阶段实施)
|
||||
--
|
||||
-- 安全说明:
|
||||
-- - 不可通过 /api/v1/auth/login 以 system 身份认证(AuthServiceImpl.login 在密码校验前拒绝 system 用户名)
|
||||
-- - 不可通过 DELETE /api/v1/users/0 删除(UserServiceImpl 防护)
|
||||
-- - 仅用于 MQ 消费者路径的 admin 旁路,无业务用途
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO `t_user` (`id`, `project_id`, `username`, `password`, `real_name`,
|
||||
`status`, `account_type`, `remark`,
|
||||
`created_at`, `updated_at`, `deleted`)
|
||||
VALUES (
|
||||
0,
|
||||
NULL,
|
||||
'system',
|
||||
-- 占位密码(BCrypt of "password"),仅满足 NOT NULL 约束;
|
||||
-- 安全依赖 AuthServiceImpl.login 按用户名拒绝,password 本身不可猜测性无意义
|
||||
'$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
|
||||
'系统占位用户',
|
||||
1,
|
||||
1,
|
||||
'系统占位用户,禁止登录。仅供 MQ 消费者 system-user SecurityContext 使用(OQ5/R4)',
|
||||
UNIX_TIMESTAMP() * 1000,
|
||||
UNIX_TIMESTAMP() * 1000,
|
||||
0
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE `updated_at` = UNIX_TIMESTAMP() * 1000;
|
||||
|
||||
-- ============================================================
|
||||
-- post-deploy 验证 SQL(执行后人工运行确认)
|
||||
-- ============================================================
|
||||
-- 验证 system 用户存在且不可登录:
|
||||
-- SELECT id, username, real_name, status, account_type, deleted FROM t_user WHERE id = 0;
|
||||
-- 预期:id=0, username='system', status=1, account_type=1, deleted=0
|
||||
--
|
||||
-- 验证 system 用户名登录被拒绝(应在 AuthServiceImpl.login 代码层拒绝,不依赖密码强度):
|
||||
-- POST /api/v1/auth/login {"username":"system","password":"anything"} → 应返回 ACCOUNT_DISABLED 错误
|
||||
-- ============================================================
|
||||
|
|
@ -257,14 +257,16 @@ class OrgControllerCrudTest {
|
|||
class SecurityAnnotationTest {
|
||||
|
||||
/**
|
||||
* 验证写操作方法标注了 @PreAuthorize("hasAuthority('system:org:manage')") 和 @AuditLog。
|
||||
* 验证写操作方法标注了 @PreAuthorize("hasFunction('org_management')") 和 @AuditLog。
|
||||
* standalone MockMvc 不加载 Spring Security 过滤链,无法测 403 响应;
|
||||
* 此处通过反射断言注解存在,实际 403 拦截由集成测试验证。
|
||||
* PR2-PR4 改造(U3):Controller 层从 hasAuthority('system:org:manage') 改为 hasFunction('org_management'),
|
||||
* Service 层写操作通过 hasAuthorityAndAdmin('system:org:manage') 控制操作粒度。
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("create/update/delete 方法标注 @PreAuthorize('system:org:manage')")
|
||||
@DisplayName("create/update/delete 方法标注 @PreAuthorize('hasFunction(org_management)')")
|
||||
void writeMethods_havePreAuthorize() throws NoSuchMethodException {
|
||||
String expected = "hasAuthority('system:org:manage')";
|
||||
String expected = "hasFunction('org_management')";
|
||||
|
||||
Method create = OrgController.class.getMethod("create", OrgSaveRequest.class);
|
||||
PreAuthorize createAnno = create.getAnnotation(PreAuthorize.class);
|
||||
|
|
@ -303,9 +305,9 @@ class OrgControllerCrudTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("list/tree/treeByParent 方法标注 @PreAuthorize('system:org:manage')(读操作也鉴权)")
|
||||
@DisplayName("list/tree/treeByParent 方法标注 @PreAuthorize('hasFunction(org_management)')(读操作也鉴权)")
|
||||
void readMethods_havePreAuthorize() throws NoSuchMethodException {
|
||||
String expected = "hasAuthority('system:org:manage')";
|
||||
String expected = "hasFunction('org_management')";
|
||||
|
||||
Method list = OrgController.class.getMethod("list", Long.class, Integer.class, Long.class, Integer.class);
|
||||
PreAuthorize listAnno = list.getAnnotation(PreAuthorize.class);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,252 @@
|
|||
package com.pms.auth.controller;
|
||||
|
||||
import com.pms.auth.service.impl.AuthServiceImpl;
|
||||
import com.pms.auth.service.impl.OrgServiceImpl;
|
||||
import com.pms.auth.service.impl.PermissionServiceImpl;
|
||||
import com.pms.auth.service.impl.PositionRoleServiceImpl;
|
||||
import com.pms.auth.service.impl.ProjectUserServiceImpl;
|
||||
import com.pms.auth.service.impl.RoleServiceImpl;
|
||||
import com.pms.auth.service.impl.UserServiceImpl;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* PreAuthorize 覆盖率守卫测试(pms-auth 模块,KTD6 反射式静态检查)
|
||||
* <p>
|
||||
* 设计依据:docs/plans/2026-07-05-002-refactor-pre-authorize-alignment-pr2-pr4-plan.md KTD6 / U3
|
||||
* <p>
|
||||
* 守卫范围:
|
||||
* <ul>
|
||||
* <li>Controller 层:所有 @RestController/@Controller 公共方法必须有 @PreAuthorize(白名单:InternalController 类名)</li>
|
||||
* <li>Service 层写操作:@Service 类的 @Transactional 公共方法必须有 @PreAuthorize(@Transactional 作为写操作指示器)</li>
|
||||
* <li>func_code 合法性:hasFunction('xxx') 的 xxx 必须是 V12 已注册的合法值</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* ponytail: KTD6 表达式形式盲区 — 本测试只校验 @PreAuthorize 存在,不校验表达式形式
|
||||
* (hasAuthorityAndAdmin vs 裸 hasAuthority)。裸 hasAuthority 会导致 system-user admin 旁路失效。
|
||||
* 当前由 UserServiceSecurityTest 抽样验证关键 ServiceImpl 的 admin 旁路。
|
||||
* 升级路径:扩展反射检查解析 SpEL 表达式 method 名,但引入 Spring Expression Parser 复杂度较高,性价比不足。
|
||||
*/
|
||||
@DisplayName("PreAuthorize 覆盖率守卫测试 (pms-auth)")
|
||||
class PreAuthorizeCoverageTest {
|
||||
|
||||
/** OQ6 决策:硬编码 InternalController 类名白名单(Feign 内部接口,依赖 InternalApiInterceptor 保护 /api/internal/** 路径) */
|
||||
private static final Set<String> WHITELISTED_CONTROLLERS = Set.of(
|
||||
"InternalController"
|
||||
);
|
||||
|
||||
/**
|
||||
* AuthServiceImpl 方法白名单(U2/U3 边界):AuthServiceImpl 由 U2 AuthController 层 @PreAuthorize 控制
|
||||
* (permitAll/isAuthenticated/管理接口),Service 层不加 @PreAuthorize 以保持 permitAll 语义
|
||||
* (permitAll 仅在 Controller 层生效,Service 层 @PreAuthorize 会拒绝未认证请求,破坏公开接口)。
|
||||
* <p>
|
||||
* 白名单方法:
|
||||
* - login / loginByPlatform:公开接口,Controller permitAll()
|
||||
* - changePassword / switchProject:当前用户操作,Controller isAuthenticated()
|
||||
* - resetPassword:管理员操作,Controller hasFunction+hasAuthorityAndAdmin,内部已有 UserContext 权限校验
|
||||
*/
|
||||
private static final Set<String> WHITELISTED_AUTH_SERVICE_METHODS = Set.of(
|
||||
"login", "loginByPlatform",
|
||||
"changePassword", "switchProject",
|
||||
"resetPassword"
|
||||
);
|
||||
|
||||
/** V12 已注册的合法 function_code 列表(与 V12 migration 同步) */
|
||||
private static final Set<String> VALID_FUNC_CODES = Set.of(
|
||||
"admin", "base", "charge", "operation",
|
||||
"user_management", "org_management", "role_management", "system_management",
|
||||
"viewer" // deprecated,但仍合法
|
||||
);
|
||||
|
||||
/** hasFunction('xxx') SpEL 表达式提取正则 */
|
||||
private static final Pattern HAS_FUNCTION_PATTERN = Pattern.compile("hasFunction\\(\\s*'([^']+)'\\s*\\)");
|
||||
|
||||
/** pms-auth 模块所有 @RestController/@Controller 类 */
|
||||
private static final List<Class<?>> CONTROLLERS = List.of(
|
||||
AuthController.class,
|
||||
UserController.class,
|
||||
OrgController.class,
|
||||
RoleController.class,
|
||||
PermissionController.class,
|
||||
ProjectUserController.class,
|
||||
PositionRoleController.class,
|
||||
InternalController.class
|
||||
);
|
||||
|
||||
/** pms-auth 模块所有 @Service 类(用于 Service 层写操作扫描) */
|
||||
private static final List<Class<?>> SERVICES = List.of(
|
||||
AuthServiceImpl.class,
|
||||
UserServiceImpl.class,
|
||||
OrgServiceImpl.class,
|
||||
RoleServiceImpl.class,
|
||||
ProjectUserServiceImpl.class,
|
||||
PositionRoleServiceImpl.class,
|
||||
PermissionServiceImpl.class // 只读,无 @Transactional 写方法
|
||||
);
|
||||
|
||||
static Stream<Arguments> publicMethods() {
|
||||
return CONTROLLERS.stream()
|
||||
.filter(c -> !WHITELISTED_CONTROLLERS.contains(c.getSimpleName()))
|
||||
.flatMap(c -> Arrays.stream(c.getDeclaredMethods())
|
||||
.filter(m -> Modifier.isPublic(m.getModifiers()))
|
||||
.filter(m -> !m.isBridge() && !m.isSynthetic())
|
||||
.map(m -> Arguments.of(c.getSimpleName(), m.getName(), m)));
|
||||
}
|
||||
|
||||
static Stream<Arguments> serviceWriteMethods() {
|
||||
return SERVICES.stream()
|
||||
.flatMap(c -> Arrays.stream(c.getDeclaredMethods())
|
||||
.filter(m -> Modifier.isPublic(m.getModifiers()))
|
||||
.filter(m -> !m.isBridge() && !m.isSynthetic())
|
||||
.filter(m -> m.isAnnotationPresent(Transactional.class))
|
||||
// AuthServiceImpl 白名单方法跳过(U2/U3 边界,见 WHITELISTED_AUTH_SERVICE_METHODS 注释)
|
||||
.filter(m -> !(c.getSimpleName().equals("AuthServiceImpl")
|
||||
&& WHITELISTED_AUTH_SERVICE_METHODS.contains(m.getName())))
|
||||
.map(m -> Arguments.of(c.getSimpleName(), m.getName(), m)));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Controller {0}#{1} 必须有 @PreAuthorize")
|
||||
@MethodSource("publicMethods")
|
||||
void controllerMethod_mustHavePreAuthorize(String className, String methodName, Method method) {
|
||||
assertThat(method.isAnnotationPresent(PreAuthorize.class))
|
||||
.as("%s#%s 缺少 @PreAuthorize 注解(白名单类已过滤)", className, methodName)
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Controller {0}#{1} 的 hasFunction func_code 必须合法")
|
||||
@MethodSource("publicMethods")
|
||||
void controllerMethod_funcCode_mustBeValid(String className, String methodName, Method method) {
|
||||
PreAuthorize ann = method.getAnnotation(PreAuthorize.class);
|
||||
if (ann == null) {
|
||||
return; // 由 controllerMethod_mustHavePreAuthorize 覆盖
|
||||
}
|
||||
String expr = ann.value();
|
||||
Matcher matcher = HAS_FUNCTION_PATTERN.matcher(expr);
|
||||
while (matcher.find()) {
|
||||
String funcCode = matcher.group(1);
|
||||
assertThat(VALID_FUNC_CODES)
|
||||
.as("%s#%s 的 hasFunction('%s') 不是 V12 合法 func_code", className, methodName, funcCode)
|
||||
.contains(funcCode);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "Service 写操作 {0}#{1} 必须有 @PreAuthorize(@Transactional 指示写操作)")
|
||||
@MethodSource("serviceWriteMethods")
|
||||
void serviceWriteMethod_mustHavePreAuthorize(String className, String methodName, Method method) {
|
||||
assertThat(method.isAnnotationPresent(PreAuthorize.class))
|
||||
.as("%s#%s 是 @Transactional 写操作但缺少 @PreAuthorize(system-user admin 旁路需要 @PreAuthorize 才能触发)",
|
||||
className, methodName)
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("白名单机制自检:InternalController 类被正确豁免")
|
||||
void whitelist_selfCheck() {
|
||||
// InternalController 在白名单中,应被过滤
|
||||
long internalCount = CONTROLLERS.stream()
|
||||
.filter(c -> WHITELISTED_CONTROLLERS.contains(c.getSimpleName()))
|
||||
.count();
|
||||
assertThat(internalCount).as("InternalController 应在白名单中").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V12 func_code 列表自检:包含 pms-auth 使用的全部 func_code")
|
||||
void validFuncCodes_selfCheck() {
|
||||
// pms-auth 使用 user_management / org_management / role_management / system_management
|
||||
assertThat(VALID_FUNC_CODES).contains(
|
||||
"user_management", "org_management", "role_management", "system_management");
|
||||
}
|
||||
|
||||
/**
|
||||
* V12 cross-check(P2 #9):解析 V12 migration SQL 提取实际定义的 function_code,
|
||||
* 验证 VALID_FUNC_CODES 列表与 V12 一致(避免 V12 加新 func_code 后测试列表漂移)。
|
||||
* <p>
|
||||
* V12 定义 8 个 func_code(admin/base/charge/operation/user_management/
|
||||
* role_management/org_management/system_management);'viewer' 是 V1 遗留,V12 未 UPDATE
|
||||
* 但仍在 t_role 中(Q10 决策保留),VALID_FUNC_CODES 单独覆盖。
|
||||
* <p>
|
||||
* ponytail: 不抽共享 fixture(需 Gradle testFixtures 插件配置),保留 3 模块列表重复,
|
||||
* 但 pms-auth 在此提供 V12 单点交叉检查 — V12 加新 func_code 时本测试强制失败,
|
||||
* 触发开发者同步更新 3 模块的 VALID_FUNC_CODES(pms-base/pms-audit 各自的 selfCheck 也会校验自身使用的 func_code)。
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("V12 cross-check: VALID_FUNC_CODES 包含 V12 migration 实际定义的全部 function_code")
|
||||
void validFuncCodes_matchesV12Migration() throws IOException {
|
||||
String v12Sql = loadV12MigrationSql();
|
||||
Set<String> v12DefinedFuncCodes = extractFuncCodesFromV12(v12Sql);
|
||||
|
||||
// V12 必须至少定义 8 个 func_code(4 UPDATE + 4 INSERT)
|
||||
assertThat(v12DefinedFuncCodes)
|
||||
.as("V12 应至少定义 8 个 func_code(4 UPDATE + 4 INSERT)")
|
||||
.hasSizeGreaterThanOrEqualTo(8);
|
||||
|
||||
// VALID_FUNC_CODES 必须包含 V12 实际定义的全部 func_code
|
||||
assertThat(VALID_FUNC_CODES)
|
||||
.as("VALID_FUNC_CODES 必须包含 V12 migration 实际定义的全部 function_code(V12 加新 code 时本断言失败,触发同步更新)")
|
||||
.containsAll(v12DefinedFuncCodes);
|
||||
|
||||
// VALID_FUNC_CODES 还必须包含 'viewer'(V1 遗留,V12 未 UPDATE 但仍合法)
|
||||
assertThat(VALID_FUNC_CODES)
|
||||
.as("VALID_FUNC_CODES 必须包含 'viewer'(V1 遗留,V12 未 UPDATE 但 Q10 决策保留)")
|
||||
.contains("viewer");
|
||||
}
|
||||
|
||||
/** 加载 V12 migration SQL 文件(pms-auth 模块 classpath 资源) */
|
||||
private String loadV12MigrationSql() throws IOException {
|
||||
try (InputStream is = getClass().getResourceAsStream(
|
||||
"/db/migration/V12__realign_function_code_and_add_management_roles.sql")) {
|
||||
assertThat(is)
|
||||
.as("V12 migration 文件必须在 classpath: /db/migration/V12__realign_function_code_and_add_management_roles.sql")
|
||||
.isNotNull();
|
||||
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 V12 SQL 提取实际定义的 function_code 集合:
|
||||
* <ul>
|
||||
* <li>UPDATE 模式:`` `function_code` = 'xxx' ``(反引号包裹列名)</li>
|
||||
* <li>INSERT 模式:INSERT INTO t_role VALUES (id, 'ROLE_CODE', '名称', 'func_code', 1, UNIX_TIMESTAMP()...)
|
||||
* — 匹配 `'func_code',\s*1,\s*UNIX_TIMESTAMP`(function_code 后跟 data_scope=1 与时间戳)</li>
|
||||
* </ul>
|
||||
* V12 的 INSERT 是单条多 VALUES 元组,第 2 个正则用全局 find() 捕获全部 4 个元组。
|
||||
*/
|
||||
private Set<String> extractFuncCodesFromV12(String sql) {
|
||||
// UPDATE 模式:`function_code` = 'xxx'(反引号可选,兼容注释中无反引号的提及)
|
||||
Pattern updatePattern = Pattern.compile(
|
||||
"`?function_code`?\\s*=\\s*'([^']+)'", Pattern.CASE_INSENSITIVE);
|
||||
// INSERT 模式:'func_code', 1, UNIX_TIMESTAMP — 匹配每个 VALUES 元组中的 function_code
|
||||
Pattern insertTuplePattern = Pattern.compile(
|
||||
"'([^']+)'\\s*,\\s*1\\s*,\\s*UNIX_TIMESTAMP", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
Set<String> codes = new java.util.HashSet<>();
|
||||
Matcher updateMatcher = updatePattern.matcher(sql);
|
||||
while (updateMatcher.find()) {
|
||||
codes.add(updateMatcher.group(1));
|
||||
}
|
||||
Matcher insertMatcher = insertTuplePattern.matcher(sql);
|
||||
while (insertMatcher.find()) {
|
||||
codes.add(insertMatcher.group(1));
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
|
|
@ -212,17 +212,19 @@ class UserRoleControllerTest {
|
|||
}
|
||||
|
||||
/**
|
||||
* 验证 assignRoles 方法标注了 @PreAuthorize('system:role:manage'),
|
||||
* 验证 assignRoles 方法标注了 @PreAuthorize('hasFunction(user_management)'),
|
||||
* 无此权限的用户调用将返回 403(由 Spring Security 方法级鉴权保证)。
|
||||
* PR2-PR4 改造(U3):Controller 层从 hasAuthority('system:role:manage') 改为 hasFunction('user_management'),
|
||||
* Service 层(UserServiceImpl.assignRoles)通过 hasAuthorityAndAdmin('system:role:manage') 控制操作粒度。
|
||||
*/
|
||||
@Test
|
||||
@DisplayName("assignRoles 方法标注 @PreAuthorize('system:role:manage')(无权限返回403)")
|
||||
@DisplayName("assignRoles 方法标注 @PreAuthorize('hasFunction(user_management)')(无权限返回403)")
|
||||
void assignRoles_hasPreAuthorizeAnnotation() throws NoSuchMethodException {
|
||||
Method method = UserController.class.getMethod("assignRoles", Long.class, AssignRolesRequest.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
|
||||
assertThat(preAuthorize).isNotNull();
|
||||
assertThat(preAuthorize.value()).contains("system:role:manage");
|
||||
assertThat(preAuthorize.value()).contains("hasFunction('user_management')");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -236,7 +238,7 @@ class UserRoleControllerTest {
|
|||
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
assertThat(preAuthorize).isNotNull();
|
||||
assertThat(preAuthorize.value()).contains("system:role:manage");
|
||||
assertThat(preAuthorize.value()).contains("hasFunction('user_management')");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -246,7 +248,7 @@ class UserRoleControllerTest {
|
|||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
|
||||
assertThat(preAuthorize).isNotNull();
|
||||
assertThat(preAuthorize.value()).contains("system:role:manage");
|
||||
assertThat(preAuthorize.value()).contains("hasFunction('user_management')");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
package com.pms.auth.migration;
|
||||
|
||||
import com.pms.common.security.SystemUserSecurityContext;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* V13 system-user migration 静态守卫测试(P1 #5 ponytail runnable check)
|
||||
* <p>
|
||||
* 设计依据:ce-code-review P1 #5 — V13 插入 system 占位用户行(id=0L, username='system'),
|
||||
* 此行存在性是 R4 system-user admin 旁路方案的根依赖({@link SystemUserSecurityContext#SYSTEM_USER_ID}
|
||||
* 必须在 t_user 表中存在,否则 setCreatedBy(null) 破坏 NOT NULL 约束)。
|
||||
* <p>
|
||||
* 测试策略:pms-auth 模块无 testcontainers/DB 集成测试基础设施,遵循 Mockito-only 单元测试约定。
|
||||
* 本测试以静态 SQL 资源校验方式运行:加载 V13__add_system_user.sql 作为 classpath 资源,
|
||||
* 解析 INSERT 语句验证关键列值与 SystemUserSecurityContext 常量一致。
|
||||
* <p>
|
||||
* 守卫范围:
|
||||
* <ul>
|
||||
* <li>id = 0(= {@link SystemUserSecurityContext#SYSTEM_USER_ID})</li>
|
||||
* <li>username = 'system'(= {@link SystemUserSecurityContext#SYSTEM_USERNAME})</li>
|
||||
* <li>status = 1(启用,避免 AuthServiceImpl.login 走"账号禁用"分支混淆)</li>
|
||||
* <li>account_type = 1(普通账号类型)</li>
|
||||
* <li>deleted = 0(未删除)</li>
|
||||
* <li>real_name 含"系统占位用户"(标识用途)</li>
|
||||
* <li>password 为 BCrypt 哈希(非明文,非空)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* ponytail: 不引入 H2/testcontainers — 仅校验 SQL 文本静态正确性,DB 实际执行行为由 Flyway 启动时
|
||||
* 与 post-deploy 验证 SQL 覆盖。本测试捕获"SQL 编辑回归"风险(如误删 INSERT、改 id/username)。
|
||||
*/
|
||||
@DisplayName("V13 system-user migration 静态守卫测试")
|
||||
class V13SystemUserMigrationTest {
|
||||
|
||||
private static final String MIGRATION_PATH = "/db/migration/V13__add_system_user.sql";
|
||||
|
||||
/**
|
||||
* INSERT INTO `t_user` (...) VALUES (0, NULL, 'system', '$2a$...', '系统占位用户', 1, 1, ...) —
|
||||
* 提取 VALUES 子句前 6 个值(id, project_id, username, password, real_name, status)。
|
||||
* 注释剥离后匹配,允许 SQL 文本跨多行书写。
|
||||
*/
|
||||
private static final Pattern VALUES_PATTERN = Pattern.compile(
|
||||
"VALUES\\s*\\(\\s*(\\d+)\\s*,\\s*NULL\\s*,\\s*'([^']+)'\\s*,\\s*'([^']+)'\\s*,"
|
||||
+ "\\s*'([^']+)'\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
|
||||
|
||||
/** SQL 行注释 (-- 至行尾),剥离后便于正则匹配跨行 VALUES */
|
||||
private static final Pattern LINE_COMMENT_PATTERN = Pattern.compile("--[^\\n]*\\n");
|
||||
|
||||
@Test
|
||||
@DisplayName("V13 migration 文件存在于 classpath")
|
||||
void migrationFile_existsOnClasspath() throws IOException {
|
||||
try (InputStream is = getClass().getResourceAsStream(MIGRATION_PATH)) {
|
||||
assertThat(is)
|
||||
.as("V13 migration 文件必须在 classpath: %s", MIGRATION_PATH)
|
||||
.isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V13 INSERT 包含 system 占位用户行:id=0L, username='system', status=1, account_type=1, deleted=0")
|
||||
void v13Insert_systemUserRow_matchesSystemUserSecurityContextConstants() throws IOException {
|
||||
String sql = stripComments(loadMigrationSql());
|
||||
Matcher matcher = VALUES_PATTERN.matcher(sql);
|
||||
assertThat(matcher.find())
|
||||
.as("V13 必须包含 INSERT INTO t_user VALUES (0, NULL, 'system', ...) 语句")
|
||||
.isTrue();
|
||||
|
||||
// id: 第 1 组 — V13 注释要求 id=0
|
||||
String idValue = matcher.group(1);
|
||||
assertThat(idValue)
|
||||
.as("V13 system-user 行 id 必须为 0(= SystemUserSecurityContext.SYSTEM_USER_ID)")
|
||||
.isEqualTo("0");
|
||||
|
||||
// username: 第 2 组
|
||||
String username = matcher.group(2);
|
||||
assertThat(username)
|
||||
.as("V13 system-user 行 username 必须为 'system'(= SystemUserSecurityContext.SYSTEM_USERNAME)")
|
||||
.isEqualTo(SystemUserSecurityContext.SYSTEM_USERNAME);
|
||||
|
||||
// password: 第 3 组(BCrypt 哈希,$2a$10$... 格式)
|
||||
String password = matcher.group(3);
|
||||
assertThat(password)
|
||||
.as("V13 system-user 行 password 必须为 BCrypt 哈希格式($2a$... 起头),非明文")
|
||||
.startsWith("$2a$");
|
||||
|
||||
// real_name: 第 4 组
|
||||
String realName = matcher.group(4);
|
||||
assertThat(realName)
|
||||
.as("V13 system-user 行 real_name 含'系统占位用户'标识用途")
|
||||
.contains("系统占位用户");
|
||||
|
||||
// status: 第 5 组
|
||||
int status = Integer.parseInt(matcher.group(5));
|
||||
assertThat(status)
|
||||
.as("V13 system-user 行 status=1(启用)")
|
||||
.isEqualTo(1);
|
||||
|
||||
// account_type: 第 6 组
|
||||
int accountType = Integer.parseInt(matcher.group(6));
|
||||
assertThat(accountType)
|
||||
.as("V13 system-user 行 account_type=1(普通账号类型)")
|
||||
.isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V13 INSERT 包含 deleted=0 标记(未软删除)")
|
||||
void v13Insert_systemUserRow_notSoftDeleted() throws IOException {
|
||||
String sql = stripComments(loadMigrationSql());
|
||||
// VALUES 子句末尾倒数第 1 个数字字段是 deleted(见 V13 SQL 列顺序:..., deleted)
|
||||
// 匹配 ..., 0\n); 模式 — deleted=0
|
||||
Pattern deletedPattern = Pattern.compile(",\\s*0\\s*\\)\\s*$", Pattern.MULTILINE);
|
||||
assertThat(deletedPattern.matcher(sql).find())
|
||||
.as("V13 system-user 行 deleted=0(未软删除,避免被 BaseModel 自动过滤)")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V13 包含 ON DUPLICATE KEY UPDATE(幂等执行)")
|
||||
void v13Insert_idempotent() throws IOException {
|
||||
String sql = loadMigrationSql();
|
||||
assertThat(sql)
|
||||
.as("V13 必须包含 ON DUPLICATE KEY UPDATE 保证重复执行幂等")
|
||||
.containsIgnoringCase("ON DUPLICATE KEY UPDATE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("V13 注释引用 R4/OQ5 决策(可追溯性)")
|
||||
void v13Comments_referenceR4AndOQ5Decisions() throws IOException {
|
||||
String sql = loadMigrationSql();
|
||||
assertThat(sql)
|
||||
.as("V13 注释必须引用 R4 system-user 方案(决策可追溯)")
|
||||
.contains("R4");
|
||||
assertThat(sql)
|
||||
.as("V13 注释必须引用 OQ5 决策(决策可追溯)")
|
||||
.contains("OQ5");
|
||||
}
|
||||
|
||||
private String loadMigrationSql() throws IOException {
|
||||
try (InputStream is = getClass().getResourceAsStream(MIGRATION_PATH)) {
|
||||
assertThat(is).as("V13 migration 文件必须在 classpath").isNotNull();
|
||||
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
/** 剥离 SQL 行注释 (-- 至行尾),便于正则匹配跨行 VALUES 子句 */
|
||||
private String stripComments(String sql) {
|
||||
return LINE_COMMENT_PATTERN.matcher(sql).replaceAll("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -271,6 +271,58 @@ class AuthServiceTest {
|
|||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("账号已被锁定");
|
||||
}
|
||||
|
||||
// ===== system 占位用户拒绝登录(R4 缓解策略 (7) + ce-code-review P1 #2 信任边界修复) =====
|
||||
|
||||
@Test
|
||||
@DisplayName("system 用户名拒绝:username='system' → 直接抛 ACCOUNT_DISABLED,不查 DB")
|
||||
void login_systemUsername_throwsBeforeDbLookup() {
|
||||
LoginRequest request = buildLoginRequest();
|
||||
request.setUsername("system");
|
||||
|
||||
assertThatThrownBy(() -> authService.login(request, "127.0.0.1"))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("账号已被禁用");
|
||||
|
||||
// 验证 defense-in-depth:用户名拒绝发生在 DB 查询前
|
||||
verify(userMapper, never()).selectUserWithRoles(anyString());
|
||||
verify(userMapper, never()).selectById(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("system 用户名拒绝:username='System' 大小写变体 → 拒绝(DB collate 大小写不敏感,必须 equalsIgnoreCase)")
|
||||
void login_systemUsernameCaseVariant_throwsBeforeDbLookup() {
|
||||
LoginRequest request = buildLoginRequest();
|
||||
request.setUsername("System");
|
||||
|
||||
assertThatThrownBy(() -> authService.login(request, "127.0.0.1"))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("账号已被禁用");
|
||||
|
||||
verify(userMapper, never()).selectUserWithRoles(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("system 用户名拒绝:username='SYSTEM' 全大写 → 拒绝")
|
||||
void login_systemUsernameUppercase_throwsBeforeDbLookup() {
|
||||
LoginRequest request = buildLoginRequest();
|
||||
request.setUsername("SYSTEM");
|
||||
|
||||
assertThatThrownBy(() -> authService.login(request, "127.0.0.1"))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("账号已被禁用");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("system 用户名拒绝:username=' system ' 带空白 → trim 后拒绝")
|
||||
void login_systemUsernameWithWhitespace_throwsAfterTrim() {
|
||||
LoginRequest request = buildLoginRequest();
|
||||
request.setUsername(" system ");
|
||||
|
||||
assertThatThrownBy(() -> authService.login(request, "127.0.0.1"))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("账号已被禁用");
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
package com.pms.auth.service;
|
||||
|
||||
import com.pms.auth.dto.AssignRolesRequest;
|
||||
import com.pms.auth.dto.UserSaveRequest;
|
||||
import com.pms.auth.dto.UserUpdateRequest;
|
||||
import com.pms.auth.entity.User;
|
||||
import com.pms.auth.mapper.ProjectUserMapper;
|
||||
import com.pms.auth.mapper.RoleMapper;
|
||||
import com.pms.auth.mapper.UserMapper;
|
||||
import com.pms.auth.mapper.UserRoleMapper;
|
||||
import com.pms.auth.service.impl.UserServiceImpl;
|
||||
import com.pms.common.exception.BusinessException;
|
||||
import com.pms.common.security.CustomMethodSecurityExpressionHandler;
|
||||
import com.pms.common.security.SystemUserSecurityContext;
|
||||
import com.pms.common.security.UserContext.CurrentUser;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* UserService @PreAuthorize 切片测试(U3)
|
||||
* <p>
|
||||
* 验证 service 层 {@code @PreAuthorize("hasAuthorityAndAdmin('system:user:manage'|'system:role:manage')")} 实际触发:
|
||||
* <ul>
|
||||
* <li>场景 1(Happy path):functions='user_management' + perm='system:user:manage' → create 放行</li>
|
||||
* <li>场景 2(Error path):functions='user_management' + 无 perm_code → create 抛 AccessDeniedException</li>
|
||||
* <li>场景 3(Admin bypass):functions='admin' + 无 perm_code → create 放行(admin 旁路)</li>
|
||||
* <li>场景 4(Edge case):functions='user_management' + perm='system:user:manage' → delete 放行</li>
|
||||
* <li>场景 5(assignRoles 权限):functions='user_management' + perm='system:role:manage' → assignRoles 放行</li>
|
||||
* <li>场景 6(assignRoles 无权限):functions='user_management' + 无 perm_code → assignRoles 抛 AccessDeniedException</li>
|
||||
* <li>场景 7(无 SecurityContext):delete 抛 AuthenticationCredentialsNotFoundException</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* SpEL 逻辑(hasFunction / hasAuthorityAndAdmin)由 CustomMethodSecurityExpressionRootTest 单元测试覆盖,
|
||||
* 本测试仅验证 @PreAuthorize AOP 代理链路集成。
|
||||
* <p>
|
||||
* 设计依据:docs/plans/2026-07-05-002-refactor-pre-authorize-alignment-pr2-pr4-plan.md U3 Test scenarios
|
||||
* <p>
|
||||
* 实现说明:手动构造 AOP 代理(ProxyFactory + AuthorizationManagerBeforeMethodInterceptor),
|
||||
* 避免 @SpringBootTest 中 @EnableMethodSecurity 的 @ConditionalOnMissingBean 评估顺序问题。
|
||||
*/
|
||||
@DisplayName("UserService @PreAuthorize 切片测试")
|
||||
class UserServiceSecurityTest {
|
||||
|
||||
private UserService proxy;
|
||||
private UserMapper userMapper;
|
||||
private UserRoleMapper userRoleMapper;
|
||||
private ProjectUserMapper projectUserMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userMapper = mock(UserMapper.class);
|
||||
RoleMapper roleMapper = mock(RoleMapper.class);
|
||||
userRoleMapper = mock(UserRoleMapper.class);
|
||||
projectUserMapper = mock(ProjectUserMapper.class);
|
||||
PasswordEncoder passwordEncoder = mock(PasswordEncoder.class);
|
||||
|
||||
UserServiceImpl target = new UserServiceImpl(
|
||||
userMapper, roleMapper, userRoleMapper, projectUserMapper, passwordEncoder);
|
||||
|
||||
// 构造自定义表达式处理器 + PreAuthorize 授权管理器
|
||||
PreAuthorizeAuthorizationManager authManager = new PreAuthorizeAuthorizationManager();
|
||||
authManager.setExpressionHandler(new CustomMethodSecurityExpressionHandler());
|
||||
|
||||
// 构造 @PreAuthorize 拦截器(pointcut 匹配 @PreAuthorize 注解方法)
|
||||
MethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(authManager);
|
||||
|
||||
// 构造 AOP 代理
|
||||
ProxyFactory factory = new ProxyFactory(target);
|
||||
factory.addAdvice(interceptor);
|
||||
proxy = (UserService) factory.getProxy();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearContext() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造以 CurrentUser 为 principal 的 Authentication 并写入 SecurityContextHolder。
|
||||
*
|
||||
* @param functions 职能代码(逗号分隔),如 "user_management" / "admin" / "user_management,role_management"
|
||||
* @param permissions 权限码(逗号分隔),如 "system:user:manage" / null
|
||||
*/
|
||||
private void authenticate(String functions, String permissions) {
|
||||
CurrentUser user = new CurrentUser(1L, "admin", 100L, "ROLE_ADMIN", "1",
|
||||
permissions, 200L, null, functions);
|
||||
List<GrantedAuthority> authorities = new ArrayList<>();
|
||||
if (permissions != null && !permissions.isBlank()) {
|
||||
authorities.addAll(Arrays.stream(permissions.split(","))
|
||||
.map(String::trim)
|
||||
.filter(p -> !p.isEmpty())
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
new PreAuthenticatedAuthenticationToken(user, null, authorities));
|
||||
}
|
||||
|
||||
private UserSaveRequest buildCreateRequest() {
|
||||
UserSaveRequest request = new UserSaveRequest();
|
||||
request.setUsername("testuser");
|
||||
request.setPassword("Test@123456");
|
||||
request.setRealName("测试用户");
|
||||
return request;
|
||||
}
|
||||
|
||||
private User buildExistingUser() {
|
||||
User user = new User();
|
||||
user.setId(1L);
|
||||
user.setUsername("existing");
|
||||
user.setAccountType(1);
|
||||
return user;
|
||||
}
|
||||
|
||||
// ===== 场景 1:Happy path: functions='user_management' + perm='system:user:manage' → create 放行 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景1: functions='user_management' + perm='system:user:manage' → create 放行")
|
||||
void create_withUserManagementFunctionAndPerm_shouldAllow() {
|
||||
authenticate("user_management", "system:user:manage");
|
||||
when(userMapper.selectCount(any())).thenReturn(0L);
|
||||
when(userMapper.insert(any())).thenReturn(1);
|
||||
assertThatCode(() -> proxy.create(buildCreateRequest()))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// ===== 场景 2:Error path: functions='user_management' + 无 perm_code → create 抛 AccessDeniedException =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景2: functions='user_management' + 无 perm_code → create 抛 AccessDeniedException")
|
||||
void create_withUserManagementFunctionButNoPerm_shouldDeny() {
|
||||
authenticate("user_management", null);
|
||||
assertThatThrownBy(() -> proxy.create(buildCreateRequest()))
|
||||
.isInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
// ===== 场景 3:Admin bypass: functions='admin' + 无 perm_code → create 放行 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景3: functions='admin' + 无 perm_code → create 放行(admin 旁路)")
|
||||
void create_withAdminFunction_shouldBypass() {
|
||||
authenticate("admin", null);
|
||||
when(userMapper.selectCount(any())).thenReturn(0L);
|
||||
when(userMapper.insert(any())).thenReturn(1);
|
||||
assertThatCode(() -> proxy.create(buildCreateRequest()))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// ===== 场景 4:Edge case: functions='user_management' + perm='system:user:manage' → delete 放行 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景4: functions='user_management' + perm='system:user:manage' → delete 放行")
|
||||
void delete_withUserManagementFunctionAndPerm_shouldAllow() {
|
||||
authenticate("user_management", "system:user:manage");
|
||||
when(userMapper.selectById(any())).thenReturn(buildExistingUser());
|
||||
assertThatCode(() -> proxy.delete(1L))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// ===== 场景 5:assignRoles 权限: functions='user_management' + perm='system:role:manage' → assignRoles 放行 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景5: functions='user_management' + perm='system:role:manage' → assignRoles 放行")
|
||||
void assignRoles_withRoleManagePerm_shouldAllow() {
|
||||
authenticate("user_management", "system:role:manage");
|
||||
when(userMapper.selectById(any())).thenReturn(buildExistingUser());
|
||||
AssignRolesRequest request = new AssignRolesRequest();
|
||||
request.setRoleIds(List.of(1L));
|
||||
assertThatCode(() -> proxy.assignRoles(1L, request))
|
||||
.doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
// ===== 场景 6:assignRoles 无权限: functions='user_management' + 无 perm_code → assignRoles 抛 AccessDeniedException =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景6: functions='user_management' + 无 perm_code → assignRoles 抛 AccessDeniedException")
|
||||
void assignRoles_withUserManagementFunctionButNoPerm_shouldDeny() {
|
||||
authenticate("user_management", null);
|
||||
AssignRolesRequest request = new AssignRolesRequest();
|
||||
request.setRoleIds(List.of(1L));
|
||||
assertThatThrownBy(() -> proxy.assignRoles(1L, request))
|
||||
.isInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
// ===== 场景 7:无 SecurityContext → delete 抛 AuthenticationCredentialsNotFoundException =====
|
||||
|
||||
@Test
|
||||
@DisplayName("场景7: 无 SecurityContext → delete 抛 AuthenticationCredentialsNotFoundException")
|
||||
void delete_withoutAuthentication_shouldDeny() {
|
||||
// SecurityContext 未设置(authenticate 未调用)
|
||||
// Spring Security 标准行为:无 Authentication 时抛 AuthenticationCredentialsNotFoundException,
|
||||
// 生产环境由 ExceptionTranslationFilter 转为 401 响应
|
||||
assertThatThrownBy(() -> proxy.delete(1L))
|
||||
.isInstanceOf(AuthenticationCredentialsNotFoundException.class);
|
||||
}
|
||||
|
||||
// ===== 补充:functions='base'(非 user_management 非 admin)→ create 拒绝 =====
|
||||
|
||||
@Test
|
||||
@DisplayName("补充: functions='base' + 无 perm_code → create 抛 AccessDeniedException")
|
||||
void create_withBaseFunction_shouldDeny() {
|
||||
authenticate("base", null);
|
||||
assertThatThrownBy(() -> proxy.create(buildCreateRequest()))
|
||||
.isInstanceOf(AccessDeniedException.class);
|
||||
}
|
||||
|
||||
// ===== OQ5 system 占位用户删除守卫(ce-code-review P1 #7 ponytail runnable check) =====
|
||||
|
||||
@Test
|
||||
@DisplayName("OQ5: admin 旁路通过 @PreAuthorize 后,delete(systemUserId=0L) 抛 BusinessException 阻断")
|
||||
void delete_withSystemUserId_throwsBusinessException() {
|
||||
authenticate("admin", null); // admin 旁路通过 @PreAuthorize
|
||||
// 不 mock userMapper.selectById/deleteById,因为业务守卫应在 mapper 调用前抛异常
|
||||
assertThatThrownBy(() -> proxy.delete(SystemUserSecurityContext.SYSTEM_USER_ID))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("系统账户不可删除");
|
||||
// 验证:未触发实际删除(DB 调用从未发生)
|
||||
verifyNoInteractions(userMapper);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.pms.base.dto.event.ProjectCreatedEvent;
|
|||
import com.pms.base.service.ProjectInitService;
|
||||
import com.pms.common.entity.MqConsumeLog;
|
||||
import com.pms.common.mapper.MqConsumeLogMapper;
|
||||
import com.pms.common.security.SystemUserSecurityContext;
|
||||
import com.pms.common.util.JsonUtils;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
|
@ -75,7 +76,12 @@ public class ProjectCreatedConsumer {
|
|||
}
|
||||
|
||||
// 3. 预置主数据
|
||||
presetMainData(event);
|
||||
// R4 缓解策略:MQ 消费者入口建立 system-user SecurityContext
|
||||
// 使 ProjectInitService.initProject → AbilityPackageService.assignPackages 的
|
||||
// @PreAuthorize admin 旁路生效;try-with-resources 自动清理,避免线程池权限泄漏
|
||||
try (SystemUserSecurityContext ctx = SystemUserSecurityContext.system()) {
|
||||
presetMainData(event);
|
||||
}
|
||||
|
||||
// 4. 记录消费成功
|
||||
MqConsumeLog consumeLog = new MqConsumeLog();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 能力包版本控制器(U21 变更审批流)
|
||||
* 写操作需系统管理员(A4)权限
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):5 方法统一标注 `hasFunction('base')` 控制模块入口;
|
||||
* 各方法保留 RoleGuard.assertA4() 调用,与 @PreAuthorize 叠加(A4 双重防护)。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -29,7 +32,7 @@ public class AbilityPackageController {
|
|||
* 创建版本草稿
|
||||
*/
|
||||
@PostMapping("/ability-packages/{packageId}/versions")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "ABILITY_PACKAGE", type = "CREATE", description = "创建能力包版本草稿")
|
||||
public Result<Map<String, Long>> createDraft(@PathVariable Long packageId) {
|
||||
RoleGuard.assertA4();
|
||||
|
|
@ -43,7 +46,7 @@ public class AbilityPackageController {
|
|||
* 提交审批
|
||||
*/
|
||||
@PostMapping("/ability-package-versions/{versionId}/submit")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "ABILITY_PACKAGE", type = "UPDATE", description = "提交能力包版本审批")
|
||||
public Result<Void> submitForApproval(@PathVariable Long versionId) {
|
||||
RoleGuard.assertA4();
|
||||
|
|
@ -55,7 +58,7 @@ public class AbilityPackageController {
|
|||
* 审批通过(snapshot 含配置明细,recordParams=false 脱敏)
|
||||
*/
|
||||
@PostMapping("/ability-package-versions/{versionId}/approve")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "ABILITY_PACKAGE", type = "UPDATE", description = "审批通过能力包版本", recordParams = false)
|
||||
public Result<Void> approve(@PathVariable Long versionId,
|
||||
@RequestParam(required = false) String comment) {
|
||||
|
|
@ -68,7 +71,7 @@ public class AbilityPackageController {
|
|||
* 驳回(recordParams=false 避免驳回原因等明细入审计)
|
||||
*/
|
||||
@PostMapping("/ability-package-versions/{versionId}/reject")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "ABILITY_PACKAGE", type = "UPDATE", description = "驳回能力包版本", recordParams = false)
|
||||
public Result<Void> reject(@PathVariable Long versionId,
|
||||
@RequestParam String reason) {
|
||||
|
|
@ -81,7 +84,7 @@ public class AbilityPackageController {
|
|||
* 删除版本(仅 DRAFT/REJECTED;APPROVED 被引用时拒绝)
|
||||
*/
|
||||
@DeleteMapping("/ability-package-versions/{versionId}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "ABILITY_PACKAGE", type = "DELETE", description = "删除能力包版本")
|
||||
public Result<Void> delete(@PathVariable Long versionId) {
|
||||
RoleGuard.assertA4();
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import java.util.Map;
|
|||
* 路径前缀: /api/v1/base/approval-flow-configs
|
||||
* <p>
|
||||
* 写操作需系统管理员(A4)权限(R23)
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):3 方法统一标注 `hasFunction('base')` 控制模块入口;
|
||||
* save 方法保留 RoleGuard.assertA4() 调用,与 @PreAuthorize 叠加(A4 双重防护)。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -34,7 +37,7 @@ public class ApprovalFlowConfigController {
|
|||
* 按阶段流转标识查询启用模板
|
||||
*/
|
||||
@GetMapping("/transition/{transition}")
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<ApprovalFlowTemplateDTO> getByTransition(@PathVariable("transition") String transition) {
|
||||
return Result.success(approvalFlowConfigService.getTemplateByTransition(transition));
|
||||
}
|
||||
|
|
@ -43,7 +46,7 @@ public class ApprovalFlowConfigController {
|
|||
* 列出全部模板
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<ApprovalFlowTemplateDTO>> list() {
|
||||
return Result.success(approvalFlowConfigService.listTemplates());
|
||||
}
|
||||
|
|
@ -52,7 +55,7 @@ public class ApprovalFlowConfigController {
|
|||
* 保存模板(创建或更新)
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "审批流配置修改")
|
||||
public Result<Map<String, Long>> save(@Valid @RequestBody ApprovalFlowTemplateSaveRequest request) {
|
||||
RoleGuard.assertA4();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 归档闭环控制器
|
||||
* 路径前缀: /api/v1/base/projects/{projectId}/archive
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):2 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:lifecycle:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -30,7 +33,7 @@ public class ArchiveController {
|
|||
* 发起归档(结算校验 → 资产移交 → 归档审批)
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "项目归档")
|
||||
public Result<Map<String, Long>> archive(@PathVariable Long projectId,
|
||||
@Valid @RequestBody AssetHandoverRequest request) {
|
||||
|
|
@ -44,7 +47,7 @@ public class ArchiveController {
|
|||
* 发起归档恢复
|
||||
*/
|
||||
@PostMapping("/recover")
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "归档恢复")
|
||||
public Result<Map<String, Long>> recover(@PathVariable Long projectId,
|
||||
@Valid @RequestBody ArchiveRecoveryRequest request) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class BuildingController {
|
|||
* 楼栋列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:building:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<BuildingDTO>> list(BuildingDTO query) {
|
||||
return Result.success(buildingService.page(query));
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ public class BuildingController {
|
|||
* 创建楼栋
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:building:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建楼栋")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody BuildingSaveRequest request) {
|
||||
Long id = buildingService.create(request);
|
||||
|
|
@ -56,7 +56,7 @@ public class BuildingController {
|
|||
* 楼栋详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:building:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<BuildingDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(buildingService.getById(id));
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ public class BuildingController {
|
|||
* 更新楼栋
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:building:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新楼栋")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody BuildingSaveRequest request) {
|
||||
buildingService.update(id, request);
|
||||
|
|
@ -76,7 +76,7 @@ public class BuildingController {
|
|||
* 删除楼栋
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:building:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除楼栋")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
buildingService.delete(id);
|
||||
|
|
@ -87,7 +87,7 @@ public class BuildingController {
|
|||
* 楼栋树(楼栋→楼层→房间)
|
||||
*/
|
||||
@GetMapping("/tree")
|
||||
@PreAuthorize("hasAuthority('base:building:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<BuildingTreeDTO>> tree(@RequestParam(required = false) Long buildingId) {
|
||||
return Result.success(buildingService.tree(UserContext.getProjectId(), buildingId));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class CamChargeController {
|
|||
* @param chargePeriod 分摊周期(如 2024-01)
|
||||
*/
|
||||
@PostMapping("/allocate")
|
||||
@PreAuthorize("hasAuthority('base:finance:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "执行 CAM 分摊")
|
||||
public Result<List<CamCharge>> allocate(@RequestParam Long totalAmountFen,
|
||||
@RequestParam String chargePeriod) {
|
||||
|
|
@ -43,7 +43,7 @@ public class CamChargeController {
|
|||
* 查询当前项目某周期的分摊明细
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:finance:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<CamCharge>> list(@RequestParam String period) {
|
||||
Long projectId = UserContext.getProjectId();
|
||||
return Result.success(camChargeService.listByProjectAndPeriod(projectId, period));
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class CommunityActivityController {
|
|||
* 分页查询活动列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:activity:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<CommunityActivity>> page(
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
|
|
@ -43,7 +43,7 @@ public class CommunityActivityController {
|
|||
* 活动详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:activity:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<CommunityActivity> getById(@PathVariable Long id) {
|
||||
return Result.success(communityActivityService.getById(id));
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ public class CommunityActivityController {
|
|||
* 创建活动
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:activity:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建社区活动")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody CommunityActivity activity) {
|
||||
Long id = communityActivityService.create(activity);
|
||||
|
|
@ -65,7 +65,7 @@ public class CommunityActivityController {
|
|||
* 更新活动
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:activity:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新社区活动")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody CommunityActivity activity) {
|
||||
communityActivityService.update(id, activity);
|
||||
|
|
@ -76,7 +76,7 @@ public class CommunityActivityController {
|
|||
* 报名参加活动
|
||||
*/
|
||||
@PostMapping("/{id}/sign-up")
|
||||
@PreAuthorize("hasAuthority('base:activity:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "活动报名")
|
||||
public Result<Void> signUp(@PathVariable Long id) {
|
||||
communityActivityService.signUp(id);
|
||||
|
|
@ -87,7 +87,7 @@ public class CommunityActivityController {
|
|||
* 删除活动
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:activity:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除社区活动")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
communityActivityService.delete(id);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class ContractController {
|
|||
* 合同列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<ContractDTO>> list(ContractDTO query) {
|
||||
return Result.success(contractService.page(query));
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ public class ContractController {
|
|||
* 创建合同
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建合同")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody ContractSaveRequest request) {
|
||||
Long id = contractService.create(request);
|
||||
|
|
@ -55,7 +55,7 @@ public class ContractController {
|
|||
* 合同详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<ContractDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(contractService.getById(id));
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ public class ContractController {
|
|||
* 更新合同
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新合同")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody ContractSaveRequest request) {
|
||||
contractService.update(id, request);
|
||||
|
|
@ -75,7 +75,7 @@ public class ContractController {
|
|||
* 删除合同
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除合同")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
contractService.delete(id);
|
||||
|
|
@ -86,7 +86,7 @@ public class ContractController {
|
|||
* 即将到期合同列表
|
||||
*/
|
||||
@GetMapping("/expiring")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<ContractDTO>> expiring(@RequestParam(defaultValue = "30") int days) {
|
||||
return Result.success(contractService.expiring(days));
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ public class ContractController {
|
|||
* 合同续签
|
||||
*/
|
||||
@PostMapping("/{id}/renew")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "合同续签")
|
||||
public Result<Map<String, Long>> renew(@PathVariable Long id, @Valid @RequestBody ContractRenewRequest request) {
|
||||
Long newId = contractService.renew(id, request);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class ContractTypeController {
|
|||
* 合同类型列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<ContractTypeDTO>> list() {
|
||||
return Result.success(contractTypeService.list(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ public class ContractTypeController {
|
|||
* 创建合同类型
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建合同类型")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody ContractTypeSaveRequest request) {
|
||||
Long id = contractTypeService.create(request);
|
||||
|
|
@ -54,7 +54,7 @@ public class ContractTypeController {
|
|||
* 更新合同类型
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新合同类型")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody ContractTypeSaveRequest request) {
|
||||
contractTypeService.update(id, request);
|
||||
|
|
@ -65,7 +65,7 @@ public class ContractTypeController {
|
|||
* 删除合同类型
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:contract:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除合同类型")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
contractTypeService.delete(id);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.pms.common.response.Result;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -24,6 +25,8 @@ import java.util.Map;
|
|||
/**
|
||||
* 仪表盘统计控制器(基础服务)
|
||||
* 路径前缀: /api/v1/base/dashboard
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):1 方法标注 `hasFunction('base')` 控制模块入口。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -42,6 +45,7 @@ public class DashboardController {
|
|||
* 统计数据:项目总数、楼栋总数、房间总数、业主总数、租户总数
|
||||
*/
|
||||
@GetMapping("/stats")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<Map<String, Object>> stats() {
|
||||
Long projectId = UserContext.getProjectId();
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import java.util.Map;
|
|||
* 自定义数据范围规则控制器(data_scope=5)
|
||||
* <p>
|
||||
* 仅 A4 系统管理员可访问,路径前缀: /api/v1/base/data-scope-rules
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('system:data-scope:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -32,7 +35,7 @@ public class DataScopeRuleController {
|
|||
* 查询全部规则
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('system:data-scope:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<DataScopeRule>> list() {
|
||||
return Result.success(dataScopeRuleService.list());
|
||||
}
|
||||
|
|
@ -41,7 +44,7 @@ public class DataScopeRuleController {
|
|||
* 创建规则
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('system:data-scope:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建数据范围规则")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody DataScopeRuleSaveRequest request) {
|
||||
Long id = dataScopeRuleService.create(request);
|
||||
|
|
@ -54,7 +57,7 @@ public class DataScopeRuleController {
|
|||
* 更新规则
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:data-scope:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新数据范围规则")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody DataScopeRuleSaveRequest request) {
|
||||
dataScopeRuleService.update(id, request);
|
||||
|
|
@ -65,7 +68,7 @@ public class DataScopeRuleController {
|
|||
* 删除规则
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('system:data-scope:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除数据范围规则")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
dataScopeRuleService.delete(id);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class DeviceCategoryController {
|
|||
* 设备分类树
|
||||
*/
|
||||
@GetMapping("/tree")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<DeviceCategoryDTO>> tree() {
|
||||
return Result.success(deviceCategoryService.tree(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ public class DeviceCategoryController {
|
|||
* 创建设备分类
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建设备分类")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody DeviceCategorySaveRequest request) {
|
||||
Long id = deviceCategoryService.create(request);
|
||||
|
|
@ -54,7 +54,7 @@ public class DeviceCategoryController {
|
|||
* 更新设备分类
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新设备分类")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody DeviceCategorySaveRequest request) {
|
||||
deviceCategoryService.update(id, request);
|
||||
|
|
@ -65,7 +65,7 @@ public class DeviceCategoryController {
|
|||
* 删除设备分类
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除设备分类")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
deviceCategoryService.delete(id);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class DeviceController {
|
|||
* 设备列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<DeviceDTO>> list(DeviceDTO query) {
|
||||
return Result.success(deviceService.page(query));
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ public class DeviceController {
|
|||
* 创建设备
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建设备")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody DeviceSaveRequest request) {
|
||||
Long id = deviceService.create(request);
|
||||
|
|
@ -54,7 +54,7 @@ public class DeviceController {
|
|||
* 设备详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<DeviceDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(deviceService.getById(id));
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ public class DeviceController {
|
|||
* 更新设备
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新设备")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody DeviceSaveRequest request) {
|
||||
deviceService.update(id, request);
|
||||
|
|
@ -74,7 +74,7 @@ public class DeviceController {
|
|||
* 删除设备
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除设备")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
deviceService.delete(id);
|
||||
|
|
@ -85,7 +85,7 @@ public class DeviceController {
|
|||
* 更新设备状态
|
||||
*/
|
||||
@PutMapping("/{id}/status")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新设备状态")
|
||||
public Result<Void> updateStatus(@PathVariable Long id, @Valid @RequestBody DeviceStatusRequest request) {
|
||||
deviceService.updateStatus(id, request.getStatus());
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class DeviceMaintenanceController {
|
|||
* 维保记录列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<DeviceMaintenanceDTO>> list(DeviceMaintenanceDTO query,
|
||||
@RequestParam(required = false) Long deviceId) {
|
||||
return Result.success(maintenanceService.page(query, deviceId));
|
||||
|
|
@ -42,7 +42,7 @@ public class DeviceMaintenanceController {
|
|||
* 创建维保记录
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建设备维保记录")
|
||||
public Result<Map<String, Long>> create(@RequestParam Long deviceId,
|
||||
@Valid @RequestBody DeviceMaintenanceSaveRequest request) {
|
||||
|
|
@ -56,7 +56,7 @@ public class DeviceMaintenanceController {
|
|||
* 维保详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<DeviceMaintenanceDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(maintenanceService.getById(id));
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ public class DeviceMaintenanceController {
|
|||
* 即将到期维保列表
|
||||
*/
|
||||
@GetMapping("/expiring")
|
||||
@PreAuthorize("hasAuthority('base:device:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<DeviceMaintenanceDTO>> expiring(@RequestParam(defaultValue = "30") int days) {
|
||||
return Result.success(maintenanceService.expiring(days));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class EnergyMeterController {
|
|||
* 表计列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:energy:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<EnergyMeter>> list() {
|
||||
return Result.success(energyMeterService.list(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ public class EnergyMeterController {
|
|||
* 表计详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:energy:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<EnergyMeter> getById(@PathVariable Long id) {
|
||||
return Result.success(energyMeterService.getById(id));
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ public class EnergyMeterController {
|
|||
* 创建表计
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:energy:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建能源表计")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody EnergyMeter entity) {
|
||||
Long id = energyMeterService.create(entity);
|
||||
|
|
@ -63,7 +63,7 @@ public class EnergyMeterController {
|
|||
* 更新表计
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:energy:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新能源表计")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody EnergyMeter entity) {
|
||||
energyMeterService.update(id, entity);
|
||||
|
|
@ -74,7 +74,7 @@ public class EnergyMeterController {
|
|||
* 删除表计
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:energy:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除能源表计")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
energyMeterService.delete(id);
|
||||
|
|
@ -85,7 +85,7 @@ public class EnergyMeterController {
|
|||
* 抄表记录(首期仅台账,预留 IoT 接入点 T4)
|
||||
*/
|
||||
@PostMapping("/{id}/reading")
|
||||
@PreAuthorize("hasAuthority('base:energy:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "能源表计抄表")
|
||||
public Result<Void> recordReading(@PathVariable Long id, @RequestParam BigDecimal newReading) {
|
||||
energyMeterService.recordReading(id, newReading);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 企业控制器
|
||||
* 路径前缀: /api/v1/enterprises
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):5 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:enterprise:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class EnterpriseController {
|
|||
* 企业列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<EnterpriseDTO>> list(EnterpriseDTO query) {
|
||||
return Result.success(enterpriseService.page(query));
|
||||
}
|
||||
|
|
@ -40,7 +43,7 @@ public class EnterpriseController {
|
|||
* 创建企业
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建企业", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody EnterpriseSaveRequest request) {
|
||||
Long id = enterpriseService.create(request);
|
||||
|
|
@ -53,7 +56,7 @@ public class EnterpriseController {
|
|||
* 企业详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<EnterpriseDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(enterpriseService.getById(id));
|
||||
}
|
||||
|
|
@ -62,7 +65,7 @@ public class EnterpriseController {
|
|||
* 更新企业
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新企业", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody EnterpriseSaveRequest request) {
|
||||
enterpriseService.update(id, request);
|
||||
|
|
@ -73,7 +76,7 @@ public class EnterpriseController {
|
|||
* 删除企业
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除企业")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
enterpriseService.delete(id);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 企业画像控制器
|
||||
* 路径前缀: /api/v1/base/enterprise-profiles
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):5 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:enterprise:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class EnterpriseProfileController {
|
|||
* 企业画像列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<EnterpriseProfile>> list() {
|
||||
return Result.success(enterpriseProfileService.list(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -40,7 +43,7 @@ public class EnterpriseProfileController {
|
|||
* 企业画像详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<EnterpriseProfile> getById(@PathVariable Long id) {
|
||||
return Result.success(enterpriseProfileService.getById(id));
|
||||
}
|
||||
|
|
@ -49,7 +52,7 @@ public class EnterpriseProfileController {
|
|||
* 创建企业画像
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建企业画像", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody EnterpriseProfile entity) {
|
||||
Long id = enterpriseProfileService.create(entity);
|
||||
|
|
@ -62,7 +65,7 @@ public class EnterpriseProfileController {
|
|||
* 更新企业画像
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新企业画像", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody EnterpriseProfile entity) {
|
||||
enterpriseProfileService.update(id, entity);
|
||||
|
|
@ -73,7 +76,7 @@ public class EnterpriseProfileController {
|
|||
* 删除企业画像
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除企业画像")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
enterpriseProfileService.delete(id);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 企业服务(申请记录)控制器
|
||||
* 路径前缀: /api/v1/base/enterprise-services
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:enterprise:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class EnterpriseServiceController {
|
|||
* 服务申请详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<EnterpriseService> getById(@PathVariable Long id) {
|
||||
return Result.success(enterpriseServiceService.getById(id));
|
||||
}
|
||||
|
|
@ -40,7 +43,7 @@ public class EnterpriseServiceController {
|
|||
* 当前项目下企业服务申请列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<EnterpriseService>> list() {
|
||||
return Result.success(enterpriseServiceService.listByProject(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -49,7 +52,7 @@ public class EnterpriseServiceController {
|
|||
* 提交服务申请
|
||||
*/
|
||||
@PostMapping("/apply")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "提交企业服务申请", recordParams = false)
|
||||
public Result<Map<String, Long>> apply(@Valid @RequestBody EnterpriseService service) {
|
||||
service.setProjectId(UserContext.getProjectId());
|
||||
|
|
@ -63,7 +66,7 @@ public class EnterpriseServiceController {
|
|||
* 处理服务申请(标记为已完成)
|
||||
*/
|
||||
@PutMapping("/{id}/process")
|
||||
@PreAuthorize("hasAuthority('base:enterprise:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "处理企业服务申请", recordParams = false)
|
||||
public Result<Void> process(@PathVariable Long id,
|
||||
@RequestParam String handler,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 楼层控制器
|
||||
* 路径前缀: /api/v1/floors
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:floor:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -32,7 +35,7 @@ public class FloorController {
|
|||
* 楼层列表查询
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:floor:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<FloorDTO>> list(@RequestParam(required = false) Long buildingId) {
|
||||
return Result.success(floorService.list(UserContext.getProjectId(), buildingId));
|
||||
}
|
||||
|
|
@ -41,7 +44,7 @@ public class FloorController {
|
|||
* 创建楼层
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:floor:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建楼层")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody FloorSaveRequest request) {
|
||||
Long id = floorService.create(request);
|
||||
|
|
@ -54,7 +57,7 @@ public class FloorController {
|
|||
* 更新楼层
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:floor:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新楼层")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody FloorSaveRequest request) {
|
||||
floorService.update(id, request);
|
||||
|
|
@ -65,7 +68,7 @@ public class FloorController {
|
|||
* 删除楼层
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:floor:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除楼层")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
floorService.delete(id);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class LeaseContractController {
|
|||
* 合同详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:lease:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<LeaseContract> getById(@PathVariable Long id) {
|
||||
return Result.success(leaseContractService.getById(id));
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ public class LeaseContractController {
|
|||
* 当前项目下合同列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:lease:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<LeaseContract>> list() {
|
||||
return Result.success(leaseContractService.listByProject(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ public class LeaseContractController {
|
|||
* 创建租赁合同
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:lease:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建租赁合同")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody LeaseContract contract) {
|
||||
contract.setProjectId(UserContext.getProjectId());
|
||||
|
|
@ -65,7 +65,7 @@ public class LeaseContractController {
|
|||
* 续约:延长合同结束日期
|
||||
*/
|
||||
@PutMapping("/{id}/renew")
|
||||
@PreAuthorize("hasAuthority('base:lease:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "租赁合同续约")
|
||||
public Result<Void> renew(@PathVariable Long id,
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate newEndDate) {
|
||||
|
|
@ -77,7 +77,7 @@ public class LeaseContractController {
|
|||
* 退租
|
||||
*/
|
||||
@PutMapping("/{id}/terminate")
|
||||
@PreAuthorize("hasAuthority('base:lease:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "租赁合同退租")
|
||||
public Result<Void> terminate(@PathVariable Long id) {
|
||||
leaseContractService.terminate(id);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 生命周期流转控制器
|
||||
* 路径前缀: /api/v1/base/projects/{projectId}/lifecycle
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:lifecycle:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -32,7 +35,7 @@ public class LifecycleController {
|
|||
* 查询当前生命周期阶段(R5)
|
||||
*/
|
||||
@GetMapping("/stage")
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "QUERY", description = "查询生命周期阶段")
|
||||
public Result<LifecycleStageDTO> getStage(@PathVariable Long projectId) {
|
||||
return Result.success(lifecycleService.getStage(projectId));
|
||||
|
|
@ -42,7 +45,7 @@ public class LifecycleController {
|
|||
* 发起阶段流转
|
||||
*/
|
||||
@PostMapping("/transition")
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "发起阶段流转")
|
||||
public Result<Map<String, Long>> transition(@PathVariable Long projectId,
|
||||
@Valid @RequestBody StageTransitionRequest request) {
|
||||
|
|
@ -56,7 +59,7 @@ public class LifecycleController {
|
|||
* 审批通过
|
||||
*/
|
||||
@PostMapping("/approve")
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "审批通过")
|
||||
public Result<ApprovalCompletedEvent> approve(@PathVariable Long projectId,
|
||||
@Valid @RequestBody ApprovalActionRequest request) {
|
||||
|
|
@ -67,7 +70,7 @@ public class LifecycleController {
|
|||
* 审批退回
|
||||
*/
|
||||
@PostMapping("/reject")
|
||||
@PreAuthorize("hasAuthority('base:lifecycle:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "审批退回")
|
||||
public Result<Void> reject(@PathVariable Long projectId,
|
||||
@Valid @RequestBody ApprovalActionRequest request) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class MeetingRoomController {
|
|||
* 会议室详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:meeting:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<MeetingRoom> getById(@PathVariable Long id) {
|
||||
return Result.success(meetingRoomService.getById(id));
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ public class MeetingRoomController {
|
|||
* 当前项目下会议室列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:meeting:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<MeetingRoom>> list() {
|
||||
return Result.success(meetingRoomService.listByProject(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ public class MeetingRoomController {
|
|||
* 创建会议室
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:meeting:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建会议室")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody MeetingRoom room) {
|
||||
room.setProjectId(UserContext.getProjectId());
|
||||
|
|
@ -63,7 +63,7 @@ public class MeetingRoomController {
|
|||
* 预订会议室
|
||||
*/
|
||||
@PutMapping("/{id}/book")
|
||||
@PreAuthorize("hasAuthority('base:meeting:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "预订会议室")
|
||||
public Result<Void> book(@PathVariable Long id) {
|
||||
meetingRoomService.book(id);
|
||||
|
|
@ -74,7 +74,7 @@ public class MeetingRoomController {
|
|||
* 取消预订
|
||||
*/
|
||||
@PutMapping("/{id}/cancel")
|
||||
@PreAuthorize("hasAuthority('base:meeting:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "取消会议室预订")
|
||||
public Result<Void> cancelBooking(@PathVariable Long id) {
|
||||
meetingRoomService.cancelBooking(id);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 业委会控制器
|
||||
* 路径前缀: /api/v1/base/owner-committees
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):5 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:committee:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class OwnerCommitteeController {
|
|||
* 分页查询业委会列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:committee:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<OwnerCommittee>> page(
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
|
|
@ -43,7 +46,7 @@ public class OwnerCommitteeController {
|
|||
* 业委会详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:committee:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<OwnerCommittee> getById(@PathVariable Long id) {
|
||||
return Result.success(ownerCommitteeService.getById(id));
|
||||
}
|
||||
|
|
@ -52,7 +55,7 @@ public class OwnerCommitteeController {
|
|||
* 创建业委会
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:committee:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建业委会", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody OwnerCommittee committee) {
|
||||
Long id = ownerCommitteeService.create(committee);
|
||||
|
|
@ -65,7 +68,7 @@ public class OwnerCommitteeController {
|
|||
* 更新业委会
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:committee:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新业委会", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody OwnerCommittee committee) {
|
||||
ownerCommitteeService.update(id, committee);
|
||||
|
|
@ -76,7 +79,7 @@ public class OwnerCommitteeController {
|
|||
* 删除业委会
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:committee:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除业委会")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
ownerCommitteeService.delete(id);
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class OwnerController {
|
|||
* 业主列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<OwnerDTO>> list(OwnerDTO query) {
|
||||
return Result.success(ownerService.page(query));
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ public class OwnerController {
|
|||
* 创建业主
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建业主", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody OwnerSaveRequest request) {
|
||||
Long id = ownerService.create(request);
|
||||
|
|
@ -59,7 +59,7 @@ public class OwnerController {
|
|||
* 业主详情(含房产信息)
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<OwnerDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(ownerService.getById(id));
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ public class OwnerController {
|
|||
* 更新业主
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新业主", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody OwnerSaveRequest request) {
|
||||
ownerService.update(id, request);
|
||||
|
|
@ -79,7 +79,7 @@ public class OwnerController {
|
|||
* 删除业主
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除业主")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
ownerService.delete(id);
|
||||
|
|
@ -90,7 +90,7 @@ public class OwnerController {
|
|||
* 业主房产列表
|
||||
*/
|
||||
@GetMapping("/{id}/rooms")
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<OwnerRoomDTO>> getOwnerRooms(@PathVariable Long id) {
|
||||
return Result.success(ownerService.getOwnerRooms(id));
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ public class OwnerController {
|
|||
* 批量导入业主(Excel上传)
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "导入业主", recordParams = false)
|
||||
public Result<Map<String, Object>> importOwners(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 业主房间关联控制器
|
||||
* 路径前缀: /api/v1/owner-rooms
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):3 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:owner:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -32,7 +35,7 @@ public class OwnerRoomController {
|
|||
* 业主房间关联列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<OwnerRoomDTO>> list(@RequestParam(required = false) Long ownerId,
|
||||
@RequestParam(required = false) Long roomId) {
|
||||
return Result.success(ownerRoomService.list(UserContext.getProjectId(), ownerId, roomId));
|
||||
|
|
@ -42,7 +45,7 @@ public class OwnerRoomController {
|
|||
* 创建业主房间关联
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建业主房间关联", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody OwnerRoomRelRequest request) {
|
||||
Long id = ownerRoomService.create(request);
|
||||
|
|
@ -55,7 +58,7 @@ public class OwnerRoomController {
|
|||
* 删除业主房间关联
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:owner:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除业主房间关联")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
ownerRoomService.delete(id);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 前期介入进度控制器
|
||||
* 路径前缀: /api/v1/base/projects/{projectId}/pre-handover/progress
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:project:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class PreHandoverProgressController {
|
|||
* 创建前期介入进度记录
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建前期介入进度", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@PathVariable Long projectId,
|
||||
@Valid @RequestBody PreHandoverProgressDTO dto) {
|
||||
|
|
@ -45,7 +48,7 @@ public class PreHandoverProgressController {
|
|||
* 查询项目前期介入进度列表(可选 progress_type 筛选)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<PreHandoverProgress>> list(@PathVariable Long projectId,
|
||||
@RequestParam(required = false) String progressType) {
|
||||
return Result.success(preHandoverProgressService.listByProject(projectId, progressType));
|
||||
|
|
@ -55,7 +58,7 @@ public class PreHandoverProgressController {
|
|||
* 更新前期介入进度记录
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新前期介入进度", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long projectId,
|
||||
@PathVariable Long id,
|
||||
|
|
@ -68,7 +71,7 @@ public class PreHandoverProgressController {
|
|||
* 删除前期介入进度记录(逻辑删除)
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除前期介入进度", recordParams = false)
|
||||
public Result<Void> delete(@PathVariable Long projectId,
|
||||
@PathVariable Long id) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ import org.springframework.web.bind.annotation.*;
|
|||
/**
|
||||
* 项目能力包主业态控制器
|
||||
* 路径前缀: /api/v1/base/projects/{projectId}/abilities
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):2 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:project:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -25,7 +28,7 @@ public class ProjectAbilityController {
|
|||
* 阶段3 自动重算主业态:按户数最多业态重设 is_primary。
|
||||
*/
|
||||
@PostMapping("/recompute-primary")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "重算主业态", recordParams = false)
|
||||
public Result<PrimaryRecomputeResultDTO> recomputePrimary(@PathVariable Long projectId) {
|
||||
return Result.success(projectAbilityService.recomputePrimary(projectId));
|
||||
|
|
@ -35,7 +38,7 @@ public class ProjectAbilityController {
|
|||
* 强制覆盖主业态(人工确认场景)。
|
||||
*/
|
||||
@PutMapping("/{abilityId}/primary")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "强制覆盖主业态", recordParams = false)
|
||||
public Result<Void> forceOverridePrimary(@PathVariable Long projectId,
|
||||
@PathVariable Long abilityId) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 项目属性扩展控制器
|
||||
* 路径前缀: /api/v1/base/projects/{id}/attributes
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:project:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class ProjectAttributeController {
|
|||
* 创建项目属性
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建项目属性", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@PathVariable Long projectId,
|
||||
@Valid @RequestBody ProjectAttributeDTO dto) {
|
||||
|
|
@ -45,7 +48,7 @@ public class ProjectAttributeController {
|
|||
* 查询项目属性列表(可选 attr_type 筛选)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<ProjectAttribute>> list(@PathVariable Long projectId,
|
||||
@RequestParam(required = false) String attrType) {
|
||||
return Result.success(projectAttributeService.listByProject(projectId, attrType));
|
||||
|
|
@ -55,7 +58,7 @@ public class ProjectAttributeController {
|
|||
* 更新项目属性
|
||||
*/
|
||||
@PutMapping("/{attrId}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新项目属性", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long projectId,
|
||||
@PathVariable Long attrId,
|
||||
|
|
@ -68,7 +71,7 @@ public class ProjectAttributeController {
|
|||
* 删除项目属性
|
||||
*/
|
||||
@DeleteMapping("/{attrId}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除项目属性", recordParams = false)
|
||||
public Result<Void> delete(@PathVariable Long projectId,
|
||||
@PathVariable Long attrId) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 物业项目控制器
|
||||
* 路径前缀: /api/v1/projects
|
||||
* <p>
|
||||
* PR2-PR4 改造(U4.1):7 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:project:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -35,7 +38,7 @@ public class ProjectController {
|
|||
* 项目列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<ProjectDTO>> list(ProjectDTO query) {
|
||||
return Result.success(projectService.page(query));
|
||||
}
|
||||
|
|
@ -44,7 +47,7 @@ public class ProjectController {
|
|||
* 创建项目
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建项目", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody ProjectSaveRequest request) {
|
||||
Long id = projectService.create(request);
|
||||
|
|
@ -57,7 +60,7 @@ public class ProjectController {
|
|||
* 获取全部项目(下拉选择用)
|
||||
*/
|
||||
@GetMapping("/all")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<ProjectDTO>> listAll() {
|
||||
return Result.success(projectService.listAll());
|
||||
}
|
||||
|
|
@ -66,7 +69,7 @@ public class ProjectController {
|
|||
* 项目详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<ProjectDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(projectService.getById(id));
|
||||
}
|
||||
|
|
@ -75,7 +78,7 @@ public class ProjectController {
|
|||
* 更新项目
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新项目", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody ProjectSaveRequest request) {
|
||||
projectService.update(id, request);
|
||||
|
|
@ -86,7 +89,7 @@ public class ProjectController {
|
|||
* 删除项目
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除项目")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
projectService.delete(id);
|
||||
|
|
@ -98,7 +101,7 @@ public class ProjectController {
|
|||
* 为项目关联业态能力包,支持幂等重复调用
|
||||
*/
|
||||
@PostMapping("/{id}/init")
|
||||
@PreAuthorize("hasAuthority('base:project:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "初始化项目能力包")
|
||||
public Result<Void> initProject(@PathVariable Long id, @Valid @RequestBody ProjectInitRequest request) {
|
||||
projectInitService.initProject(id, request.getPackageCodes());
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class PublicRevenueController {
|
|||
* 分页查询公共收益
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:revenue:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<PublicRevenue>> page(
|
||||
@RequestParam(required = false) String period,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
|
|
@ -43,7 +43,7 @@ public class PublicRevenueController {
|
|||
* 公共收益详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:revenue:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PublicRevenue> getById(@PathVariable Long id) {
|
||||
return Result.success(publicRevenueService.getById(id));
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ public class PublicRevenueController {
|
|||
* 录入公共收益
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:revenue:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "录入公共收益")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody PublicRevenue revenue) {
|
||||
Long id = publicRevenueService.create(revenue);
|
||||
|
|
@ -65,7 +65,7 @@ public class PublicRevenueController {
|
|||
* 更新公共收益
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:revenue:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新公共收益")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody PublicRevenue revenue) {
|
||||
publicRevenueService.update(id, revenue);
|
||||
|
|
@ -78,7 +78,7 @@ public class PublicRevenueController {
|
|||
* @param period 周期,如 2025-06
|
||||
*/
|
||||
@GetMapping("/summary")
|
||||
@PreAuthorize("hasAuthority('base:revenue:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<Map<String, Object>> monthlySummary(@RequestParam String period) {
|
||||
Long total = publicRevenueService.monthlySummary(UserContext.getProjectId(), period);
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
|
|
@ -91,7 +91,7 @@ public class PublicRevenueController {
|
|||
* 删除公共收益
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:revenue:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除公共收益")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
publicRevenueService.delete(id);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class RenovationController {
|
|||
* 分页查询装修记录
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<Renovation>> page(
|
||||
@RequestParam(required = false) Integer status,
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
|
|
@ -43,7 +43,7 @@ public class RenovationController {
|
|||
* 装修记录详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<Renovation> getById(@PathVariable Long id) {
|
||||
return Result.success(renovationService.getById(id));
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ public class RenovationController {
|
|||
* 申请装修
|
||||
*/
|
||||
@PostMapping("/apply")
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "申请装修", recordParams = false)
|
||||
public Result<Map<String, Long>> apply(@Valid @RequestBody Renovation renovation) {
|
||||
Long id = renovationService.applyRenovation(renovation);
|
||||
|
|
@ -70,7 +70,7 @@ public class RenovationController {
|
|||
* @param comment 审批意见
|
||||
*/
|
||||
@PostMapping("/{id}/approve")
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "审批装修申请")
|
||||
public Result<Void> approve(@PathVariable Long id,
|
||||
@RequestParam boolean approved,
|
||||
|
|
@ -84,7 +84,7 @@ public class RenovationController {
|
|||
* 开始施工
|
||||
*/
|
||||
@PostMapping("/{id}/start")
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "开始施工")
|
||||
public Result<Void> startConstruction(@PathVariable Long id) {
|
||||
renovationService.startConstruction(id);
|
||||
|
|
@ -95,7 +95,7 @@ public class RenovationController {
|
|||
* 装修完成
|
||||
*/
|
||||
@PostMapping("/{id}/complete")
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "装修完成")
|
||||
public Result<Void> complete(@PathVariable Long id) {
|
||||
renovationService.complete(id);
|
||||
|
|
@ -106,7 +106,7 @@ public class RenovationController {
|
|||
* 删除装修记录
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:renovation:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除装修记录")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
renovationService.delete(id);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 房间控制器
|
||||
* 路径前缀: /api/v1/base/rooms
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):8 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:room:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -38,7 +41,7 @@ public class RoomController {
|
|||
* 房间列表查询(分页,支持多条件筛选)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<RoomDTO>> list(RoomDTO query) {
|
||||
return Result.success(roomService.page(query));
|
||||
}
|
||||
|
|
@ -47,7 +50,7 @@ public class RoomController {
|
|||
* 创建房间
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建房间")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody RoomSaveRequest request) {
|
||||
Long id = roomService.create(request);
|
||||
|
|
@ -60,7 +63,7 @@ public class RoomController {
|
|||
* 批量创建房间
|
||||
*/
|
||||
@PostMapping("/batch")
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "批量创建房间")
|
||||
public Result<Map<String, Object>> batchCreate(@Valid @RequestBody List<RoomSaveRequest> rooms) {
|
||||
List<Long> ids = roomService.batchCreate(rooms);
|
||||
|
|
@ -74,7 +77,7 @@ public class RoomController {
|
|||
* 房间详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<RoomDTO> getById(@PathVariable Long id) {
|
||||
return Result.success(roomService.getById(id));
|
||||
}
|
||||
|
|
@ -83,7 +86,7 @@ public class RoomController {
|
|||
* 更新房间
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新房间")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody RoomSaveRequest request) {
|
||||
roomService.update(id, request);
|
||||
|
|
@ -94,7 +97,7 @@ public class RoomController {
|
|||
* 删除房间
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除房间")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
roomService.delete(id);
|
||||
|
|
@ -105,7 +108,7 @@ public class RoomController {
|
|||
* 房间树(项目→楼栋→楼层→房间)
|
||||
*/
|
||||
@GetMapping("/tree")
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<BuildingTreeDTO>> tree() {
|
||||
return Result.success(roomService.tree(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -114,7 +117,7 @@ public class RoomController {
|
|||
* 批量导入房间(Excel上传)
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
@PreAuthorize("hasAuthority('base:room:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "导入房间")
|
||||
public Result<Map<String, Object>> importRooms(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class SafetyInspectionController {
|
|||
* 安全检查列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<SafetyInspection>> list() {
|
||||
return Result.success(safetyInspectionService.list(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ public class SafetyInspectionController {
|
|||
* 安全检查详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<SafetyInspection> getById(@PathVariable Long id) {
|
||||
return Result.success(safetyInspectionService.getById(id));
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ public class SafetyInspectionController {
|
|||
* 创建安全检查计划
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建安全检查计划")
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody SafetyInspection entity) {
|
||||
Long id = safetyInspectionService.create(entity);
|
||||
|
|
@ -62,7 +62,7 @@ public class SafetyInspectionController {
|
|||
* 更新安全检查
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新安全检查")
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody SafetyInspection entity) {
|
||||
safetyInspectionService.update(id, entity);
|
||||
|
|
@ -73,7 +73,7 @@ public class SafetyInspectionController {
|
|||
* 删除安全检查
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除安全检查")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
safetyInspectionService.delete(id);
|
||||
|
|
@ -84,7 +84,7 @@ public class SafetyInspectionController {
|
|||
* 执行安全检查(计划中 -> 执行中)
|
||||
*/
|
||||
@PostMapping("/{id}/execute")
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "执行安全检查")
|
||||
public Result<Void> execute(@PathVariable Long id) {
|
||||
safetyInspectionService.execute(id);
|
||||
|
|
@ -95,7 +95,7 @@ public class SafetyInspectionController {
|
|||
* 上报发现(执行中 -> 已上报,闭环)
|
||||
*/
|
||||
@PostMapping("/{id}/report")
|
||||
@PreAuthorize("hasAuthority('base:inspection:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "上报安全检查发现")
|
||||
public Result<Void> reportFindings(@PathVariable Long id,
|
||||
@RequestParam String result,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import java.util.Map;
|
|||
/**
|
||||
* 租户控制器
|
||||
* 路径前缀: /api/v1/base/tenants
|
||||
* <p>
|
||||
* PR2-PR4 改造(U5.1):4 方法统一标注 `hasFunction('base')` 控制模块入口。
|
||||
* Service 层写操作通过 `hasAuthorityAndAdmin('base:tenant:manage')` 控制操作粒度。
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
|
|
@ -31,7 +34,7 @@ public class TenantController {
|
|||
* 租户列表查询(分页)
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:tenant:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<PageResult<TenantDTO>> list(TenantDTO query) {
|
||||
return Result.success(tenantService.page(query));
|
||||
}
|
||||
|
|
@ -40,7 +43,7 @@ public class TenantController {
|
|||
* 创建租户
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:tenant:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建租户", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody TenantSaveRequest request) {
|
||||
Long id = tenantService.create(request);
|
||||
|
|
@ -53,7 +56,7 @@ public class TenantController {
|
|||
* 更新租户
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:tenant:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新租户", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody TenantSaveRequest request) {
|
||||
tenantService.update(id, request);
|
||||
|
|
@ -64,7 +67,7 @@ public class TenantController {
|
|||
* 删除租户
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:tenant:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除租户")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
tenantService.delete(id);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public class WorkshopLeaseController {
|
|||
* 厂房租赁列表
|
||||
*/
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('base:workshop:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<List<WorkshopLease>> list() {
|
||||
return Result.success(workshopLeaseService.list(UserContext.getProjectId()));
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ public class WorkshopLeaseController {
|
|||
* 厂房租赁详情
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:workshop:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
public Result<WorkshopLease> getById(@PathVariable Long id) {
|
||||
return Result.success(workshopLeaseService.getById(id));
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ public class WorkshopLeaseController {
|
|||
* 创建厂房租赁
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('base:workshop:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "创建厂房租赁", recordParams = false)
|
||||
public Result<Map<String, Long>> create(@Valid @RequestBody WorkshopLease entity) {
|
||||
Long id = workshopLeaseService.create(entity);
|
||||
|
|
@ -62,7 +62,7 @@ public class WorkshopLeaseController {
|
|||
* 更新厂房租赁
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:workshop:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "UPDATE", description = "更新厂房租赁", recordParams = false)
|
||||
public Result<Void> update(@PathVariable Long id, @Valid @RequestBody WorkshopLease entity) {
|
||||
workshopLeaseService.update(id, entity);
|
||||
|
|
@ -73,7 +73,7 @@ public class WorkshopLeaseController {
|
|||
* 删除厂房租赁
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('base:workshop:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "DELETE", description = "删除厂房租赁")
|
||||
public Result<Void> delete(@PathVariable Long id) {
|
||||
workshopLeaseService.delete(id);
|
||||
|
|
@ -84,7 +84,7 @@ public class WorkshopLeaseController {
|
|||
* 生成租金账单
|
||||
*/
|
||||
@PostMapping("/{id}/rent-bill")
|
||||
@PreAuthorize("hasAuthority('base:workshop:manage')")
|
||||
@PreAuthorize("hasFunction('base')")
|
||||
@AuditLog(module = "base", type = "CREATE", description = "生成租金账单")
|
||||
public Result<Map<String, String>> generateRentBill(@PathVariable Long id) {
|
||||
String billNo = workshopLeaseService.generateRentBill(id);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.pms.common.security.UserContext;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ public class AbilityPackageServiceImpl implements AbilityPackageService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignPackages(Long projectId, List<String> packageCodes) {
|
||||
if (projectId == null) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.pms.common.security.UserContext;
|
|||
import com.pms.common.util.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ public class AbilityPackageVersionServiceImpl implements AbilityPackageVersionSe
|
|||
private final ProjectAbilityMapper projectAbilityMapper;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public AbilityPackageVersion createDraft(Long packageId) {
|
||||
AbilityPackage pkg = abilityPackageMapper.selectById(packageId);
|
||||
|
|
@ -78,6 +80,7 @@ public class AbilityPackageVersionServiceImpl implements AbilityPackageVersionSe
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void submitForApproval(Long versionId) {
|
||||
AbilityPackageVersion version = loadVersion(versionId);
|
||||
|
|
@ -96,6 +99,7 @@ public class AbilityPackageVersionServiceImpl implements AbilityPackageVersionSe
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void approve(Long versionId, String comment) {
|
||||
AbilityPackageVersion version = loadVersion(versionId);
|
||||
|
|
@ -135,6 +139,7 @@ public class AbilityPackageVersionServiceImpl implements AbilityPackageVersionSe
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void reject(Long versionId, String reason) {
|
||||
AbilityPackageVersion version = loadVersion(versionId);
|
||||
|
|
@ -156,6 +161,7 @@ public class AbilityPackageVersionServiceImpl implements AbilityPackageVersionSe
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long versionId) {
|
||||
AbilityPackageVersion version = loadVersion(versionId);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.pms.common.security.UserContext;
|
|||
import com.pms.common.util.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -68,6 +69,7 @@ public class ApprovalFlowConfigServiceImpl implements ApprovalFlowConfigService
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long saveTemplate(ApprovalFlowTemplateSaveRequest request) {
|
||||
// 查找现有模板(同 stage_transition)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.pms.common.util.JsonUtils;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ public class ApprovalFlowRuntimeServiceImpl implements ApprovalFlowRuntimeServic
|
|||
private static final String ACTION_REJECT_PREV = "REJECT_PREV";
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createInstance(Long projectId, String stageTransition) {
|
||||
ApprovalFlowTemplateDTO template = configService.getTemplateByTransition(stageTransition);
|
||||
|
|
@ -101,6 +103,7 @@ public class ApprovalFlowRuntimeServiceImpl implements ApprovalFlowRuntimeServic
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ApprovalCompletedEvent approve(Long instanceId, Long userId, String comment) {
|
||||
ApprovalFlowInstance instance = loadInstance(instanceId);
|
||||
|
|
@ -166,6 +169,7 @@ public class ApprovalFlowRuntimeServiceImpl implements ApprovalFlowRuntimeServic
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void rejectToInitiator(Long instanceId, Long userId, String comment) {
|
||||
ApprovalFlowInstance instance = loadInstance(instanceId);
|
||||
|
|
@ -182,6 +186,7 @@ public class ApprovalFlowRuntimeServiceImpl implements ApprovalFlowRuntimeServic
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void rejectToPrevious(Long instanceId, Long userId, String comment) {
|
||||
ApprovalFlowInstance instance = loadInstance(instanceId);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import lombok.RequiredArgsConstructor;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ public class ArchiveServiceImpl implements ArchiveService {
|
|||
private LifecycleService lifecycleService;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long archive(Long projectId, AssetHandoverRequest handoverRequest) {
|
||||
// 1. 结算校验
|
||||
|
|
@ -81,6 +83,7 @@ public class ArchiveServiceImpl implements ArchiveService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void markArchived(Long projectId, Long operatorId) {
|
||||
long now = System.currentTimeMillis();
|
||||
|
|
@ -95,6 +98,7 @@ public class ArchiveServiceImpl implements ArchiveService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long recoverArchive(Long projectId, String targetStage) {
|
||||
Long instanceId = lifecycleService.recoverArchive(projectId, targetStage);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -68,6 +69,7 @@ public class BuildingServiceImpl implements BuildingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:building:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(BuildingSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -113,6 +115,7 @@ public class BuildingServiceImpl implements BuildingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:building:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, BuildingSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -146,6 +149,7 @@ public class BuildingServiceImpl implements BuildingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:building:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ public class CamChargeServiceImpl implements CamChargeService {
|
|||
private static final int SCALE = 4;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:finance:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<CamCharge> allocateCamCharge(Long projectId, Long totalAmountFen, String chargePeriod) {
|
||||
if (projectId == null) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -65,6 +66,7 @@ public class CommunityActivityServiceImpl implements CommunityActivityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:activity:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(CommunityActivity activity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -88,6 +90,7 @@ public class CommunityActivityServiceImpl implements CommunityActivityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:activity:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, CommunityActivity activity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -118,6 +121,7 @@ public class CommunityActivityServiceImpl implements CommunityActivityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:activity:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void signUp(Long id) {
|
||||
CommunityActivity existing = communityActivityMapper.selectById(id);
|
||||
|
|
@ -143,6 +147,7 @@ public class CommunityActivityServiceImpl implements CommunityActivityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:activity:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.pms.common.constant.CommonConstants;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ public class ContractRoomServiceImpl implements ContractRoomService {
|
|||
private final ContractRoomMapper contractRoomMapper;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void bindRooms(Long contractId, Long projectId, List<Long> roomIds) {
|
||||
if (roomIds == null) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ public class ContractServiceImpl implements ContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(ContractSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -108,6 +110,7 @@ public class ContractServiceImpl implements ContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, ContractSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -146,6 +149,7 @@ public class ContractServiceImpl implements ContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -163,6 +167,7 @@ public class ContractServiceImpl implements ContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long renew(Long id, ContractRenewRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ public class ContractTypeServiceImpl implements ContractTypeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(ContractTypeSaveRequest request) {
|
||||
// 检查编码唯一性
|
||||
|
|
@ -61,6 +63,7 @@ public class ContractTypeServiceImpl implements ContractTypeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, ContractTypeSaveRequest request) {
|
||||
ContractType existing = contractTypeMapper.selectById(id);
|
||||
|
|
@ -86,6 +89,7 @@ public class ContractTypeServiceImpl implements ContractTypeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:contract:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
ContractType existing = contractTypeMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ public class DataScopeRuleServiceImpl implements DataScopeRuleService {
|
|||
private final DataScopeRuleMapper dataScopeRuleMapper;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:data-scope:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(DataScopeRuleSaveRequest request) {
|
||||
validateScopeType(request);
|
||||
|
|
@ -56,6 +58,7 @@ public class DataScopeRuleServiceImpl implements DataScopeRuleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:data-scope:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, DataScopeRuleSaveRequest request) {
|
||||
DataScopeRule existing = dataScopeRuleMapper.selectById(id);
|
||||
|
|
@ -79,6 +82,7 @@ public class DataScopeRuleServiceImpl implements DataScopeRuleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('system:data-scope:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
DataScopeRule existing = dataScopeRuleMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ public class DeviceCategoryServiceImpl implements DeviceCategoryService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(DeviceCategorySaveRequest request) {
|
||||
DeviceCategory category = new DeviceCategory();
|
||||
|
|
@ -67,6 +69,7 @@ public class DeviceCategoryServiceImpl implements DeviceCategoryService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, DeviceCategorySaveRequest request) {
|
||||
DeviceCategory existing = deviceCategoryMapper.selectById(id);
|
||||
|
|
@ -95,6 +98,7 @@ public class DeviceCategoryServiceImpl implements DeviceCategoryService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
DeviceCategory existing = deviceCategoryMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -85,6 +86,7 @@ public class DeviceMaintenanceServiceImpl implements DeviceMaintenanceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(Long deviceId, DeviceMaintenanceSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ public class DeviceServiceImpl implements DeviceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(DeviceSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -100,6 +102,7 @@ public class DeviceServiceImpl implements DeviceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, DeviceSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -138,6 +141,7 @@ public class DeviceServiceImpl implements DeviceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -150,6 +154,7 @@ public class DeviceServiceImpl implements DeviceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:device:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateStatus(Long id, Integer status) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -49,6 +50,7 @@ public class EnergyMeterServiceImpl implements EnergyMeterService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:energy:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(EnergyMeter entity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -73,6 +75,7 @@ public class EnergyMeterServiceImpl implements EnergyMeterService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:energy:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, EnergyMeter entity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -100,6 +103,7 @@ public class EnergyMeterServiceImpl implements EnergyMeterService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:energy:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -113,6 +117,7 @@ public class EnergyMeterServiceImpl implements EnergyMeterService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:energy:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void recordReading(Long meterId, BigDecimal newReading) {
|
||||
EnergyMeter meter = getById(meterId);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ public class EnterpriseProfileServiceImpl implements EnterpriseProfileService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(EnterpriseProfile entity) {
|
||||
entity.setProjectId(UserContext.getProjectId());
|
||||
|
|
@ -64,6 +66,7 @@ public class EnterpriseProfileServiceImpl implements EnterpriseProfileService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, EnterpriseProfile entity) {
|
||||
EnterpriseProfile existing = enterpriseProfileMapper.selectById(id);
|
||||
|
|
@ -91,6 +94,7 @@ public class EnterpriseProfileServiceImpl implements EnterpriseProfileService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
EnterpriseProfile existing = enterpriseProfileMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.pms.common.security.UserContext;
|
|||
import com.pms.common.util.CryptoUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -98,6 +99,7 @@ public class EnterpriseServiceImpl implements EnterpriseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(EnterpriseSaveRequest request) {
|
||||
Long projectId = UserContext.getProjectId();
|
||||
|
|
@ -140,6 +142,7 @@ public class EnterpriseServiceImpl implements EnterpriseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, EnterpriseSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -182,6 +185,7 @@ public class EnterpriseServiceImpl implements EnterpriseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -50,6 +51,7 @@ public class EnterpriseServiceServiceImpl implements EnterpriseServiceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long apply(EnterpriseService service) {
|
||||
if (service.getStatus() == null) {
|
||||
|
|
@ -67,6 +69,7 @@ public class EnterpriseServiceServiceImpl implements EnterpriseServiceService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:enterprise:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void process(Long id, String handler, String result) {
|
||||
EnterpriseService existing = enterpriseServiceMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ public class FloorServiceImpl implements FloorService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:floor:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(FloorSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -75,6 +77,7 @@ public class FloorServiceImpl implements FloorService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:floor:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, FloorSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -108,6 +111,7 @@ public class FloorServiceImpl implements FloorService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:floor:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ public class LeaseContractServiceImpl implements LeaseContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lease:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(LeaseContract contract) {
|
||||
lifecycleWriteGuard.assertWritable(contract.getProjectId());
|
||||
|
|
@ -88,6 +90,7 @@ public class LeaseContractServiceImpl implements LeaseContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lease:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void renew(Long id, LocalDate newEndDate) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -113,6 +116,7 @@ public class LeaseContractServiceImpl implements LeaseContractService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lease:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void terminate(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import lombok.RequiredArgsConstructor;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -90,6 +91,7 @@ public class LifecycleServiceImpl implements LifecycleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long transition(Long projectId, StageTransitionRequest request) {
|
||||
Project project = projectMapper.selectById(projectId);
|
||||
|
|
@ -120,6 +122,7 @@ public class LifecycleServiceImpl implements LifecycleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ApprovalCompletedEvent approve(Long projectId, Long instanceId, String comment) {
|
||||
assertInstanceBelongsToProject(projectId, instanceId);
|
||||
|
|
@ -138,6 +141,7 @@ public class LifecycleServiceImpl implements LifecycleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void reject(Long projectId, Long instanceId, String action, String comment) {
|
||||
assertInstanceBelongsToProject(projectId, instanceId);
|
||||
|
|
@ -150,6 +154,7 @@ public class LifecycleServiceImpl implements LifecycleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void advanceStage(Long projectId, String targetStage, Long operatorId, String transitionType, Long instanceId) {
|
||||
// SELECT FOR UPDATE 锁定 Project 行,防止 guard 读与阶段变更的 TOCTOU 竞态
|
||||
|
|
@ -217,6 +222,7 @@ public class LifecycleServiceImpl implements LifecycleService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lifecycle:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long recoverArchive(Long projectId, String targetStage) {
|
||||
Project project = projectMapper.selectById(projectId);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.pms.common.security.UserContext;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -76,6 +77,7 @@ public class LookupServiceImpl implements LookupService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lookup:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long addProjectItem(Long projectId, String dictCode, String itemCode, String itemName, Integer sort) {
|
||||
if (projectId == null) {
|
||||
|
|
@ -108,6 +110,7 @@ public class LookupServiceImpl implements LookupService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:lookup:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void disableProjectItem(Long projectId, String dictCode, String itemCode) {
|
||||
if (projectId == null) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ public class MeetingRoomServiceImpl implements MeetingRoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:meeting:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(MeetingRoom room) {
|
||||
lifecycleWriteGuard.assertWritable(room.getProjectId());
|
||||
|
|
@ -78,6 +80,7 @@ public class MeetingRoomServiceImpl implements MeetingRoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:meeting:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void book(Long id) {
|
||||
MeetingRoom existing = meetingRoomMapper.selectById(id);
|
||||
|
|
@ -98,6 +101,7 @@ public class MeetingRoomServiceImpl implements MeetingRoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:meeting:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancelBooking(Long id) {
|
||||
MeetingRoom existing = meetingRoomMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ public class OwnerCommitteeServiceImpl implements OwnerCommitteeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:committee:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(OwnerCommittee committee) {
|
||||
committee.setProjectId(UserContext.getProjectId());
|
||||
|
|
@ -86,6 +88,7 @@ public class OwnerCommitteeServiceImpl implements OwnerCommitteeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:committee:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, OwnerCommittee committee) {
|
||||
OwnerCommittee existing = ownerCommitteeMapper.selectById(id);
|
||||
|
|
@ -114,6 +117,7 @@ public class OwnerCommitteeServiceImpl implements OwnerCommitteeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:committee:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
OwnerCommittee existing = ownerCommitteeMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ public class OwnerRoomServiceImpl implements OwnerRoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:owner:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(OwnerRoomRelRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -88,6 +90,7 @@ public class OwnerRoomServiceImpl implements OwnerRoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:owner:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -98,6 +99,7 @@ public class OwnerServiceImpl implements OwnerService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:owner:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(OwnerSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -162,6 +164,7 @@ public class OwnerServiceImpl implements OwnerService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:owner:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, OwnerSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -212,6 +215,7 @@ public class OwnerServiceImpl implements OwnerService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:owner:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.pms.common.security.UserContext;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ public class PreHandoverProgressServiceImpl implements PreHandoverProgressServic
|
|||
private final PreHandoverProgressMapper preHandoverProgressMapper;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(Long projectId, PreHandoverProgressDTO dto) {
|
||||
validateProgressType(dto.getProgressType());
|
||||
|
|
@ -56,6 +58,7 @@ public class PreHandoverProgressServiceImpl implements PreHandoverProgressServic
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long projectId, Long id, PreHandoverProgressDTO dto) {
|
||||
validateProgressType(dto.getProgressType());
|
||||
|
|
@ -85,6 +88,7 @@ public class PreHandoverProgressServiceImpl implements PreHandoverProgressServic
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long projectId, Long id) {
|
||||
PreHandoverProgress existing = preHandoverProgressMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ public class ProjectAbilityServiceImpl implements ProjectAbilityService {
|
|||
BuildingType.COMPLEX_VALUE, "MIXED_COMMERCIAL_OFFICE");
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void bindWithFallbackPrimary(Long projectId, List<String> abilityCodes) {
|
||||
if (projectId == null) {
|
||||
|
|
@ -88,6 +90,7 @@ public class ProjectAbilityServiceImpl implements ProjectAbilityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PrimaryRecomputeResultDTO recomputePrimary(Long projectId) {
|
||||
PrimaryRecomputeResultDTO result = new PrimaryRecomputeResultDTO();
|
||||
|
|
@ -188,6 +191,7 @@ public class ProjectAbilityServiceImpl implements ProjectAbilityService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void forceOverridePrimary(Long projectId, Long abilityId) {
|
||||
// 1. 校验 ProjectAbility 记录存在且属于该项目
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.pms.common.security.UserContext;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ public class ProjectAttributeServiceImpl implements ProjectAttributeService {
|
|||
private final ProjectAttributeMapper projectAttributeMapper;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(Long projectId, ProjectAttributeDTO dto) {
|
||||
validateAttrType(dto.getAttrType());
|
||||
|
|
@ -54,6 +56,7 @@ public class ProjectAttributeServiceImpl implements ProjectAttributeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long projectId, Long attrId, ProjectAttributeDTO dto) {
|
||||
validateAttrType(dto.getAttrType());
|
||||
|
|
@ -81,6 +84,7 @@ public class ProjectAttributeServiceImpl implements ProjectAttributeService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long projectId, Long attrId) {
|
||||
ProjectAttribute existing = projectAttributeMapper.selectById(attrId);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.pms.base.service.AbilityPackageService;
|
|||
import com.pms.base.service.ProjectInitService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ public class ProjectInitServiceImpl implements ProjectInitService {
|
|||
private final AbilityPackageService abilityPackageService;
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void initProject(Long projectId, List<String> packageCodes) {
|
||||
log.info("开始初始化项目能力包,projectId={}, packageCodes={}", projectId, packageCodes);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.pms.common.security.UserContext;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -59,6 +60,7 @@ public class ProjectServiceImpl implements ProjectService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(ProjectSaveRequest request) {
|
||||
// 检查项目编码唯一性
|
||||
|
|
@ -122,6 +124,7 @@ public class ProjectServiceImpl implements ProjectService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, ProjectSaveRequest request) {
|
||||
Project existing = projectMapper.selectById(id);
|
||||
|
|
@ -173,6 +176,7 @@ public class ProjectServiceImpl implements ProjectService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:project:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
Project existing = projectMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -64,6 +65,7 @@ public class PublicRevenueServiceImpl implements PublicRevenueService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:revenue:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(PublicRevenue revenue) {
|
||||
revenue.setProjectId(UserContext.getProjectId());
|
||||
|
|
@ -92,6 +94,7 @@ public class PublicRevenueServiceImpl implements PublicRevenueService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:revenue:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, PublicRevenue revenue) {
|
||||
PublicRevenue existing = publicRevenueMapper.selectById(id);
|
||||
|
|
@ -134,6 +137,7 @@ public class PublicRevenueServiceImpl implements PublicRevenueService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:revenue:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
PublicRevenue existing = publicRevenueMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
|
@ -78,6 +79,7 @@ public class RenovationServiceImpl implements RenovationService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:renovation:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long applyRenovation(Renovation renovation) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -104,6 +106,7 @@ public class RenovationServiceImpl implements RenovationService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:renovation:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void approve(Long id, boolean approved, String permitType, String comment) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -158,6 +161,7 @@ public class RenovationServiceImpl implements RenovationService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:renovation:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void startConstruction(Long id) {
|
||||
Renovation existing = renovationMapper.selectById(id);
|
||||
|
|
@ -173,6 +177,7 @@ public class RenovationServiceImpl implements RenovationService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:renovation:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void complete(Long id) {
|
||||
Renovation existing = renovationMapper.selectById(id);
|
||||
|
|
@ -188,6 +193,7 @@ public class RenovationServiceImpl implements RenovationService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:renovation:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -69,6 +70,7 @@ public class RoomServiceImpl implements RoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:room:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(RoomSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -118,6 +120,7 @@ public class RoomServiceImpl implements RoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:room:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, RoomSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -148,6 +151,7 @@ public class RoomServiceImpl implements RoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:room:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -160,6 +164,7 @@ public class RoomServiceImpl implements RoomService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:room:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<Long> batchCreate(List<RoomSaveRequest> rooms) {
|
||||
List<Long> ids = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ public class SafetyInspectionServiceImpl implements SafetyInspectionService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:inspection:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(SafetyInspection entity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -71,6 +73,7 @@ public class SafetyInspectionServiceImpl implements SafetyInspectionService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:inspection:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, SafetyInspection entity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -98,6 +101,7 @@ public class SafetyInspectionServiceImpl implements SafetyInspectionService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:inspection:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -111,6 +115,7 @@ public class SafetyInspectionServiceImpl implements SafetyInspectionService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:inspection:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void execute(Long id) {
|
||||
SafetyInspection existing = safetyInspectionMapper.selectById(id);
|
||||
|
|
@ -131,6 +136,7 @@ public class SafetyInspectionServiceImpl implements SafetyInspectionService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:inspection:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void reportFindings(Long id, String result, String finding) {
|
||||
SafetyInspection existing = safetyInspectionMapper.selectById(id);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.pms.common.response.PageResult;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:tenant:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(TenantSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -88,6 +90,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:tenant:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, TenantSaveRequest request) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -130,6 +133,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:tenant:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.pms.common.security.ProjectSecurityChecker;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ public class WorkshopLeaseServiceImpl implements WorkshopLeaseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:workshop:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long create(WorkshopLease entity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -64,6 +66,7 @@ public class WorkshopLeaseServiceImpl implements WorkshopLeaseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:workshop:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void update(Long id, WorkshopLease entity) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -92,6 +95,7 @@ public class WorkshopLeaseServiceImpl implements WorkshopLeaseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:workshop:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long id) {
|
||||
lifecycleWriteGuard.assertWritable(UserContext.getProjectId());
|
||||
|
|
@ -105,6 +109,7 @@ public class WorkshopLeaseServiceImpl implements WorkshopLeaseService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasAuthorityAndAdmin('base:workshop:manage')")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String generateRentBill(Long leaseId) {
|
||||
WorkshopLease lease = getById(leaseId);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue