feat(ops): 安保工单模块完整实现 #1

Merged
lzh merged 17 commits from feat/security-work-order into master 2026-03-15 16:44:14 +08:00
11 changed files with 490 additions and 1 deletions
Showing only changes of commit 2e4432e51b - Show all commits

View File

@@ -0,0 +1,88 @@
package com.viewsh.module.ops.controller.admin.security;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.ops.controller.admin.security.vo.OpsAreaSecurityUserBindReqVO;
import com.viewsh.module.ops.controller.admin.security.vo.OpsAreaSecurityUserRespVO;
import com.viewsh.module.ops.controller.admin.security.vo.OpsAreaSecurityUserUpdateReqVO;
import com.viewsh.module.ops.security.dal.dataobject.area.OpsAreaSecurityUserDO;
import com.viewsh.module.ops.security.service.area.OpsAreaSecurityUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* 安保区域人员绑定管理
*
* @author lzh
*/
@Tag(name = "安保区域人员绑定")
@RestController
@RequestMapping("/ops/security/area-user")
@Validated
public class SecurityAreaUserController {
@Resource
private OpsAreaSecurityUserService areaSecurityUserService;
@GetMapping("/list")
@Operation(summary = "查询区域绑定的安保人员")
@PreAuthorize("@ss.hasPermission('ops:security-area-user:query')")
public CommonResult<List<OpsAreaSecurityUserRespVO>> list(
@Parameter(description = "区域ID", required = true) @RequestParam("areaId") Long areaId) {
List<OpsAreaSecurityUserDO> list = areaSecurityUserService.listByAreaId(areaId);
List<OpsAreaSecurityUserRespVO> result = list.stream()
.map(this::convertToRespVO)
.toList();
return success(result);
}
@PostMapping("/bind")
@Operation(summary = "绑定安保人员到区域")
@PreAuthorize("@ss.hasPermission('ops:security-area-user:create')")
public CommonResult<Long> bind(@Valid @RequestBody OpsAreaSecurityUserBindReqVO reqVO) {
Long id = areaSecurityUserService.bindUser(
reqVO.getAreaId(), reqVO.getUserId(), reqVO.getUserName(),
reqVO.getTeamId(), reqVO.getSort());
return success(id);
}
@PutMapping("/update")
@Operation(summary = "更新绑定信息")
@PreAuthorize("@ss.hasPermission('ops:security-area-user:update')")
public CommonResult<Boolean> update(@Valid @RequestBody OpsAreaSecurityUserUpdateReqVO reqVO) {
areaSecurityUserService.updateBinding(reqVO.getId(), reqVO.getEnabled(), reqVO.getSort(), reqVO.getTeamId());
return success(true);
}
@DeleteMapping("/unbind")
@Operation(summary = "解除绑定")
@PreAuthorize("@ss.hasPermission('ops:security-area-user:delete')")
public CommonResult<Boolean> unbind(
@Parameter(description = "绑定记录ID", required = true) @RequestParam("id") Long id) {
areaSecurityUserService.unbindUser(id);
return success(true);
}
private OpsAreaSecurityUserRespVO convertToRespVO(OpsAreaSecurityUserDO entity) {
OpsAreaSecurityUserRespVO vo = new OpsAreaSecurityUserRespVO();
vo.setId(entity.getId());
vo.setAreaId(entity.getAreaId());
vo.setUserId(entity.getUserId());
vo.setUserName(entity.getUserName());
vo.setTeamId(entity.getTeamId());
vo.setEnabled(entity.getEnabled());
vo.setSort(entity.getSort());
vo.setCreateTime(entity.getCreateTime());
return vo;
}
}

View File

