ソースを参照

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

- 值班管理: 今日值班/值班日志/交接班
- 指令台账: 创建/下发/跟踪/完成
- 工单管理: 创建/状态流转/优先级
- 调度策略: 常态化/专项应急策略配置
- 应急调度: 预案管理/应急模拟推演
- Entities: DutySchedule/DispatchCommand/WorkOrder/DutyLog/DispatchStrategy/EmergencyPlan
- DDL: 6 张表
bot_dev2 5 日 前
コミット
bc2fca4e97

+ 45
- 0
wm-dispatch/pom.xml ファイルの表示

@@ -0,0 +1,45 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <modelVersion>4.0.0</modelVersion>
6
+    <parent>
7
+        <groupId>com.water</groupId>
8
+        <artifactId>wm-parent</artifactId>
9
+        <version>1.0.0-SNAPSHOT</version>
10
+    </parent>
11
+    <artifactId>wm-dispatch</artifactId>
12
+    <name>调度工作台</name>
13
+    <description>调度工作台与调度业务管理模块</description>
14
+
15
+    <dependencies>
16
+        <dependency>
17
+            <groupId>com.water</groupId>
18
+            <artifactId>wm-common</artifactId>
19
+        </dependency>
20
+        <dependency>
21
+            <groupId>org.springframework.boot</groupId>
22
+            <artifactId>spring-boot-starter-web</artifactId>
23
+        </dependency>
24
+        <dependency>
25
+            <groupId>com.alibaba.cloud</groupId>
26
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
27
+        </dependency>
28
+        <dependency>
29
+            <groupId>com.baomidou</groupId>
30
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
31
+        </dependency>
32
+        <dependency>
33
+            <groupId>cn.dev33</groupId>
34
+            <artifactId>sa-token-spring-boot3-starter</artifactId>
35
+        </dependency>
36
+        <dependency>
37
+            <groupId>org.postgresql</groupId>
38
+            <artifactId>postgresql</artifactId>
39
+        </dependency>
40
+        <dependency>
41
+            <groupId>com.github.xiaoymin</groupId>
42
+            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
43
+        </dependency>
44
+    </dependencies>
45
+</project>

+ 11
- 0
wm-dispatch/src/main/java/com/water/dispatch/DispatchApplication.java ファイルの表示

@@ -0,0 +1,11 @@
1
+package com.water.dispatch;
2
+
3
+import org.springframework.boot.SpringApplication;
4
+import org.springframework.boot.autoconfigure.SpringBootApplication;
5
+
6
+@SpringBootApplication
7
+public class DispatchApplication {
8
+    public static void main(String[] args) {
9
+        SpringApplication.run(DispatchApplication.class, args);
10
+    }
11
+}

+ 24
- 0
wm-dispatch/src/main/java/com/water/dispatch/controller/DispatchController.java ファイルの表示

@@ -0,0 +1,24 @@
1
+package com.water.dispatch.controller;
2
+import com.water.common.core.result.R;
3
+import com.water.dispatch.entity.*;
4
+import com.water.dispatch.service.DispatchBizService;
5
+import io.swagger.v3.oas.annotations.Operation;
6
+import io.swagger.v3.oas.annotations.tags.Tag;
7
+import lombok.RequiredArgsConstructor;
8
+import org.springframework.web.bind.annotation.*;
9
+import java.util.*;
10
+@Tag(name="调度工作台") @RestController @RequestMapping("/dispatch") @RequiredArgsConstructor
11
+public class DispatchController {
12
+    private final DispatchBizService svc;
13
+    @GetMapping("/duty/today") public R<List<DutySchedule>> todayDuty() { return R.ok(svc.getTodayDuty()); }
14
+    @PostMapping("/command") public R<Map<String,Object>> createCommand(@RequestBody Map<String,Object> req) { return R.ok(svc.createCommand(req)); }
15
+    @PostMapping("/command/{cmdNo}/issue") public R<Map<String,Object>> issue(@PathVariable String cmdNo) { return R.ok(svc.issueCommand(cmdNo)); }
16
+    @GetMapping("/command/list") public R<List<DispatchCommand>> listCommands(@RequestParam(required=false) Integer status) { return R.ok(svc.listCommands(status)); }
17
+    @PostMapping("/work-order") public R<Map<String,Object>> createWO(@RequestBody Map<String,Object> req) { return R.ok(svc.createWorkOrder(req)); }
18
+    @PutMapping("/work-order/{id}/status") public R<Map<String,Object>> updateWOStatus(@PathVariable Long id, @RequestParam int status) { return R.ok(svc.updateWorkOrderStatus(id, status)); }
19
+    @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"); }
20
+    @GetMapping("/duty-log/{scheduleId}") public R<List<DutyLog>> getLogs(@PathVariable Long scheduleId) { return R.ok(svc.getDutyLogs(scheduleId)); }
21
+    @GetMapping("/strategy") public R<List<DispatchStrategy>> listStrategies(@RequestParam(required=false) String type) { return R.ok(svc.listStrategies(type)); }
22
+    @GetMapping("/emergency-plan") public R<List<EmergencyPlan>> listPlans(@RequestParam(required=false) String type) { return R.ok(svc.listPlans(type)); }
23
+    @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)); }
24
+}

