权限细化:实现接口级权限控制(学生仅访问个人用水 / 水质查询,维修人员仅访问辖区工单 / 设备,管理员全权限);

告警功能完善:告警分级(一般 / 紧急)、告警推送(Web/APP 消息提醒)、告警历史查询接口;
pull/43/head
周竞由 1 month ago
parent 85fe0bd79b
commit 8d19836ea0

@ -93,6 +93,35 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
<build>

@ -0,0 +1,96 @@
// com/campus/water/config/SecurityConfig.java
package com.campus.water.config;
import com.campus.water.security.JwtAuthenticationFilter;
import com.campus.water.security.RoleConstants;
import com.campus.water.security.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
*
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // 启用方法级权限控制
public class SecurityConfig {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
*
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**
*
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
*
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
/**
*
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 关闭CSRF
.csrf(csrf -> csrf.disable())
// 无状态会话
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 权限控制
.authorizeHttpRequests(auth -> auth
// 登录接口放行
.requestMatchers("/api/app/student/login", "/api/app/repair/login", "/api/web/login").permitAll()
// 静态资源放行
.requestMatchers("/static/**", "/templates/**").permitAll()
// 新增告警接口权限控制URL级
.requestMatchers("/api/alerts/**").hasAnyRole("ADMIN", "REPAIRMAN")
// 基础权限控制细粒度在Controller层通过注解控制
.requestMatchers("/api/app/student/**").hasAnyRole("STUDENT", "ADMIN")
.requestMatchers("/api/app/repair/**").hasAnyRole("REPAIRMAN", "ADMIN")
.requestMatchers("/api/web/**").hasRole("ADMIN")
// 其他接口需要认证
.anyRequest().authenticated()
)
// 添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// 设置认证提供者
http.authenticationProvider(authenticationProvider());
return http.build();
}
}

@ -0,0 +1,75 @@
package com.campus.water.controller;
import com.campus.water.entity.Alert;
import com.campus.water.mapper.AlertRepository;
import com.campus.water.util.ResultVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/api/alerts")
@RequiredArgsConstructor
@Tag(name = "告警管理接口") // 替换 @Api
public class AlertController {
private final AlertRepository alertRepository;
@GetMapping("/history")
@PreAuthorize("hasAnyRole('ADMIN', 'REPAIRMAN')")
@Operation(summary = "分页查询告警历史(支持多条件筛选)") // 替换 @ApiOperation若有
public ResultVO<List<Alert>> getAlertHistory(
@Parameter(description = "设备ID可选") @RequestParam(required = false) String deviceId, // 替换 @ApiParam
@Parameter(description = "告警级别可选如error、critical") @RequestParam(required = false) String level,
@Parameter(description = "告警状态可选如pending、resolved") @RequestParam(required = false) String status,
@Parameter(description = "开始时间可选格式yyyy-MM-dd HH:mm:ss") @RequestParam(required = false) LocalDateTime startTime,
@Parameter(description = "结束时间(可选)") @RequestParam(required = false) LocalDateTime endTime,
@Parameter(description = "所属区域(维修人员仅能查询自己的区域)") @RequestParam(required = false) String areaId
) {
List<Alert> alerts;
// 构建查询条件根据参数动态拼接实际可使用Specification更灵活
if (deviceId != null) {
alerts = alertRepository.findByDeviceIdAndTimestampBetween(deviceId, startTime, endTime);
} else if (level != null) {
alerts = alertRepository.findByAlertLevelAndTimestampBetween(
Alert.AlertLevel.valueOf(level), startTime, endTime);
} else if (status != null) {
alerts = alertRepository.findByStatusAndTimestampBetween(
Alert.AlertStatus.valueOf(status), startTime, endTime);
} else if (areaId != null) {
alerts = alertRepository.findByAreaIdAndTimestampBetween(areaId, startTime, endTime);
} else {
alerts = alertRepository.findByTimestampBetween(startTime, endTime);
}
return ResultVO.success(alerts);
}
/**
*
*/
@GetMapping("/pending")
@PreAuthorize("hasAnyRole('ADMIN', 'REPAIRMAN')")
public ResultVO<List<Alert>> getPendingAlerts(
@Parameter(description = "区域ID可选") @RequestParam(required = false) String areaId) { // 替换@ApiParam为@Parameter
List<Alert> pendingAlerts = areaId != null
? alertRepository.findByAreaIdAndStatus(areaId, Alert.AlertStatus.pending)
: alertRepository.findByStatus(Alert.AlertStatus.pending);
// 按优先级排序(紧急在前)
pendingAlerts.sort((a1, a2) ->
Integer.compare(a2.getAlertLevel().getPriority(), a1.getAlertLevel().getPriority()));
return ResultVO.success(pendingAlerts);
}
}

