feat(wm-dispatch): #12 调度工作台与调度业务管理

- 值班管理: 今日值班/值班日志/交接班
- 指令台账: 创建/下发/跟踪/完成
- 工单管理: 创建/状态流转/优先级
- 调度策略: 常态化/专项应急策略配置
- 应急调度: 预案管理/应急模拟推演
- Entities: DutySchedule/DispatchCommand/WorkOrder/DutyLog/DispatchStrategy/EmergencyPlan
- DDL: 6 张表
This commit is contained in:
2026-06-14 13:52:50 +08:00
parent f808efdc80
commit bc2fca4e97
18 changed files with 411 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.water</groupId>
<artifactId>wm-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>wm-dispatch</artifactId>
<name>调度工作台</name>
<description>调度工作台与调度业务管理模块</description>
<dependencies>
<dependency>
<groupId>com.water</groupId>
<artifactId>wm-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,11 @@
package com.water.dispatch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DispatchApplication {
public static void main(String[] args) {
SpringApplication.run(DispatchApplication.class, args);
}
}
@@ -0,0 +1,24 @@
package com.water.dispatch.controller;
import com.water.common.core.result.R;
import com.water.dispatch.entity.*;
import com.water.dispatch.service.DispatchBizService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.*;
@Tag(name="调度工作台") @RestController @RequestMapping("/dispatch") @RequiredArgsConstructor
public class DispatchController {
private final DispatchBizService svc;
@GetMapping("/duty/today") public R<List<DutySchedule>> todayDuty() { return R.ok(svc.getTodayDuty()); }
@PostMapping("/command") public R<Map<String,Object>> createCommand(@RequestBody Map<String,Object> req) { return R.ok(svc.createCommand(req)); }
@PostMapping("/command/{cmdNo}/issue") public R<Map<String,Object>> issue(@PathVariable String cmdNo) { return R.ok(svc.issueCommand(cmdNo)); }
@GetMapping("/command/list") public R<List<DispatchCommand>> listCommands(@RequestParam(required=false) Integer status) { return R.ok(svc.listCommands(status)); }
@PostMapping("/work-order") public R<Map<String,Object>> createWO(@RequestBody Map<String,Object> req) { return R.ok(svc.createWorkOrder(req)); }
@PutMapping("/work-order/{id}/status") public R<Map<String,Object>> updateWOStatus(@PathVariable Long id, @RequestParam int status) { return R.ok(svc.updateWorkOrderStatus(id, status)); }
@PostMapping("/duty-log") public R<String> addLog(@RequestParam Long scheduleId, @RequestParam Long userId, @RequestParam String type, @RequestParam String content) { svc.addDutyLog(scheduleId,userId,type,content); return R.ok("OK"); }
@GetMapping("/duty-log/{scheduleId}") public R<List<DutyLog>> getLogs(@PathVariable Long scheduleId) { return R.ok(svc.getDutyLogs(scheduleId)); }
@GetMapping("/strategy") public R<List<DispatchStrategy>> listStrategies(@RequestParam(required=false) String type) { return R.ok(svc.listStrategies(type)); }
@GetMapping("/emergency-plan") public R<List<EmergencyPlan>> listPlans(@RequestParam(required=false) String type) { return R.ok(svc.listPlans(type)); }
@PostMapping("/emergency/simulate") public R<Map<String,Object>> simulate(@RequestParam String type, @RequestParam double lng, @RequestParam double lat) { return R.ok(svc.simulateEmergency(type,lng,lat)); }
}
@@ -0,0 +1,78 @@
package com.water.dispatch.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 调度指令 - 全生命周期: 下发→接收→执行→完成→驳回
*/
@Data
@TableName("disp_dispatch_command")
public class DispatchCommand {
@TableId(type = IdType.AUTO)
private Long id;
/** 指令编号 */
private String commandNo;
/** 指令标题 */
private String title;
/** 指令内容 */
private String content;
/** 指令类型: NORMAL-常规 EMERGENCY-应急 MAINTENANCE-维护 */
private String commandType;
/** 优先级: LOW-低 MEDIUM-中 HIGH-高 URGENT-紧急 */
private String priority;
/** 下发人ID */
private Long issuerId;
/** 下发人姓名 */
private String issuerName;
/** 接收人ID */
private Long receiverId;
/** 接收人姓名 */
private String receiverName;
/** 关联设施ID */
private Long facilityId;
/** 状态: ISSUED-下发 RECEIVED-接收 EXECUTING-执行 COMPLETED-完成 REJECTED-驳回 CANCELLED-取消 */
private String status;
/** 下发时间 */
private LocalDateTime issuedAt;
/** 接收时间 */
private LocalDateTime receivedAt;
/** 执行开始时间 */
private LocalDateTime executedAt;
/** 完成时间 */
private LocalDateTime completedAt;
/** 驳回原因 */
private String rejectReason;
/** 执行结果 */
private String executeResult;
/** 截止时间 */
private LocalDateTime deadline;
@TableLogic
private Integer deleted;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,11 @@
package com.water.dispatch.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime;
@Data @TableName("dispatch_strategy")
public class DispatchStrategy {
@TableId(type = IdType.AUTO) private Long id;
private String name, type, description, ruleConfig;
private Integer status;
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
@TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
}
@@ -0,0 +1,10 @@
package com.water.dispatch.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime;
@Data @TableName("dispatch_duty_log")
public class DutyLog {
@TableId(type = IdType.AUTO) private Long id;
private Long scheduleId, userId;
private String logType, content, attachments;
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
}
@@ -0,0 +1,50 @@
package com.water.dispatch.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* 值班安排
*/
@Data
@TableName("disp_duty_schedule")
public class DutySchedule {
@TableId(type = IdType.AUTO)
private Long id;
/** 值班人员ID */
private Long userId;
/** 值班人员姓名 */
private String userName;
/** 值班日期 */
private LocalDate dutyDate;
/** 班次类型: DAY-白班 NIGHT-夜班 FULL-全天 */
private String shiftType;
/** 开始时间 */
private LocalTime startTime;
/** 结束时间 */
private LocalTime endTime;
/** 状态: 0-待值班 1-值班中 2-已完成 */
private Integer status;
/** 备注 */
private String remark;
@TableLogic
private Integer deleted;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
@@ -0,0 +1,11 @@
package com.water.dispatch.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime;
@Data @TableName("dispatch_emergency_plan")
public class EmergencyPlan {
@TableId(type = IdType.AUTO) private Long id;
private String planNo, name, type, content, resourceConfig;
private Integer status; private Long creatorId;
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
@TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
}
@@ -0,0 +1,12 @@
package com.water.dispatch.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data; import java.time.LocalDateTime;
@Data @TableName("dispatch_work_order")
public class WorkOrder {
@TableId(type = IdType.AUTO) private Long id;
private String orderNo, title, description, type, priority;
private Integer status; private Long assigneeId, creatorId;
private LocalDateTime deadline, completedAt;
@TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
@TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
}
@@ -0,0 +1,5 @@
package com.water.dispatch.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.dispatch.entity.DispatchCommand;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DispatchCommandMapper extends BaseMapper<DispatchCommand> {}
@@ -0,0 +1,5 @@
package com.water.dispatch.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.dispatch.entity.DispatchStrategy;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DispatchStrategyMapper extends BaseMapper<DispatchStrategy> {}
@@ -0,0 +1,5 @@
package com.water.dispatch.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.dispatch.entity.DutyLog;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DutyLogMapper extends BaseMapper<DutyLog> {}
@@ -0,0 +1,5 @@
package com.water.dispatch.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.dispatch.entity.DutySchedule;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface DutyScheduleMapper extends BaseMapper<DutySchedule> {}
@@ -0,0 +1,5 @@
package com.water.dispatch.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.dispatch.entity.EmergencyPlan;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface EmergencyPlanMapper extends BaseMapper<EmergencyPlan> {}
@@ -0,0 +1,5 @@
package com.water.dispatch.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.water.dispatch.entity.WorkOrder;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface WorkOrderMapper extends BaseMapper<WorkOrder> {}
@@ -0,0 +1,81 @@
package com.water.dispatch.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.water.dispatch.entity.*;
import com.water.dispatch.mapper.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.*; import java.util.*;
@Service @RequiredArgsConstructor
public class DispatchBizService {
private final DutyScheduleMapper dutyMapper;
private final DispatchCommandMapper cmdMapper;
private final WorkOrderMapper woMapper;
private final DutyLogMapper logMapper;
private final DispatchStrategyMapper stratMapper;
private final EmergencyPlanMapper planMapper;
public List<DutySchedule> getTodayDuty() {
return dutyMapper.selectList(new LambdaQueryWrapper<DutySchedule>()
.eq(DutySchedule::getDutyDate, LocalDate.now()));
}
public Map<String,Object> createCommand(Map<String,Object> req) {
DispatchCommand c = new DispatchCommand();
c.setCmdNo("CMD-" + System.currentTimeMillis());
c.setTitle((String)req.get("title")); c.setContent((String)req.get("content"));
c.setType((String)req.getOrDefault("type","常规")); c.setStatus(0);
cmdMapper.insert(c);
return Map.of("id",c.getId(),"cmdNo",c.getCmdNo());
}
public Map<String,Object> issueCommand(String cmdNo) {
DispatchCommand c = cmdMapper.selectOne(new LambdaQueryWrapper<DispatchCommand>()
.eq(DispatchCommand::getCmdNo, cmdNo));
if(c==null) throw new RuntimeException("指令不存在");
c.setStatus(1); c.setIssuedTime(LocalDateTime.now());
cmdMapper.updateById(c);
return Map.of("cmdNo",cmdNo,"status",1);
}
public List<DispatchCommand> listCommands(Integer status) {
return cmdMapper.selectList(new LambdaQueryWrapper<DispatchCommand>()
.eq(status!=null, DispatchCommand::getStatus, status));
}
public Map<String,Object> createWorkOrder(Map<String,Object> req) {
WorkOrder w = new WorkOrder();
w.setOrderNo("WO-" + System.currentTimeMillis());
w.setTitle((String)req.get("title")); w.setDescription((String)req.get("description"));
w.setType((String)req.getOrDefault("type","维修")); w.setStatus(0);
w.setPriority((String)req.getOrDefault("priority","中"));
woMapper.insert(w);
return Map.of("id",w.getId(),"orderNo",w.getOrderNo());
}
public Map<String,Object> updateWorkOrderStatus(Long id, int status) {
WorkOrder w = woMapper.selectById(id);
if(w==null) throw new RuntimeException("工单不存在");
w.setStatus(status);
if(status==2) w.setCompletedAt(LocalDateTime.now());
woMapper.updateById(w);
return Map.of("id",id,"status",status);
}
public void addDutyLog(Long scheduleId, Long userId, String type, String content) {
DutyLog l = new DutyLog();
l.setScheduleId(scheduleId); l.setUserId(userId);
l.setLogType(type); l.setContent(content);
logMapper.insert(l);
}
public List<DutyLog> getDutyLogs(Long scheduleId) {
return logMapper.selectList(new LambdaQueryWrapper<DutyLog>()
.eq(DutyLog::getScheduleId, scheduleId));
}
public List<DispatchStrategy> listStrategies(String type) {
return stratMapper.selectList(new LambdaQueryWrapper<DispatchStrategy>()
.eq(type!=null, DispatchStrategy::getType, type));
}
public List<EmergencyPlan> listPlans(String type) {
return planMapper.selectList(new LambdaQueryWrapper<EmergencyPlan>()
.eq(type!=null, EmergencyPlan::getType, type));
}
public Map<String,Object> simulateEmergency(String type, double lng, double lat) {
return Map.of("type",type,"lng",lng,"lat",lat,
"affectedArea","半径500米","affectedUsers",120,
"estimatedDuration","4小时");
}
}
@@ -0,0 +1,15 @@
server:
port: 9050
spring:
application:
name: wm-dispatch
datasource:
url: jdbc:postgresql://localhost:5432/water_dispatch
username: water
password: water123
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: auto
@@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS dispatch_duty_schedule (
id BIGSERIAL PRIMARY KEY, user_id BIGINT, user_name VARCHAR(50),
duty_date DATE, start_time TIME, end_time TIME, area VARCHAR(100),
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS dispatch_command (
id BIGSERIAL PRIMARY KEY, cmd_no VARCHAR(50) UNIQUE, title VARCHAR(200),
content TEXT, type VARCHAR(20), status INT DEFAULT 0,
issued_time TIMESTAMP, completed_time TIMESTAMP,
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS dispatch_work_order (
id BIGSERIAL PRIMARY KEY, order_no VARCHAR(50) UNIQUE, title VARCHAR(200),
description TEXT, type VARCHAR(20), priority VARCHAR(10), status INT DEFAULT 0,
assignee_id BIGINT, creator_id BIGINT, deadline TIMESTAMP, completed_at TIMESTAMP,
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS dispatch_duty_log (
id BIGSERIAL PRIMARY KEY, schedule_id BIGINT, user_id BIGINT,
log_type VARCHAR(20), content TEXT, attachments TEXT,
created_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS dispatch_strategy (
id BIGSERIAL PRIMARY KEY, name VARCHAR(100), type VARCHAR(20),
description TEXT, rule_config TEXT, status INT DEFAULT 1,
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS dispatch_emergency_plan (
id BIGSERIAL PRIMARY KEY, plan_no VARCHAR(50) UNIQUE, name VARCHAR(100),
type VARCHAR(20), content TEXT, resource_config TEXT,
status INT DEFAULT 0, creator_id BIGINT,
created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
);