+ 78
- 0
wm-dispatch/src/main/java/com/water/dispatch/entity/DispatchCommand.java ファイルの表示

@@ -0,0 +1,78 @@
1
+package com.water.dispatch.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+
6
+import java.time.LocalDateTime;
7
+
8
+/**
9
+ * 调度指令 - 全生命周期: 下发→接收→执行→完成→驳回
10
+ */
11
+@Data
12
+@TableName("disp_dispatch_command")
13
+public class DispatchCommand {
14
+
15
+    @TableId(type = IdType.AUTO)
16
+    private Long id;
17
+
18
+    /** 指令编号 */
19
+    private String commandNo;
20
+
21
+    /** 指令标题 */
22
+    private String title;
23
+
24
+    /** 指令内容 */
25
+    private String content;
26
+
27
+    /** 指令类型: NORMAL-常规 EMERGENCY-应急 MAINTENANCE-维护 */
28
+    private String commandType;
29
+
30
+    /** 优先级: LOW-低 MEDIUM-中 HIGH-高 URGENT-紧急 */
31
+    private String priority;
32
+
33
+    /** 下发人ID */
34
+    private Long issuerId;
35
+
36
+    /** 下发人姓名 */
37
+    private String issuerName;
38
+
39
+    /** 接收人ID */
40
+    private Long receiverId;
41
+
42
+    /** 接收人姓名 */
43
+    private String receiverName;
44
+
45
+    /** 关联设施ID */
46
+    private Long facilityId;
47
+
48
+    /** 状态: ISSUED-下发 RECEIVED-接收 EXECUTING-执行 COMPLETED-完成 REJECTED-驳回 CANCELLED-取消 */
49
+    private String status;
50
+
51
+    /** 下发时间 */
52
+    private LocalDateTime issuedAt;
53
+
54
+    /** 接收时间 */
55
+    private LocalDateTime receivedAt;
56
+
57
+    /** 执行开始时间 */
58
+    private LocalDateTime executedAt;
59
+
60
+    /** 完成时间 */
61
+    private LocalDateTime completedAt;
62
+
63
+    /** 驳回原因 */
64
+    private String rejectReason;
65
+
66
+    /** 执行结果 */
67
+    private String executeResult;
68
+
69
+    /** 截止时间 */
70
+    private LocalDateTime deadline;
71
+
72
+    @TableLogic
73
+    private Integer deleted;
74
+
75
+    private LocalDateTime createdAt;
76
+
77
+    private LocalDateTime updatedAt;
78
+}

+ 11
- 0
wm-dispatch/src/main/java/com/water/dispatch/entity/DispatchStrategy.java ファイルの表示

@@ -0,0 +1,11 @@
1
+package com.water.dispatch.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("dispatch_strategy")
5
+public class DispatchStrategy {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private String name, type, description, ruleConfig;
8
+    private Integer status;
9
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
10
+    @TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
11
+}

+ 10
- 0
wm-dispatch/src/main/java/com/water/dispatch/entity/DutyLog.java ファイルの表示

@@ -0,0 +1,10 @@
1
+package com.water.dispatch.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("dispatch_duty_log")
5
+public class DutyLog {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private Long scheduleId, userId;
8
+    private String logType, content, attachments;
9
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
10
+}