@ -1,25 +1,68 @@
package main.java.com.campus.water.controller;
package com.campus.water.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import com.campus.water.util.ResultVO;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import java.util.HashMap;
import java.util.Map;
import java.time.format.DateTimeParseException;
/**
*
* -
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* /
*/
@ExceptionHandler(IllegalArgumentException.class)
public ResultVO<Void> handleIllegalArgument(IllegalArgumentException e) {
// 针对告警级别/状态参数错误做友好提示
String msg = e.getMessage();
if (msg.contains("AlertLevel") || msg.contains("AlertStatus")) {
msg = "参数错误:告警级别可选值(info/warning/error/critical),告警状态可选值(pending/resolved/closed)";
}
return ResultVO.error(400, "参数错误:" + msg);
}
/**
*
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResultVO<Void> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
String errorMsg;
// 特殊处理时间格式错误(告警查询的时间参数)
if (e.getCause() instanceof DateTimeParseException) {
errorMsg = "时间参数格式错误正确格式yyyy-MM-dd HH:mm:ss示例2025-12-05 10:30:00";
} else {
// 通用类型不匹配提示
errorMsg = String.format(
"参数[%s]类型错误,期望类型:%s实际传入值%s",
e.getName(),
e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "未知",
e.getValue()
);
}
return ResultVO.error(400, errorMsg);
}
/**
* /访
*/
@ExceptionHandler(AccessDeniedException.class)
public ResultVO<Void> handleAccessDenied(AccessDeniedException e) {
return ResultVO.error(403, "权限不足:仅管理员/维修人员可访问告警相关功能");
}
/**
*
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException e) {
Map<String, Object> response = new HashMap<>();
response.put("code", 401); // 权限相关错误用401其他可调整
response.put("msg", e.getMessage());
response.put("data", null);
return new ResponseEntity<>(response, HttpStatus.OK);
public ResultVO<Void> handleRuntimeException(RuntimeException e) {
// 生产环境建议添加日志记录,此处简化
// log.error("服务器运行时异常", e);
return ResultVO.error(500, "服务器内部错误:" + e.getMessage());
}
}

@ -5,6 +5,7 @@ import com.campus.water.mapper.WorkOrderRepository;
import com.campus.water.mapper.RepairmanRepository;
import com.campus.water.mapper.AlertRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
@ -24,7 +25,8 @@ public class WorkOrderController {
@Autowired
private AlertRepository alertRepository;
// 抢单功能
// 抢单功能 - 维修人员和管理员可访问
@PreAuthorize("hasAnyRole('REPAIRMAN', 'ADMIN')")
@Transactional
public boolean grabOrder(String orderId, String repairmanId) {
Optional<WorkOrder> orderOpt = workOrderRepository.findById(orderId);
@ -48,7 +50,8 @@ public class WorkOrderController {
return false;
}
// 拒单功能
// 拒单功能 - 维修人员和管理员可访问
@PreAuthorize("hasAnyRole('REPAIRMAN', 'ADMIN')")
@Transactional
public boolean rejectOrder(String orderId, String repairmanId, String reason) {
Optional<WorkOrder> orderOpt = workOrderRepository.findById(orderId);
@ -75,7 +78,8 @@ public class WorkOrderController {
return false;
}
// 提交维修结果
// 提交维修结果 - 维修人员和管理员可访问
@PreAuthorize("hasAnyRole('REPAIRMAN', 'ADMIN')")
@Transactional
public boolean submitRepairResult(String orderId, String repairmanId,
String dealNote, String imgUrl) {
@ -106,12 +110,14 @@ public class WorkOrderController {
return false;
}
// 获取可抢工单列表
// 获取可抢工单列表 - 维修人员和管理员可访问
@PreAuthorize("hasAnyRole('REPAIRMAN', 'ADMIN')")
public List<WorkOrder> getAvailableOrders(String areaId) {
return workOrderRepository.findByAreaIdAndStatus(areaId, WorkOrder.OrderStatus.pending);
}
// 获取维修工自己的工单
// 获取维修工自己的工单 - 维修人员和管理员可访问
@PreAuthorize("hasAnyRole('REPAIRMAN', 'ADMIN')")
public List<WorkOrder> getMyOrders(String repairmanId) {
return workOrderRepository.findByAssignedRepairmanId(repairmanId);
}

@ -54,7 +54,28 @@ public class Alert {
private LocalDateTime updatedTime = LocalDateTime.now();
public enum AlertLevel {
info, warning, error, critical
info("一般", 1), // 信息级(如状态通知,无需处理)
warning("一般", 2), // 警告级(需关注,非紧急)
error("紧急", 3), // 错误级(需立即处理)
critical("紧急", 4); // 严重级(影响服务,最高优先级)
private final String levelName; // 分级名称(一般/紧急)
private final int priority; // 处理优先级1-4升序
AlertLevel(String levelName, int priority) {
this.levelName = levelName;
this.priority = priority;
}
// 获取分级名称(用于前端展示)
public String getLevelName() {
return levelName;
}
// 获取优先级(用于推送排序)
public int getPriority() {
return priority;
}
}
public enum AlertStatus {

@ -8,24 +8,40 @@ import java.util.List;
@Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
// 根据设备ID和时间范围查询告警
List<Alert> findByDeviceIdAndTimestampBetween(String deviceId, LocalDateTime startTime, LocalDateTime endTime);
// 根据告警级别和时间范围查询告警
List<Alert> findByAlertLevelAndTimestampBetween(Alert.AlertLevel level, LocalDateTime startTime, LocalDateTime endTime);
// 根据告警状态和时间范围查询告警
List<Alert> findByStatusAndTimestampBetween(Alert.AlertStatus status, LocalDateTime startTime, LocalDateTime endTime);
// 根据区域ID和时间范围查询告警
List<Alert> findByAreaIdAndTimestampBetween(String areaId, LocalDateTime startTime, LocalDateTime endTime);
// 根据时间范围查询告警
List<Alert> findByTimestampBetween(LocalDateTime startTime, LocalDateTime endTime);
// 根据区域ID和告警状态查询告警
List<Alert> findByAreaIdAndStatus(String areaId, Alert.AlertStatus status);
// 根据告警状态查询告警
List<Alert> findByStatus(Alert.AlertStatus status);
// 保留原有的其他查询方法(如果有)
// 根据设备ID查询告警
List<Alert> findByDeviceId(String deviceId);
// 根据告警类型查询
List<Alert> findByAlertType(String alertType);
// 根据告警状态查询
List<Alert> findByStatus(Alert.AlertStatus status);
// 根据告警级别查询
List<Alert> findByAlertLevel(Alert.AlertLevel alertLevel);
// 根据区域ID查询告警
List<Alert> findByAreaId(String areaId);
// 根据时间范围查询告警记录
List<Alert> findByTimestampBetween(LocalDateTime start, LocalDateTime end);
// 按状态和级别查询告警
List<Alert> findByStatusAndAlertLevel(Alert.AlertStatus status, Alert.AlertLevel level);
@ -35,7 +51,7 @@ public interface AlertRepository extends JpaRepository<Alert, Long> {
// 根据处理人查询告警
List<Alert> findByResolvedBy(String resolvedBy);
// 新增:检查重复未处理告警
// 检查重复未处理告警
List<Alert> findByDeviceIdAndAlertTypeAndStatusAndTimestampAfter(
String deviceId,
String alertType,

@ -0,0 +1,55 @@
// com/campus/water/security/JwtAuthenticationFilter.java
package com.campus.water.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
/**
* JWT
*/
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
// 提取JWT令牌
String jwt = jwtTokenProvider.getJwtFromRequest(request);
if (jwt != null && jwtTokenProvider.validateJwtToken(jwt)) {
// 获取用户名并加载用户信息
String username = jwtTokenProvider.getUsernameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 设置认证信息
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("认证失败: ", e);
}
filterChain.doFilter(request, response);
}
}