@@ -0,0 +1,93 @@
package com.viewsh.module.ops.controller.admin.security;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.framework.security.core.util.SecurityFrameworkUtils;
import com.viewsh.module.ops.controller.admin.security.vo.SecurityOrderAutoCompleteReqVO;
import com.viewsh.module.ops.controller.admin.security.vo.SecurityOrderCompleteReqVO;
import com.viewsh.module.ops.controller.admin.security.vo.SecurityOrderConfirmReqVO;
import com.viewsh.module.ops.controller.admin.security.vo.SecurityOrderCreateReqVO;
import com.viewsh.module.ops.security.service.securityorder.SecurityOrderCompleteReqDTO;
import com.viewsh.module.ops.security.service.securityorder.SecurityOrderCreateReqDTO;
import com.viewsh.module.ops.security.service.securityorder.SecurityOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* 安保工单管理Admin
* <p>
* 后台管理端安保工单接口:
* - 创建安保工单
* - 确认工单
* - 自动完单
* - 人工提交结果完单
*
* @author lzh
*/
@Tag(name = "安保工单")
@RestController
@RequestMapping("/ops/security/order")
@Validated
public class SecurityOrderController {
@Resource
private SecurityOrderService securityOrderService;
@PostMapping("/create")
@Operation(summary = "创建安保工单", description = "管理端创建安保工单,传入告警信息和区域,系统自动分配安保人员")
@PreAuthorize("@ss.hasPermission('ops:security-order:create')")
public CommonResult<Long> createOrder(@Valid @RequestBody SecurityOrderCreateReqVO reqVO) {
SecurityOrderCreateReqDTO dto = SecurityOrderCreateReqDTO.builder()
.title(reqVO.getTitle())
.description(reqVO.getDescription())
.priority(reqVO.getPriority())
.areaId(reqVO.getAreaId())
.location(reqVO.getLocation())
.alarmId(reqVO.getAlarmId())
.alarmType(reqVO.getAlarmType())
.cameraId(reqVO.getCameraId())
.roiId(reqVO.getRoiId())
.imageUrl(reqVO.getImageUrl())
.sourceType(reqVO.getSourceType())
.build();
Long orderId = securityOrderService.createSecurityOrder(dto);
return success(orderId);
}
@PostMapping("/confirm")
@Operation(summary = "确认工单", description = "安保人员确认接单")
@PreAuthorize("@ss.hasPermission('ops:security-order:confirm')")
public CommonResult<Boolean> confirmOrder(@Valid @RequestBody SecurityOrderConfirmReqVO reqVO) {
securityOrderService.confirmOrder(reqVO.getOrderId(), reqVO.getUserId());
return success(true);
}
@PostMapping("/auto-complete")
@Operation(summary = "自动完单", description = "由外部系统调用,无需提交处理结果")
@PreAuthorize("@ss.hasPermission('ops:security-order:complete')")
public CommonResult<Boolean> autoCompleteOrder(@Valid @RequestBody SecurityOrderAutoCompleteReqVO reqVO) {
securityOrderService.autoCompleteOrder(reqVO.getOrderId(), reqVO.getRemark());
return success(true);
}
@PostMapping("/manual-complete")
@Operation(summary = "人工完单", description = "安保人员提交处理结果result + 图片)完成工单")
@PreAuthorize("@ss.hasPermission('ops:security-order:complete')")
public CommonResult<Boolean> manualCompleteOrder(@Valid @RequestBody SecurityOrderCompleteReqVO reqVO) {
SecurityOrderCompleteReqDTO dto = SecurityOrderCompleteReqDTO.builder()
.orderId(reqVO.getOrderId())
.result(reqVO.getResult())
.resultImgUrls(reqVO.getResultImgUrls())
.operatorId(SecurityFrameworkUtils.getLoginUserId())
.build();
securityOrderService.manualCompleteOrder(dto);
return success(true);
}
}

View File