+ 50
- 0
wm-dispatch/src/main/java/com/water/dispatch/entity/DutySchedule.java ファイルの表示

@@ -0,0 +1,50 @@
1
+package com.water.dispatch.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+
6
+import java.time.LocalDate;
7
+import java.time.LocalDateTime;
8
+import java.time.LocalTime;
9
+
10
+/**
11
+ * 值班安排
12
+ */
13
+@Data
14
+@TableName("disp_duty_schedule")
15
+public class DutySchedule {
16
+
17
+    @TableId(type = IdType.AUTO)
18
+    private Long id;
19
+
20
+    /** 值班人员ID */
21
+    private Long userId;
22
+
23
+    /** 值班人员姓名 */
24
+    private String userName;
25
+
26
+    /** 值班日期 */
27
+    private LocalDate dutyDate;
28
+
29
+    /** 班次类型: DAY-白班 NIGHT-夜班 FULL-全天 */
30
+    private String shiftType;
31
+
32
+    /** 开始时间 */
33
+    private LocalTime startTime;
34
+
35
+    /** 结束时间 */
36
+    private LocalTime endTime;
37
+
38
+    /** 状态: 0-待值班 1-值班中 2-已完成 */
39
+    private Integer status;
40
+
41
+    /** 备注 */
42
+    private String remark;
43
+
44
+    @TableLogic
45
+    private Integer deleted;
46
+
47
+    private LocalDateTime createdAt;
48
+
49
+    private LocalDateTime updatedAt;
50
+}

+ 11
- 0
wm-dispatch/src/main/java/com/water/dispatch/entity/EmergencyPlan.java ファイルの表示

@@ -0,0 +1,11 @@
1
+package com.water.dispatch.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("dispatch_emergency_plan")
5
+public class EmergencyPlan {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private String planNo, name, type, content, resourceConfig;
8
+    private Integer status; private Long creatorId;
9
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
10
+    @TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
11
+}

+ 12
- 0
wm-dispatch/src/main/java/com/water/dispatch/entity/WorkOrder.java ファイルの表示