@ -0,0 +1,90 @@
// com/campus/water/security/JwtTokenProvider.java
package com.campus.water.security;
import io.jsonwebtoken.*;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Date;
/**
* JWT
*/
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private long jwtExpirationMs;
/**
* JWT
*/
public String generateToken(Authentication authentication) {
UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();
return Jwts.builder()
.setSubject(userPrincipal.getUsername())
.claim("roles", userPrincipal.getAuthorities().toString())
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
/**
*
*/
public String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("roles", role)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
/**
*
*/
public String getUsernameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
/**
*
*/
public String getRoleFromJwtToken(String token) {
Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
return claims.get("roles", String.class);
}
/**
* JWT
*/
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) {
return false;
}
}
/**
* JWT
*/
public String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

@ -0,0 +1,16 @@
// com/campus/water/security/RoleConstants.java
package com.campus.water.security;
/**
*
*/
public class RoleConstants {
/** 学生角色 */
public static final String ROLE_STUDENT = "ROLE_STUDENT";
/** 维修人员角色 */
public static final String ROLE_REPAIRMAN = "ROLE_REPAIRMAN";
/** 管理员角色 */
public static final String ROLE_ADMIN = "ROLE_ADMIN";
private RoleConstants() {}
}

@ -0,0 +1,79 @@
// com/campus/water/security/UserDetailsServiceImpl.java
package com.campus.water.security;
import com.campus.water.entity.po.AdminPO;
import com.campus.water.entity.po.RepairerAuthPO;
import com.campus.water.entity.po.UserPO;
import com.campus.water.mapper.AdminRepository;
import com.campus.water.mapper.RepairerAuthRepository;
import com.campus.water.mapper.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
/**
*
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository; // 学生用户仓库
@Autowired
private AdminRepository adminRepository; // 管理员仓库
@Autowired
private RepairerAuthRepository repairerAuthRepository; // 维修人员仓库
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 尝试查询学生用户
UserPO student = userRepository.findByUsername(username).orElse(null);
if (student != null) {
return createUserDetails(
student.getUsername(),
student.getPassword(),
RoleConstants.ROLE_STUDENT
);
}
// 2. 尝试查询管理员用户
AdminPO admin = adminRepository.findByUsername(username).orElse(null);
if (admin != null) {
return createUserDetails(
admin.getUsername(),
admin.getPassword(),
RoleConstants.ROLE_ADMIN
);
}
// 3. 尝试查询维修人员用户
RepairerAuthPO repairer = repairerAuthRepository.findByUsername(username).orElse(null);
if (repairer != null) {
return createUserDetails(
repairer.getUsername(),
repairer.getPassword(),
RoleConstants.ROLE_REPAIRMAN
);
}
// 所有类型用户都不存在
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 构建UserDetails对象的工具方法
private UserDetails createUserDetails(String username, String password, String role) {
return new User(
username,
password,
Collections.singletonList(new SimpleGrantedAuthority(role))
);
}
}

@ -0,0 +1,77 @@
package com.campus.water.service;
import com.campus.water.entity.Alert;
import com.campus.water.entity.MessagePush;
import com.campus.water.entity.Repairman;
import com.campus.water.mapper.MessagePushRepository;
import com.campus.water.mapper.RepairmanRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
@Slf4j
public class AlertPushService {
private final MessagePushRepository messagePushRepository;
private final RepairmanRepository repairmanRepository; // 维修人员数据访问
/**
*
* @param alert
*/
@Transactional
public void pushAlertMessage(Alert alert) {
// 1. 确定推送目标(紧急告警推送给管理员+区域维修人员;一般告警仅推送给区域维修人员)
String alertType = alert.getAlertType();
String areaId = alert.getAreaId();
boolean isEmergency = alert.getAlertLevel().getPriority() >= 3; // 紧急级别error/critical
// 2. 构建消息内容
MessagePush message = new MessagePush();
message.setTitle(String.format("[%s告警] %s",
alert.getAlertLevel().getLevelName(), alertType));
message.setContent(alert.getAlertMessage());
message.setMessageType("ALERT");
message.setRelatedId(alert.getAlertId().toString()); // 关联告警ID
message.setPushTime(LocalDateTime.now());
// 3. 推送区域维修人员(所有级别都推送)
List<Repairman> areaRepairmen = repairmanRepository.findByAreaId(areaId);
for (Repairman repairman : areaRepairmen) {
MessagePush repairmanMsg = copyMessage(message);
repairmanMsg.setRepairmanId(repairman.getRepairmanId());
repairmanMsg.setUserId(repairman.getRepairmanId());
repairmanMsg.setUserType("REPAIRMAN");
messagePushRepository.save(repairmanMsg);
}
log.info("告警推送区域维修人员完成 | 告警ID{} | 区域:{} | 人数:{}",
alert.getAlertId(), areaId, areaRepairmen.size());
// 4. 紧急告警额外推送管理员假设管理员userType为"ADMIN",可扩展查询逻辑)
if (isEmergency) {
MessagePush adminMsg = copyMessage(message);
adminMsg.setAdminId("ADMIN001"); // 实际应从管理员表查询
adminMsg.setUserId("ADMIN001");
adminMsg.setUserType("ADMIN");
messagePushRepository.save(adminMsg);
log.info("紧急告警推送管理员完成 | 告警ID{}", alert.getAlertId());
}
}
// 复制消息基础信息(避免重复代码)
private MessagePush copyMessage(MessagePush source) {
MessagePush target = new MessagePush();
target.setTitle(source.getTitle());
target.setContent(source.getContent());
target.setMessageType(source.getMessageType());
target.setRelatedId(source.getRelatedId());
target.setPushTime(source.getPushTime());
return target;
}
}

