From f337ebedfdf26403741c0ad9a90cc182231d8552 Mon Sep 17 00:00:00 2001 From: bot_pm Date: Sun, 14 Jun 2026 13:15:38 +0800 Subject: [PATCH] =?UTF-8?q?Phase=201=20#18:=20PostgreSQL=20+=20TDengine=20?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=20DDL=20=E5=BB=BA=E8=A1=A8=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 15+张核心表: sys_*(部门/用户/角色/菜单/日志/字典/通知/文件) - iot_*: 设备模型/设备实例/设备影子/设备事件/站点(PostGIS空间坐标) - rev_*: 用水户/水价阶梯/水表档案/水表操作日志/抄表/账单/缴费/报装 - production: alert_rule(报警规则)/alert_event(报警事件)/dispatch_command(调度)/duty(值班)/patrol(巡检)/water_quality(水质) - TDengine: iot_telemetry超级表(6类设备子表)+事件表+小时聚合 - 种子数据: admin管理员/5个角色/6个片区/基础字典 --- db/postgresql/V1__base.sql | 210 +++++++++++++++++++++++++++++++ db/postgresql/V1__iot.sql | 101 +++++++++++++++ db/postgresql/V1__production.sql | 189 ++++++++++++++++++++++++++++ db/postgresql/V1__revenue.sql | 164 ++++++++++++++++++++++++ db/seed/V1__seed.sql | 79 ++++++++++++ db/tdengine/init.sql | 68 ++++++++++ 6 files changed, 811 insertions(+) create mode 100644 db/postgresql/V1__base.sql create mode 100644 db/postgresql/V1__iot.sql create mode 100644 db/postgresql/V1__production.sql create mode 100644 db/postgresql/V1__revenue.sql create mode 100644 db/seed/V1__seed.sql create mode 100644 db/tdengine/init.sql diff --git a/db/postgresql/V1__base.sql b/db/postgresql/V1__base.sql new file mode 100644 index 00000000..d7dbdbf2 --- /dev/null +++ b/db/postgresql/V1__base.sql @@ -0,0 +1,210 @@ +-- ============================================= +-- 智慧水务管理系统 - PostgreSQL DDL +-- 版本: V1 +-- 描述: 核心业务表建表脚本 +-- ============================================= + +-- ==================== 系统管理 ==================== + +-- 部门表 +CREATE TABLE IF NOT EXISTS sys_dept ( + id BIGSERIAL PRIMARY KEY, + parent_id BIGINT, + dept_name VARCHAR(100) NOT NULL, + dept_type VARCHAR(20) NOT NULL DEFAULT 'water_company', -- water_bureau/water_company/ops + sort_order INT DEFAULT 0, + leader VARCHAR(50), + phone VARCHAR(20), + status SMALLINT DEFAULT 1, + deleted SMALLINT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_dept IS '部门表'; +COMMENT ON COLUMN sys_dept.dept_type IS '部门类型: water_bureau(水利局)/water_company(水务公司)/ops(运维单位)'; + +-- 用户表 +CREATE TABLE IF NOT EXISTS sys_user ( + id BIGSERIAL PRIMARY KEY, + dept_id BIGINT REFERENCES sys_dept(id), + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + real_name VARCHAR(50), + nickname VARCHAR(50), + phone VARCHAR(20), + email VARCHAR(100), + avatar VARCHAR(500), + gender SMALLINT DEFAULT 0, -- 0:未知 1:男 2:女 + role_type VARCHAR(30) DEFAULT 'operator', -- admin/leader/manager/operator/tech + status SMALLINT DEFAULT 1, -- 0:停用 1:启用 + login_ip VARCHAR(50), + login_at TIMESTAMP, + deleted SMALLINT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_user IS '用户表'; +COMMENT ON COLUMN sys_user.role_type IS '角色类型: admin/leader/manager/operator/tech'; + +-- 角色表 +CREATE TABLE IF NOT EXISTS sys_role ( + id BIGSERIAL PRIMARY KEY, + role_name VARCHAR(50) UNIQUE NOT NULL, + role_key VARCHAR(50) UNIQUE NOT NULL, + role_sort INT DEFAULT 0, + data_scope VARCHAR(20) DEFAULT 'SELF', -- ALL/DEPT/CUSTOM/SELF + status SMALLINT DEFAULT 1, + remark VARCHAR(500), + deleted SMALLINT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_role IS '角色表'; +COMMENT ON COLUMN sys_role.role_key IS '角色标识: admin/supervisor/biz_manager/field_ops/tech_maintain'; +COMMENT ON COLUMN sys_role.data_scope IS '数据权限范围: ALL/DEPT/CUSTOM/SELF'; + +-- 菜单表 +CREATE TABLE IF NOT EXISTS sys_menu ( + id BIGSERIAL PRIMARY KEY, + parent_id BIGINT DEFAULT 0, + menu_name VARCHAR(50) NOT NULL, + menu_type CHAR(1) DEFAULT 'M', -- M:目录 C:菜单 F:按钮 + path VARCHAR(200), + component VARCHAR(255), + perms VARCHAR(100), + icon VARCHAR(100), + sort_order INT DEFAULT 0, + visible SMALLINT DEFAULT 1, + status SMALLINT DEFAULT 1, + deleted SMALLINT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_menu IS '菜单表'; + +-- 角色-菜单关联 +CREATE TABLE IF NOT EXISTS sys_role_menu ( + role_id BIGINT NOT NULL REFERENCES sys_role(id), + menu_id BIGINT NOT NULL REFERENCES sys_menu(id), + PRIMARY KEY (role_id, menu_id) +); +COMMENT ON TABLE sys_role_menu IS '角色菜单关联表'; + +-- 用户-角色关联 +CREATE TABLE IF NOT EXISTS sys_user_role ( + user_id BIGINT NOT NULL REFERENCES sys_user(id), + role_id BIGINT NOT NULL REFERENCES sys_role(id), + PRIMARY KEY (user_id, role_id) +); +COMMENT ON TABLE sys_user_role IS '用户角色关联表'; + +-- 操作日志表 +CREATE TABLE IF NOT EXISTS sys_oper_log ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT, + username VARCHAR(50), + module VARCHAR(50), + operation VARCHAR(50), + method VARCHAR(200), + request_method VARCHAR(10), + request_url VARCHAR(500), + request_params TEXT, + response_result TEXT, + ip VARCHAR(50), + location VARCHAR(100), + duration BIGINT, + status SMALLINT DEFAULT 1, + error_msg TEXT, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_oper_log IS '操作日志表'; + +-- 登录日志表 +CREATE TABLE IF NOT EXISTS sys_login_log ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50), + ip VARCHAR(50), + location VARCHAR(100), + browser VARCHAR(100), + os VARCHAR(100), + status SMALLINT DEFAULT 1, -- 0:失败 1:成功 + msg VARCHAR(500), + login_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_login_log IS '登录日志表'; + +-- ==================== 数据字典 ==================== + +CREATE TABLE IF NOT EXISTS sys_dict_type ( + id BIGSERIAL PRIMARY KEY, + dict_name VARCHAR(100) NOT NULL, + dict_key VARCHAR(100) UNIQUE NOT NULL, + status SMALLINT DEFAULT 1, + remark VARCHAR(500), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_dict_type IS '字典类型表'; + +CREATE TABLE IF NOT EXISTS sys_dict_data ( + id BIGSERIAL PRIMARY KEY, + dict_type_id BIGINT REFERENCES sys_dict_type(id), + dict_label VARCHAR(100) NOT NULL, + dict_value VARCHAR(100) NOT NULL, + css_class VARCHAR(100), + list_class VARCHAR(100), + sort_order INT DEFAULT 0, + status SMALLINT DEFAULT 1, + remark VARCHAR(500), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_dict_data IS '字典数据表'; + +-- ==================== 通知方案 ==================== + +CREATE TABLE IF NOT EXISTS sys_notify_scheme ( + id BIGSERIAL PRIMARY KEY, + scheme_name VARCHAR(100) NOT NULL, + channels VARCHAR(200), -- sms,app_push,websocket,wechat (逗号分隔) + template_id VARCHAR(50), + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_notify_scheme IS '通知方案表'; + +CREATE TABLE IF NOT EXISTS sys_notify_record ( + id BIGSERIAL PRIMARY KEY, + scheme_id BIGINT REFERENCES sys_notify_scheme(id), + target_user_id BIGINT, + target_phone VARCHAR(20), + channel VARCHAR(20), + title VARCHAR(200), + content TEXT, + status VARCHAR(20) DEFAULT 'pending', -- pending/sent/failed/read + sent_at TIMESTAMP, + read_at TIMESTAMP, + error_msg VARCHAR(500), + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_notify_record IS '通知记录表'; + +-- ==================== 文件管理 ==================== + +CREATE TABLE IF NOT EXISTS sys_file ( + id BIGSERIAL PRIMARY KEY, + file_name VARCHAR(200) NOT NULL, + original_name VARCHAR(200), + file_path VARCHAR(500) NOT NULL, + file_size BIGINT, + mime_type VARCHAR(100), + file_ext VARCHAR(20), + storage_type VARCHAR(20) DEFAULT 'minio', + bucket VARCHAR(100) DEFAULT 'water-management', + module VARCHAR(50), + biz_id BIGINT, + upload_by BIGINT, + deleted SMALLINT DEFAULT 0, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE sys_file IS '文件管理表'; diff --git a/db/postgresql/V1__iot.sql b/db/postgresql/V1__iot.sql new file mode 100644 index 00000000..ed44a856 --- /dev/null +++ b/db/postgresql/V1__iot.sql @@ -0,0 +1,101 @@ +-- ============================================= +-- 智慧水务管理系统 - 物联网设备相关 DDL +-- 版本: V1 +-- ============================================= + +-- 扩展 +CREATE EXTENSION IF NOT EXISTS postgis; + +-- 设备模型定义 +CREATE TABLE IF NOT EXISTS iot_device_model ( + id BIGSERIAL PRIMARY KEY, + model_key VARCHAR(50) UNIQUE NOT NULL, + model_name VARCHAR(100) NOT NULL, + vendor VARCHAR(100), + protocol VARCHAR(20) NOT NULL, -- MQTT/Modbus/CoAP/HTTP + properties JSONB, -- [{key, name, unit, data_type, range_min, range_max}] + commands JSONB, -- [{key, name, params: [{key, name, type}]}] + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE iot_device_model IS '设备模型定义表'; +COMMENT ON COLUMN iot_device_model.properties IS '属性定义JSON数组'; +COMMENT ON COLUMN iot_device_model.commands IS '支持指令JSON数组'; + +-- 设备实例 +CREATE TABLE IF NOT EXISTS iot_device ( + id BIGSERIAL PRIMARY KEY, + device_sn VARCHAR(100) UNIQUE NOT NULL, + device_name VARCHAR(200) NOT NULL, + model_id BIGINT REFERENCES iot_device_model(id), + device_type VARCHAR(30) NOT NULL, -- flow_meter/pressure_sensor/valve/water_quality/level_sensor/pump + position_type VARCHAR(20), -- water_plant/pressure_station/pipe_network/village + label VARCHAR(50), -- 设备标签(便于搜索) + loc_lng DOUBLE PRECISION, + loc_lat DOUBLE PRECISION, + geom GEOMETRY(Point, 4326), + area VARCHAR(50) NOT NULL, -- 所属片区 + station_id BIGINT, -- 所属站点 + address VARCHAR(300), + install_date DATE, + warranty_until DATE, + status VARCHAR(20) DEFAULT 'offline', -- online/offline/maintenance/fault + firmware_version VARCHAR(20), + last_report_time TIMESTAMP, + metadata JSONB, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE iot_device IS '设备实例表'; +COMMENT ON COLUMN iot_device.geom IS 'PostGIS空间坐标点'; +COMMENT ON COLUMN iot_device.metadata IS '扩展元数据'; + +-- 空间索引 +CREATE INDEX IF NOT EXISTS idx_iot_device_geom ON iot_device USING GIST(geom); +CREATE INDEX IF NOT EXISTS idx_iot_device_area ON iot_device(area); +CREATE INDEX IF NOT EXISTS idx_iot_device_type ON iot_device(device_type); +CREATE INDEX IF NOT EXISTS idx_iot_device_status ON iot_device(status); + +-- 设备影子 +CREATE TABLE IF NOT EXISTS iot_device_shadow ( + id BIGSERIAL PRIMARY KEY, + device_id BIGINT UNIQUE REFERENCES iot_device(id), + reported_state JSONB, -- 设备上报的最新状态 + desired_state JSONB, -- 期望状态(云端下发) + delta_state JSONB, -- 差异状态 + version BIGINT DEFAULT 1, + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE iot_device_shadow IS '设备影子表'; + +-- 设备事件 +CREATE TABLE IF NOT EXISTS iot_device_event ( + id BIGSERIAL PRIMARY KEY, + device_id BIGINT REFERENCES iot_device(id), + device_sn VARCHAR(100) NOT NULL, + event_type VARCHAR(30) NOT NULL, -- online/offline/fault/recovery/ota + event_data JSONB, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE iot_device_event IS '设备事件表'; +CREATE INDEX IF NOT EXISTS idx_iot_device_event_sn ON iot_device_event(device_sn, created_at DESC); + +-- 站点管理(水厂/调压站等) +CREATE TABLE IF NOT EXISTS iot_station ( + id BIGSERIAL PRIMARY KEY, + station_name VARCHAR(100) NOT NULL, + station_type VARCHAR(30) NOT NULL, -- water_plant/pressure_station/pump_station + area VARCHAR(50) NOT NULL, + loc_lng DOUBLE PRECISION, + loc_lat DOUBLE PRECISION, + geom GEOMETRY(Point, 4326), + address VARCHAR(300), + manager VARCHAR(50), + manager_phone VARCHAR(20), + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE iot_station IS '站点管理表'; +CREATE INDEX IF NOT EXISTS idx_iot_station_geom ON iot_station USING GIST(geom); diff --git a/db/postgresql/V1__production.sql b/db/postgresql/V1__production.sql new file mode 100644 index 00000000..8d7131b4 --- /dev/null +++ b/db/postgresql/V1__production.sql @@ -0,0 +1,189 @@ +-- ============================================= +-- 智慧水务管理系统 - 供水生产 + 巡检 + 报警 DDL +-- 版本: V1 +-- ============================================= + +-- ==================== 报警规则 ==================== + +CREATE TABLE IF NOT EXISTS alert_rule ( + id BIGSERIAL PRIMARY KEY, + rule_name VARCHAR(100) NOT NULL, + device_type VARCHAR(30), + metric_key VARCHAR(50) NOT NULL, + alert_level VARCHAR(10) NOT NULL, -- info/warning/critical/emergency + condition_expr VARCHAR(200) NOT NULL, -- "> 0.5" 或 "ABS_DELTA > 10" + threshold_value DECIMAL(12,4), + debounce_sec INT DEFAULT 300, -- 去重窗口(秒) + notify_scheme_id BIGINT REFERENCES sys_notify_scheme(id), + enabled SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE alert_rule IS '报警规则表'; +COMMENT ON COLUMN alert_rule.alert_level IS '报警等级: info/warning/critical/emergency'; + +CREATE TABLE IF NOT EXISTS alert_event ( + id BIGSERIAL PRIMARY KEY, + rule_id BIGINT REFERENCES alert_rule(id), + device_id BIGINT, + device_sn VARCHAR(100) NOT NULL, + device_name VARCHAR(200), + area VARCHAR(50), + metric_key VARCHAR(50) NOT NULL, + metric_value DECIMAL(12,4), + threshold_value VARCHAR(50), + alert_level VARCHAR(10) NOT NULL, + title VARCHAR(200), + message TEXT, + confirmed_by BIGINT, + confirmed_at TIMESTAMP, + dispatched SMALLINT DEFAULT 0, -- 是否已派单 + resolved_by BIGINT, + resolved_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE alert_event IS '报警事件表'; +CREATE INDEX IF NOT EXISTS idx_alert_event_time ON alert_event(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_alert_event_device ON alert_event(device_sn, created_at DESC); + +-- ==================== 调度指令 ==================== + +CREATE TABLE IF NOT EXISTS dispatch_command ( + id BIGSERIAL PRIMARY KEY, + command_no VARCHAR(30) UNIQUE NOT NULL, + command_type VARCHAR(30) NOT NULL, -- routine/emergency + command_title VARCHAR(200) NOT NULL, + command_content TEXT NOT NULL, + source VARCHAR(50), -- 来源: 平台/SCADA/上级 + issuer_id BIGINT, + issuer_name VARCHAR(50), + target_type VARCHAR(30), -- station/device_area/person + target_ids JSONB, -- 目标站点/设备ID数组 + priority VARCHAR(10) DEFAULT 'normal', -- low/normal/high/urgent + status VARCHAR(20) DEFAULT 'draft', -- draft/issued/received/executing/completed/rejected + issued_at TIMESTAMP, + received_at TIMESTAMP, + completed_at TIMESTAMP, + result_remark TEXT, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE dispatch_command IS '调度指令表'; + +CREATE TABLE IF NOT EXISTS dispatch_log ( + id BIGSERIAL PRIMARY KEY, + command_id BIGINT REFERENCES dispatch_command(id), + action VARCHAR(20) NOT NULL, -- issue/receive/execute/complete/reject + operator_id BIGINT, + operator_name VARCHAR(50), + remark TEXT, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE dispatch_log IS '调度指令操作日志'; + +-- ==================== 值班管理 ==================== + +CREATE TABLE IF NOT EXISTS duty_schedule ( + id BIGSERIAL PRIMARY KEY, + schedule_name VARCHAR(100), + dept_id BIGINT REFERENCES sys_dept(id), + station_id BIGINT, + shift_type VARCHAR(20), -- day/night/full + start_time TIME NOT NULL, + end_time TIME NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE duty_schedule IS '值班班次表'; + +CREATE TABLE IF NOT EXISTS duty_record ( + id BIGSERIAL PRIMARY KEY, + schedule_id BIGINT REFERENCES duty_schedule(id), + duty_date DATE NOT NULL, + user_id BIGINT REFERENCES sys_user(id), + shift_type VARCHAR(20), + on_duty_at TIMESTAMP, + off_duty_at TIMESTAMP, + status VARCHAR(20) DEFAULT 'pending', -- pending/on_duty/off_duty + handover_remark TEXT, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE duty_record IS '值班记录表'; + +-- ==================== 巡检管理 ==================== + +CREATE TABLE IF NOT EXISTS patrol_route ( + id BIGSERIAL PRIMARY KEY, + route_name VARCHAR(100) NOT NULL, + area VARCHAR(50) NOT NULL, + route_points JSONB, -- [{seq, lng, lat, name, device_ids}] + estim_duration INT, -- 预计时长(分钟) + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE patrol_route IS '巡检路线表'; + +CREATE TABLE IF NOT EXISTS patrol_task ( + id BIGSERIAL PRIMARY KEY, + route_id BIGINT REFERENCES patrol_route(id), + assignee_id BIGINT REFERENCES sys_user(id), + task_name VARCHAR(200), + task_date DATE NOT NULL, + plan_start TIMESTAMP, + plan_end TIMESTAMP, + actual_start TIMESTAMP, + actual_end TIMESTAMP, + status VARCHAR(20) DEFAULT 'pending', -- pending/in_progress/completed/expired/cancelled + distance DECIMAL(8,2), -- 实际巡检距离(km) + score INT, -- 评分 + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE patrol_task IS '巡检任务表'; +CREATE INDEX IF NOT EXISTS idx_patrol_task_date ON patrol_task(task_date); + +CREATE TABLE IF NOT EXISTS patrol_record ( + id BIGSERIAL PRIMARY KEY, + task_id BIGINT REFERENCES patrol_task(id), + point_seq INT, + device_id BIGINT, + device_name VARCHAR(200), + check_items JSONB, -- [{item, result, value, photo_urls, remark}] + gps_lng DOUBLE PRECISION, + gps_lat DOUBLE PRECISION, + geom GEOMETRY(Point, 4326), + record_time TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE patrol_record IS '巡检记录表'; +CREATE INDEX IF NOT EXISTS idx_patrol_record_geom ON patrol_record USING GIST(geom); + +-- ==================== 水质检测 ==================== + +CREATE TABLE IF NOT EXISTS water_quality_record ( + id BIGSERIAL PRIMARY KEY, + test_type VARCHAR(20) NOT NULL, -- manual/auto + test_point VARCHAR(100) NOT NULL, -- 检测点位名称 + point_type VARCHAR(20), -- source_water/plant_out/pipe_network/terminal + area VARCHAR(50), + station_id BIGINT, + lng DOUBLE PRECISION, + lat DOUBLE PRECISION, + test_date DATE NOT NULL, + test_time TIME, + tester VARCHAR(50), + turbidity DECIMAL(8,2), -- 浊度(NTU) + ph DECIMAL(5,2), + residual_chlorine DECIMAL(8,2), -- 余氯(mg/L) + total_chlorine DECIMAL(8,2), + cod DECIMAL(8,2), -- 化学需氧量(mg/L) + ammonia DECIMAL(8,2), -- 氨氮(mg/L) + total_bacteria INT, -- 总大肠菌群(CFU/100mL) + extra_data JSONB, -- 扩展指标 + is_qualified SMALLINT, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE water_quality_record IS '水质检测记录表'; +CREATE INDEX IF NOT EXISTS idx_wq_record_date ON water_quality_record(test_date); +CREATE INDEX IF NOT EXISTS idx_wq_record_area ON water_quality_record(area); diff --git a/db/postgresql/V1__revenue.sql b/db/postgresql/V1__revenue.sql new file mode 100644 index 00000000..9250fdc3 --- /dev/null +++ b/db/postgresql/V1__revenue.sql @@ -0,0 +1,164 @@ +-- ============================================= +-- 智慧水务管理系统 - 营业收费相关 DDL +-- 版本: V1 +-- ============================================= + +-- 用水户 +CREATE TABLE IF NOT EXISTS rev_customer ( + id BIGSERIAL PRIMARY KEY, + customer_no VARCHAR(30) UNIQUE NOT NULL, + customer_name VARCHAR(100) NOT NULL, + customer_type VARCHAR(20) NOT NULL, -- residential/business/enterprise/institution + area VARCHAR(50) NOT NULL, + address VARCHAR(300), + phone VARCHAR(20), + id_card VARCHAR(18), + contract_no VARCHAR(50), + open_date DATE, + meter_count INT DEFAULT 0, + status VARCHAR(20) DEFAULT 'active', -- active/suspended/closed + remark VARCHAR(500), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_customer IS '用水户表'; + +-- 水价阶梯 +CREATE TABLE IF NOT EXISTS rev_water_price ( + id BIGSERIAL PRIMARY KEY, + customer_type VARCHAR(20) NOT NULL, -- residential/business/enterprise/institution + tier_no INT NOT NULL, -- 第几阶梯 + range_start DECIMAL(12,2) DEFAULT 0, -- 起始水量(立方米) + range_end DECIMAL(12,2), -- 结束水量(null=无上限) + water_price DECIMAL(10,4) NOT NULL, -- 水价(元/立方米) + sewage_price DECIMAL(10,4) DEFAULT 0, -- 污水处理费 + effective_date DATE NOT NULL, + status SMALLINT DEFAULT 1, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_water_price IS '水价阶梯配置表'; + +-- 水表档案 +CREATE TABLE IF NOT EXISTS rev_meter ( + id BIGSERIAL PRIMARY KEY, + meter_no VARCHAR(50) UNIQUE NOT NULL, + customer_id BIGINT REFERENCES rev_customer(id), + device_id BIGINT, -- 关联 IoT 设备 + caliber VARCHAR(10), -- DN15/DN20/DN40/DN80+ + meter_type VARCHAR(20), -- mechanical/ultrasonic/electromagnetic + manufacturer VARCHAR(100), + max_reading DECIMAL(10,2) DEFAULT 99999, + initial_reading DECIMAL(10,2) DEFAULT 0, + current_reading DECIMAL(10,2) DEFAULT 0, + install_date DATE, + install_address VARCHAR(300), + status VARCHAR(20) DEFAULT 'active', -- active/dismantled/scrapped/repaired/warehouse + remark VARCHAR(500), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_meter IS '水表档案表'; + +-- 水表操作记录(全生命周期) +CREATE TABLE IF NOT EXISTS rev_meter_log ( + id BIGSERIAL PRIMARY KEY, + meter_id BIGINT REFERENCES rev_meter(id), + operation_type VARCHAR(30) NOT NULL, -- install/dismantle/repair/change/scrap/calibrate/refurbish + old_reading DECIMAL(10,2), + new_reading DECIMAL(10,2), + new_meter_no VARCHAR(50), + operator_id BIGINT, + operator_name VARCHAR(50), + photos JSONB, -- 现场照片URL数组 + remark VARCHAR(500), + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_meter_log IS '水表操作记录表(全生命周期)'; + +-- 抄表记录 +CREATE TABLE IF NOT EXISTS rev_reading ( + id BIGSERIAL PRIMARY KEY, + meter_id BIGINT REFERENCES rev_meter(id), + reading_date DATE NOT NULL, + reading_period VARCHAR(10), -- 2026-06 + prev_reading DECIMAL(10,2), + curr_reading DECIMAL(10,2), + consumption DECIMAL(10,2), + read_type VARCHAR(20) DEFAULT 'manual', -- manual/remote/estimate + reader_id BIGINT, + reader_name VARCHAR(50), + photo_urls JSONB, -- 拍照图片URL数组 + abnormal_flag SMALLINT DEFAULT 0, -- 0:正常 1:异常 + verified SMALLINT DEFAULT 0, -- 0:未审核 1:已审核 + verified_by BIGINT, + verified_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_reading IS '抄表记录表'; +CREATE INDEX IF NOT EXISTS idx_reading_period ON rev_reading(reading_period); + +-- 水费账单 +CREATE TABLE IF NOT EXISTS rev_bill ( + id BIGSERIAL PRIMARY KEY, + bill_no VARCHAR(30) UNIQUE NOT NULL, + customer_id BIGINT REFERENCES rev_customer(id), + meter_id BIGINT REFERENCES rev_meter(id), + reading_id BIGINT REFERENCES rev_reading(id), + bill_period VARCHAR(10) NOT NULL, -- 2026-06 + prev_reading DECIMAL(10,2), + curr_reading DECIMAL(10,2), + consumption DECIMAL(10,2), + water_fee DECIMAL(10,2), + sewage_fee DECIMAL(10,2), + other_fee DECIMAL(10,2) DEFAULT 0, -- 污水处理费/垃圾处理费等 + total_fee DECIMAL(10,2), + paid_fee DECIMAL(10,2) DEFAULT 0, + discount_fee DECIMAL(10,2) DEFAULT 0, + status VARCHAR(20) DEFAULT 'pending', -- pending/partial/paid/overdue/cancelled + due_date DATE, + paid_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_bill IS '水费账单表'; +CREATE INDEX IF NOT EXISTS idx_bill_customer ON rev_bill(customer_id, bill_period); +CREATE INDEX IF NOT EXISTS idx_bill_status ON rev_bill(status); + +-- 缴费记录 +CREATE TABLE IF NOT EXISTS rev_payment ( + id BIGSERIAL PRIMARY KEY, + bill_id BIGINT REFERENCES rev_bill(id), + customer_id BIGINT REFERENCES rev_customer(id), + payment_no VARCHAR(50) UNIQUE NOT NULL, + amount DECIMAL(10,2) NOT NULL, + pay_method VARCHAR(20), -- wechat/alipay/cash/bank_transfer/pos + pay_channel VARCHAR(30), -- counter/app/wechat_mini/third_party + transaction_id VARCHAR(100), + operator_id BIGINT, + remark VARCHAR(500), + paid_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_payment IS '缴费记录表'; + +-- 报装申请 +CREATE TABLE IF NOT EXISTS rev_install ( + id BIGSERIAL PRIMARY KEY, + application_no VARCHAR(30) UNIQUE NOT NULL, + applicant_name VARCHAR(50) NOT NULL, + applicant_phone VARCHAR(20) NOT NULL, + applicant_id_card VARCHAR(18), + area VARCHAR(50), + address VARCHAR(300), + customer_type VARCHAR(20), -- residential/business/enterprise + caliber VARCHAR(10), -- 申请管径 + purpose VARCHAR(200), + status VARCHAR(20) DEFAULT 'pre_apply', -- pre_apply/engineering/pending_review/approved/rejected/completed + survey_date DATE, + survey_result TEXT, + approved_by BIGINT, + approved_at TIMESTAMP, + completed_at TIMESTAMP, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); +COMMENT ON TABLE rev_install IS '报装申请表'; diff --git a/db/seed/V1__seed.sql b/db/seed/V1__seed.sql new file mode 100644 index 00000000..4514bbe0 --- /dev/null +++ b/db/seed/V1__seed.sql @@ -0,0 +1,79 @@ +-- ============================================= +-- 种子数据:管理员 + 基础角色 + 字典 +-- ============================================= + +-- 插入部门 +INSERT INTO sys_dept (id, parent_id, dept_name, dept_type, sort_order) VALUES +(1, NULL, '精河县水利局', 'water_bureau', 1), +(2, NULL, '安阜清源水务公司', 'water_company', 2), +(3, 2, '一体化水厂', 'water_company', 1), +(4, 2, '精芒片区', 'water_company', 2), +(5, 2, '八家户片区', 'water_company', 3), +(6, 2, '托里片区', 'water_company', 4), +(7, 2, '大镇阿合其片区', 'water_company', 5), +(8, 2, '托托片区', 'water_company', 6), +(9, 2, '运维中心', 'ops', 7) +ON CONFLICT DO NOTHING; + +-- 重置序列 +SELECT setval('sys_dept_id_seq', (SELECT MAX(id) FROM sys_dept)); + +-- 插入角色 +INSERT INTO sys_role (id, role_name, role_key, role_sort, data_scope, remark) VALUES +(1, '系统管理员', 'admin', 1, 'ALL', '最高权限,管理全部功能'), +(2, '水务分管领导', 'leader', 2, 'ALL', '查看全部数据、BI看板'), +(3, '水务业务管理人员', 'manager', 3, 'DEPT', '调度、营收、水质管理'), +(4, '现场运维操作人员', 'operator', 4, 'DEPT', '巡检、值班、工单处理'), +(5, '系统技术维护人员', 'tech', 5, 'DEPT', '配置管理、设备维护') +ON CONFLICT (role_key) DO NOTHING; + +-- 插入管理员用户 (密码: admin123, BCrypt加密) +INSERT INTO sys_user (id, dept_id, username, password, real_name, role_type, status) VALUES +(1, 1, 'admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iAt6Z5Eh', '系统管理员', 'admin', 1) +ON CONFLICT (username) DO NOTHING; + +-- 管理员关联角色 +INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1) ON CONFLICT DO NOTHING; + +-- 插入字典类型 +INSERT INTO sys_dict_type (id, dict_key, dict_name) VALUES +(1, 'device_type', '设备类型'), +(2, 'area', '片区'), +(3, 'customer_type', '用水户类型'), +(4, 'alert_level', '报警等级'), +(5, 'meter_status', '水表状态') +ON CONFLICT (dict_key) DO NOTHING; + +-- 插入字典数据 +INSERT INTO sys_dict_data (dict_type_id, dict_label, dict_value, sort_order) VALUES +-- 设备类型 +(1, '流量计', 'flow_meter', 1), +(1, '压力传感器', 'pressure_sensor', 2), +(1, '电动阀门', 'valve', 3), +(1, '水质监测仪', 'water_quality', 4), +(1, '液位计', 'level_sensor', 5), +(1, '水泵', 'pump', 6), +-- 片区 +(2, '一体化水厂', '一体化水厂', 1), +(2, '精芒片区', '精芒片区', 2), +(2, '八家户片区', '八家户片区', 3), +(2, '托里片区', '托里片区', 4), +(2, '大镇阿合其片区', '大镇阿合其片区', 5), +(2, '托托片区', '托托片区', 6), +-- 用水户类型 +(3, '居民', 'residential', 1), +(3, '商业', 'business', 2), +(3, '企业', 'enterprise', 3), +(3, '事业单位', 'institution', 4), +-- 报警等级 +(4, '提示', 'info', 1), +(4, '一般', 'warning', 2), +(4, '严重', 'critical', 3), +(4, '紧急', 'emergency', 4), +-- 水表状态 +(5, '正常使用', 'active', 1), +(5, '已拆除', 'dismantled', 2), +(5, '已报废', 'scrapped', 3), +(5, '维修中', 'repaired', 4), +(5, '库存', 'warehouse', 5) +ON CONFLICT DO NOTHING; diff --git a/db/tdengine/init.sql b/db/tdengine/init.sql new file mode 100644 index 00000000..835dfeea --- /dev/null +++ b/db/tdengine/init.sql @@ -0,0 +1,68 @@ +-- ============================================= +-- 智慧水务管理系统 - TDengine 建表脚本 +-- 用于物联网设备遥测数据存储 +-- ============================================= + +-- 创建数据库 +CREATE DATABASE IF NOT EXISTS water_iot + KEEP 365 + DURATION 30 + BUFFER 256 + PAGES 256 + PAGESIZE 4 + WAL_LEVEL 1; + +USE water_iot; + +-- 遥测数据超级表 +CREATE STABLE IF NOT EXISTS iot_telemetry ( + ts TIMESTAMP, + device_sn NCHAR(100), + metric_key NCHAR(50), + metric_value DOUBLE, + quality TINYINT +) TAGS ( + device_type NCHAR(30), + area NCHAR(50), + station NCHAR(100) +); + +-- 按设备类型创建子表(模板,实际运行时动态创建) +CREATE TABLE IF NOT EXISTS telemetry_flow_meter USING iot_telemetry TAGS('flow_meter', 'default', 'default'); +CREATE TABLE IF NOT EXISTS telemetry_pressure USING iot_telemetry TAGS('pressure_sensor', 'default', 'default'); +CREATE TABLE IF NOT EXISTS telemetry_valve USING iot_telemetry TAGS('valve', 'default', 'default'); +CREATE TABLE IF NOT EXISTS telemetry_water_quality USING iot_telemetry TAGS('water_quality', 'default', 'default'); +CREATE TABLE IF NOT EXISTS telemetry_level USING iot_telemetry TAGS('level_sensor', 'default', 'default'); +CREATE TABLE IF NOT EXISTS telemetry_pump USING iot_telemetry TAGS('pump', 'default', 'default'); + +-- 设备事件超级表 +CREATE STABLE IF NOT EXISTS iot_device_event ( + ts TIMESTAMP, + device_sn NCHAR(100), + event_type NCHAR(30), + event_data NCHAR(2000) +) TAGS ( + device_type NCHAR(30), + area NCHAR(50) +); + +-- 小时聚合超级表(自动聚合) +CREATE STABLE IF NOT EXISTS iot_telemetry_hourly ( + ts TIMESTAMP, + device_sn NCHAR(100), + metric_key NCHAR(50), + min_value DOUBLE, + max_value DOUBLE, + avg_value DOUBLE, + count_value INT +) TAGS ( + device_type NCHAR(30), + area NCHAR(50) +); + +-- 日聚合视图(示例) +-- CREATE STREAM IF NOT EXISTS hourly_stream INTO iot_telemetry_hourly AS +-- SELECT _wstart as ts, device_sn, metric_key, +-- MIN(metric_value), MAX(metric_value), AVG(metric_value), COUNT(*) +-- FROM iot_telemetry +-- INTERVAL(1h);