@@ -0,0 +1,33 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 区域安保人员绑定请求 VO
*
* @author lzh
*/
@Schema(description = "区域安保人员绑定请求")
@Data
public class OpsAreaSecurityUserBindReqVO {
@Schema(description = "区域ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "区域ID不能为空")
private Long areaId;
@Schema(description = "安保人员用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2001")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(description = "安保人员姓名", example = "张三")
private String userName;
@Schema(description = "班组ID", example = "10")
private Long teamId;
@Schema(description = "排序值", example = "0")
private Integer sort;
}

View File

@@ -0,0 +1,41 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 区域安保人员绑定响应 VO
*
* @author lzh
*/
@Schema(description = "区域安保人员绑定响应")
@Data
public class OpsAreaSecurityUserRespVO {
@Schema(description = "绑定记录ID", example = "1")
private Long id;
@Schema(description = "区域ID", example = "100")
private Long areaId;
@Schema(description = "安保人员用户ID", example = "2001")
private Long userId;
@Schema(description = "安保人员姓名", example = "张三")
private String userName;
@Schema(description = "班组ID", example = "10")
private Long teamId;
@Schema(description = "是否启用", example = "true")
private Boolean enabled;
@Schema(description = "排序值", example = "0")
private Integer sort;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,29 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 区域安保人员绑定更新请求 VO
*
* @author lzh
*/
@Schema(description = "区域安保人员绑定更新请求")
@Data
public class OpsAreaSecurityUserUpdateReqVO {
@Schema(description = "绑定记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "绑定记录ID不能为空")
private Long id;
@Schema(description = "是否启用", example = "true")
private Boolean enabled;
@Schema(description = "排序值", example = "0")
private Integer sort;
@Schema(description = "班组ID", example = "10")
private Long teamId;
}

View File

@@ -0,0 +1,23 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 安保工单自动完单请求 VO
*
* @author lzh
*/
@Schema(description = "安保工单自动完单请求")
@Data
public class SecurityOrderAutoCompleteReqVO {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
@NotNull(message = "工单ID不能为空")
private Long orderId;
@Schema(description = "备注", example = "告警自动解除")
private String remark;
}

View File

@@ -0,0 +1,30 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 安保工单人工完单请求 VO
*
* @author lzh
*/
@Schema(description = "安保工单人工完单请求")
@Data
public class SecurityOrderCompleteReqVO {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
@NotNull(message = "工单ID不能为空")
private Long orderId;
@Schema(description = "处理结果描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "已到现场排查,系误报")
@NotBlank(message = "处理结果不能为空")
private String result;
@Schema(description = "处理结果图片URL列表", example = "[\"https://oss.example.com/result1.jpg\"]")
private List<String> resultImgUrls;
}

View File

@@ -0,0 +1,24 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 安保工单确认请求 VO
*
* @author lzh
*/
@Schema(description = "安保工单确认请求")
@Data
public class SecurityOrderConfirmReqVO {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
@NotNull(message = "工单ID不能为空")
private Long orderId;
@Schema(description = "安保人员user_id", requiredMode = Schema.RequiredMode.REQUIRED, example = "2001")
@NotNull(message = "用户ID不能为空")
private Long userId;
}

View File

@@ -0,0 +1,54 @@
package com.viewsh.module.ops.controller.admin.security.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 安保工单创建请求 VO
*
* @author lzh
*/
@Schema(description = "安保工单创建请求")
@Data
public class SecurityOrderCreateReqVO {
@Schema(description = "工单标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "A栋3层入侵告警")
@NotBlank(message = "工单标题不能为空")
private String title;
@Schema(description = "工单描述", example = "摄像头检测到异常人员入侵")
private String description;
@Schema(description = "优先级0=P0紧急 1=P1重要 2=P2普通", example = "1")
private Integer priority;
@Schema(description = "区域ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "区域ID不能为空")
private Long areaId;
@Schema(description = "具体位置描述", example = "A栋3层东侧走廊")
private String location;
// ==================== 告警来源 ====================
@Schema(description = "关联告警ID", example = "ALM20260211001")
private String alarmId;
@Schema(description = "告警类型: intrusion/leave_post/fire/fence", example = "intrusion")
private String alarmType;
@Schema(description = "摄像头ID", example = "CAM_001")
private String cameraId;
@Schema(description = "ROI区域ID", example = "ROI_001")
private String roiId;
@Schema(description = "告警截图URL", example = "https://oss.example.com/alarm/snapshot.jpg")
private String imageUrl;
@Schema(description = "来源类型ALARM=告警触发/MANUAL=手动创建)", example = "ALARM")
private String sourceType;
}

View File

@@ -0,0 +1,72 @@
package com.viewsh.module.ops.controller.open.security;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.framework.signature.core.annotation.ApiSignature;
import com.viewsh.module.ops.controller.admin.security.vo.SecurityOrderAutoCompleteReqVO;
import com.viewsh.module.ops.controller.admin.security.vo.SecurityOrderCreateReqVO;
import com.viewsh.module.ops.security.service.securityorder.SecurityOrderCreateReqDTO;
import com.viewsh.module.ops.security.service.securityorder.SecurityOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.viewsh.framework.common.pojo.CommonResult.success;
/**
* 安保工单 - 开放接口
* <p>
* 提供给外部告警系统调用,通过 {@link ApiSignature} 签名验证保护,
* 不走用户登录鉴权Token
* <p>
* 实际路径前缀为 /open-api由框架自动添加
*
* @author lzh
*/
@Tag(name = "安保工单 - 开放接口")
@RestController
@RequestMapping("/ops/security/order")
@Validated
public class SecurityOrderOpenController {
@Resource
private SecurityOrderService securityOrderService;
@PostMapping("/create")
@Operation(summary = "创建安保工单", description = "由外部告警系统调用,传入告警信息和区域,系统自动分配安保人员")
@ApiSignature
@PermitAll
public CommonResult<Long> createOrder(@Valid @RequestBody SecurityOrderCreateReqVO reqVO) {
SecurityOrderCreateReqDTO dto = SecurityOrderCreateReqDTO.builder()
.title(reqVO.getTitle())
.description(reqVO.getDescription())
.priority(reqVO.getPriority())
.areaId(reqVO.getAreaId())
.location(reqVO.getLocation())
.alarmId(reqVO.getAlarmId())
.alarmType(reqVO.getAlarmType())
.cameraId(reqVO.getCameraId())
.roiId(reqVO.getRoiId())
.imageUrl(reqVO.getImageUrl())
.sourceType(reqVO.getSourceType())
.build();
Long orderId = securityOrderService.createSecurityOrder(dto);
return success(orderId);
}
@PostMapping("/auto-complete")
@Operation(summary = "自动完单", description = "由外部告警系统调用,无需提交处理结果")
@ApiSignature
@PermitAll
public CommonResult<Boolean> autoCompleteOrder(@Valid @RequestBody SecurityOrderAutoCompleteReqVO req) {
securityOrderService.autoCompleteOrder(req.getOrderId(), req.getRemark());
return success(true);
}
}

View File

@@ -8,7 +8,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
/**
* IoT 模块的 Security 配置
* Ops 模块的 Security 配置
*/
@Configuration("opsSecurityConfiguration")
public class SecurityConfiguration {
@@ -31,6 +31,8 @@ public class SecurityConfiguration {
registry.requestMatchers("/druid/**").anonymous();
// RPC 服务的安全配置
registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll();
// Open API 接口放行(由 @ApiSignature 签名保护)
registry.requestMatchers(buildOpenApi("/**")).permitAll();
}
};