@ -155,6 +155,7 @@ public class AlertTriggerService {
}
}
private final AlertPushService alertPushService;
/**
* repair/maintenance
* @param orderType repair:maintenance:inspection:<>
@ -175,6 +176,7 @@ public class AlertTriggerService {
alert.setStatus(Alert.AlertStatus.pending);
alert.setTimestamp(LocalDateTime.now());
alertRepository.save(alert);
alertPushService.pushAlertMessage(alert);
log.info("创建告警记录成功 | 告警ID{} | 设备ID{} | 告警类型:{}",
alert.getAlertId(), deviceId, alertType);

@ -3,6 +3,7 @@ package com.campus.water.service.app;
import com.campus.water.controller.WaterUsageController;
import com.campus.water.util.ResultVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.util.Map;
@ -13,7 +14,8 @@ public class StudentAppService {
@Autowired
private WaterUsageController waterUsageController;
// 扫码获取终端信息
// 扫码获取终端信息 - 学生和管理员可访问
@PreAuthorize("hasAnyRole('STUDENT', 'ADMIN')")
public ResultVO<Map<String, Object>> getTerminalInfo(String terminalId) {
try {
Map<String, Object> result = waterUsageController.getTerminalInfo(terminalId);
@ -23,7 +25,8 @@ public class StudentAppService {
}
}
// 扫码用水
// 扫码用水 - 学生和管理员可访问
@PreAuthorize("hasAnyRole('STUDENT', 'ADMIN')")
public ResultVO<Map<String, Object>> scanToDrink(Map<String, Object> request) {
try {
String terminalId = (String) request.get("terminalId");
@ -37,7 +40,8 @@ public class StudentAppService {
}
}
// 查询水质信息
// 查询水质信息 - 学生和管理员可访问
@PreAuthorize("hasAnyRole('STUDENT', 'ADMIN')")
public ResultVO<Map<String, Object>> getWaterQuality(String deviceId) {
try {
Map<String, Object> result = waterUsageController.getWaterQualityInfo(deviceId);

Loading…
Cancel
Save