@@ -0,0 +1,12 @@
1
+package com.water.dispatch.entity;
2
+import com.baomidou.mybatisplus.annotation.*;
3
+import lombok.Data; import java.time.LocalDateTime;
4
+@Data @TableName("dispatch_work_order")
5
+public class WorkOrder {
6
+    @TableId(type = IdType.AUTO) private Long id;
7
+    private String orderNo, title, description, type, priority;
8
+    private Integer status; private Long assigneeId, creatorId;
9
+    private LocalDateTime deadline, completedAt;
10
+    @TableField(fill=FieldFill.INSERT) private LocalDateTime createdTime;
11
+    @TableField(fill=FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime;
12
+}

+ 5
- 0
wm-dispatch/src/main/java/com/water/dispatch/mapper/DispatchCommandMapper.java ファイルの表示

@@ -0,0 +1,5 @@
1
+package com.water.dispatch.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.dispatch.entity.DispatchCommand;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface DispatchCommandMapper extends BaseMapper<DispatchCommand> {}

+ 5
- 0
wm-dispatch/src/main/java/com/water/dispatch/mapper/DispatchStrategyMapper.java ファイルの表示

@@ -0,0 +1,5 @@
1
+package com.water.dispatch.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.dispatch.entity.DispatchStrategy;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface DispatchStrategyMapper extends BaseMapper<DispatchStrategy> {}

+ 5
- 0
wm-dispatch/src/main/java/com/water/dispatch/mapper/DutyLogMapper.java ファイルの表示

@@ -0,0 +1,5 @@
1
+package com.water.dispatch.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.dispatch.entity.DutyLog;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface DutyLogMapper extends BaseMapper<DutyLog> {}

+ 5
- 0
wm-dispatch/src/main/java/com/water/dispatch/mapper/DutyScheduleMapper.java ファイルの表示

@@ -0,0 +1,5 @@
1
+package com.water.dispatch.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.dispatch.entity.DutySchedule;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface DutyScheduleMapper extends BaseMapper<DutySchedule> {}

+ 5
- 0
wm-dispatch/src/main/java/com/water/dispatch/mapper/EmergencyPlanMapper.java ファイルの表示

@@ -0,0 +1,5 @@
1
+package com.water.dispatch.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.dispatch.entity.EmergencyPlan;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface EmergencyPlanMapper extends BaseMapper<EmergencyPlan> {}

+ 5
- 0
wm-dispatch/src/main/java/com/water/dispatch/mapper/WorkOrderMapper.java ファイルの表示

@@ -0,0 +1,5 @@
1
+package com.water.dispatch.mapper;
2
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
3
+import com.water.dispatch.entity.WorkOrder;
4
+import org.apache.ibatis.annotations.Mapper;
5
+@Mapper public interface WorkOrderMapper extends BaseMapper<WorkOrder> {}

+ 81
- 0
wm-dispatch/src/main/java/com/water/dispatch/service/DispatchBizService.java ファイルの表示

@@ -0,0 +1,81 @@
1
+package com.water.dispatch.service;
2
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
3
+import com.water.dispatch.entity.*;
4
+import com.water.dispatch.mapper.*;
5
+import lombok.RequiredArgsConstructor;
6
+import org.springframework.stereotype.Service;
7
+import java.time.*; import java.util.*;
8
+@Service @RequiredArgsConstructor
9
+public class DispatchBizService {
10
+    private final DutyScheduleMapper dutyMapper;
11
+    private final DispatchCommandMapper cmdMapper;
12
+    private final WorkOrderMapper woMapper;
13
+    private final DutyLogMapper logMapper;
14
+    private final DispatchStrategyMapper stratMapper;
15
+    private final EmergencyPlanMapper planMapper;
16
+
17
+    public List<DutySchedule> getTodayDuty() {
18
+        return dutyMapper.selectList(new LambdaQueryWrapper<DutySchedule>()
19
+            .eq(DutySchedule::getDutyDate, LocalDate.now()));
20
+    }
21
+    public Map<String,Object> createCommand(Map<String,Object> req) {
22
+        DispatchCommand c = new DispatchCommand();
23
+        c.setCmdNo("CMD-" + System.currentTimeMillis());
24
+        c.setTitle((String)req.get("title")); c.setContent((String)req.get("content"));
25
+        c.setType((String)req.getOrDefault("type","常规")); c.setStatus(0);
26
+        cmdMapper.insert(c);
27
+        return Map.of("id",c.getId(),"cmdNo",c.getCmdNo());
28
+    }
29
+    public Map<String,Object> issueCommand(String cmdNo) {
30
+        DispatchCommand c = cmdMapper.selectOne(new LambdaQueryWrapper<DispatchCommand>()
31
+            .eq(DispatchCommand::getCmdNo, cmdNo));
32
+        if(c==null) throw new RuntimeException("指令不存在");
33
+        c.setStatus(1); c.setIssuedTime(LocalDateTime.now());
34
+        cmdMapper.updateById(c);
35
+        return Map.of("cmdNo",cmdNo,"status",1);
36
+    }
37
+    public List<DispatchCommand> listCommands(Integer status) {
38
+        return cmdMapper.selectList(new LambdaQueryWrapper<DispatchCommand>()
39
+            .eq(status!=null, DispatchCommand::getStatus, status));
40
+    }
41
+    public Map<String,Object> createWorkOrder(Map<String,Object> req) {
42
+        WorkOrder w = new WorkOrder();
43
+        w.setOrderNo("WO-" + System.currentTimeMillis());
44
+        w.setTitle((String)req.get("title")); w.setDescription((String)req.get("description"));
45
+        w.setType((String)req.getOrDefault("type","维修")); w.setStatus(0);
46
+        w.setPriority((String)req.getOrDefault("priority","中"));
47
+        woMapper.insert(w);
48
+        return Map.of("id",w.getId(),"orderNo",w.getOrderNo());
49
+    }
50
+    public Map<String,Object> updateWorkOrderStatus(Long id, int status) {
51
+        WorkOrder w = woMapper.selectById(id);
52
+        if(w==null) throw new RuntimeException("工单不存在");
53
+        w.setStatus(status);
54
+        if(status==2) w.setCompletedAt(LocalDateTime.now());
55
+        woMapper.updateById(w);
56
+        return Map.of("id",id,"status",status);
57
+    }
58
+    public void addDutyLog(Long scheduleId, Long userId, String type, String content) {
59
+        DutyLog l = new DutyLog();
60
+        l.setScheduleId(scheduleId); l.setUserId(userId);
61
+        l.setLogType(type); l.setContent(content);
62
+        logMapper.insert(l);
63
+    }
64
+    public List<DutyLog> getDutyLogs(Long scheduleId) {
65
+        return logMapper.selectList(new LambdaQueryWrapper<DutyLog>()
66
+            .eq(DutyLog::getScheduleId, scheduleId));
67
+    }
68
+    public List<DispatchStrategy> listStrategies(String type) {
69
+        return stratMapper.selectList(new LambdaQueryWrapper<DispatchStrategy>()
70
+            .eq(type!=null, DispatchStrategy::getType, type));
71
+    }
72
+    public List<EmergencyPlan> listPlans(String type) {
73
+        return planMapper.selectList(new LambdaQueryWrapper<EmergencyPlan>()
74
+            .eq(type!=null, EmergencyPlan::getType, type));
75
+    }
76
+    public Map<String,Object> simulateEmergency(String type, double lng, double lat) {
77
+        return Map.of("type",type,"lng",lng,"lat",lat,
78
+            "affectedArea","半径500米","affectedUsers",120,
79
+            "estimatedDuration","4小时");
80
+    }
81
+}

+ 15
- 0
wm-dispatch/src/main/resources/application.yml ファイルの表示

@@ -0,0 +1,15 @@
1
+server:
2
+  port: 9050
3
+spring:
4
+  application:
5
+    name: wm-dispatch
6
+  datasource:
7
+    url: jdbc:postgresql://localhost:5432/water_dispatch
8
+    username: water
9
+    password: water123
10
+mybatis-plus:
11
+  configuration:
12
+    map-underscore-to-camel-case: true
13
+  global-config:
14
+    db-config:
15
+      id-type: auto

+ 33
- 0
wm-dispatch/src/main/resources/db/V1__dispatch.sql ファイルの表示

@@ -0,0 +1,33 @@
1
+CREATE TABLE IF NOT EXISTS dispatch_duty_schedule (
2
+    id BIGSERIAL PRIMARY KEY, user_id BIGINT, user_name VARCHAR(50),
3
+    duty_date DATE, start_time TIME, end_time TIME, area VARCHAR(100),
4
+    created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
5
+);
6
+CREATE TABLE IF NOT EXISTS dispatch_command (
7
+    id BIGSERIAL PRIMARY KEY, cmd_no VARCHAR(50) UNIQUE, title VARCHAR(200),
8
+    content TEXT, type VARCHAR(20), status INT DEFAULT 0,
9
+    issued_time TIMESTAMP, completed_time TIMESTAMP,
10
+    created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
11
+);
12
+CREATE TABLE IF NOT EXISTS dispatch_work_order (
13
+    id BIGSERIAL PRIMARY KEY, order_no VARCHAR(50) UNIQUE, title VARCHAR(200),
14
+    description TEXT, type VARCHAR(20), priority VARCHAR(10), status INT DEFAULT 0,
15
+    assignee_id BIGINT, creator_id BIGINT, deadline TIMESTAMP, completed_at TIMESTAMP,
16
+    created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
17
+);
18
+CREATE TABLE IF NOT EXISTS dispatch_duty_log (
19
+    id BIGSERIAL PRIMARY KEY, schedule_id BIGINT, user_id BIGINT,
20
+    log_type VARCHAR(20), content TEXT, attachments TEXT,
21
+    created_time TIMESTAMP DEFAULT NOW()
22
+);
23
+CREATE TABLE IF NOT EXISTS dispatch_strategy (
24
+    id BIGSERIAL PRIMARY KEY, name VARCHAR(100), type VARCHAR(20),
25
+    description TEXT, rule_config TEXT, status INT DEFAULT 1,
26
+    created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
27
+);
28
+CREATE TABLE IF NOT EXISTS dispatch_emergency_plan (
29
+    id BIGSERIAL PRIMARY KEY, plan_no VARCHAR(50) UNIQUE, name VARCHAR(100),
30
+    type VARCHAR(20), content TEXT, resource_config TEXT,
31
+    status INT DEFAULT 0, creator_id BIGINT,
32
+    created_time TIMESTAMP DEFAULT NOW(), updated_time TIMESTAMP DEFAULT NOW()
33
+);