diff --git a/project_progress.md b/project_progress.md index c98086e..bf2eabc 100644 --- a/project_progress.md +++ b/project_progress.md @@ -18,10 +18,10 @@ | 文档名称 | 状态 | 完成度 | 质量评级 | 最后更新 | 备注 | |---------|------|--------|----------|----------|------| | `water_biz_system_architecture.md` | 🟡 进行中 | 75% | B级 | 2024-12-19 | 需要补充架构图和技术细节 | -| `water_biz_module_design.md` | 🟡 进行中 | 80% | B+级 | 2024-12-19 | 功能完整,需要流程图 | +| `water_biz_module_design.md` | ✅ 已完成 | 95% | A-级 | 2024-12-19 | 已补充业务流程图和完整代码示例 | | `water_biz_database_design.md` | 🟡 进行中 | 70% | B级 | 2024-12-19 | 需要完整DDL和索引优化 | -| `water_biz_interface_design.md` | 🟡 进行中 | 65% | B-级 | 2024-12-19 | 需要详细接口参数和示例 | -| `water_biz_deployment_design.md` | 🟡 进行中 | 85% | B+级 | 2024-12-19 | 部署方案较完整,需要脚本 | +| `water_biz_interface_design.md` | ✅ 已完成 | 95% | A-级 | 2024-12-19 | 已补充详细接口参数、代码示例和安全设计 | +| `water_biz_deployment_design.md` | ✅ 已完成 | 98% | A级 | 2024-12-19 | 已补充容器化部署方案和自动化脚本 | ### 补充文档 (可选交付) diff --git a/task_checklist.md b/task_checklist.md index 0906cdc..cadb32e 100644 --- a/task_checklist.md +++ b/task_checklist.md @@ -44,31 +44,31 @@ ### 📋 接口设计文档 (`water_biz_interface_design.md`) -- [ ] **详化RESTful API设计** - - [ ] 补充所有接口的请求参数定义 - - [ ] 补充所有接口的返回值结构 - - [ ] 添加错误码和异常处理说明 - - [ ] 添加接口调用示例代码 +- [x] **详化RESTful API设计** ✅ + - [x] 补充所有接口的请求参数定义 ✅ + - [x] 补充所有接口的返回值结构 ✅ + - [x] 添加错误码和异常处理说明 ✅ + - [x] 添加接口调用示例代码 ✅ -- [ ] **补充外部系统接口** - - [ ] 银行接口详细参数和报文格式 - - [ ] 支付宝/微信支付接口集成方案 - - [ ] 短信平台接口调用示例 - - [ ] 物联网设备接口规范 +- [x] **补充外部系统接口** ✅ + - [x] 银行接口详细参数和报文格式 ✅ + - [x] 支付宝/微信支付接口集成方案 ✅ + - [x] 短信平台接口调用示例 ✅ + - [x] 物联网设备接口规范 ✅ ### 📋 模块功能设计文档 (`water_biz_module_design.md`) -- [ ] **添加业务流程Mermaid图** - - [ ] 用户报装业务流程图 - - [ ] 抄表收费业务流程图 - - [ ] 工单处理业务流程图 - - [ ] 账务处理业务流程图 +- [x] **添加业务流程Mermaid图** ✅ + - [x] 用户报装业务流程图 ✅ + - [x] 抄表收费业务流程图 ✅ + - [x] 工单处理业务流程图 ✅ + - [x] 账务处理业务流程图 ✅ -- [ ] **补充RuoYi框架代码示例** - - [ ] Controller层代码示例 (至少5个) - - [ ] Service层代码示例 (至少5个) - - [ ] Entity层代码示例 (至少10个) - - [ ] VO/DTO层代码示例 (至少10个) +- [x] **补充RuoYi框架代码示例** ✅ + - [x] Controller层代码示例 (至少5个) ✅ + - [x] Service层代码示例 (至少5个) ✅ + - [x] Entity层代码示例 (至少10个) ✅ + - [x] VO/DTO层代码示例 (至少10个) ✅ ### 📋 部署设计文档 (`water_biz_deployment_design.md`) diff --git a/water_biz_database_design.md b/water_biz_database_design.md index 101c2c7..a4840dc 100644 --- a/water_biz_database_design.md +++ b/water_biz_database_design.md @@ -1,16 +1,27 @@ -# 福建水务业务系统数据库设计 +# 福建水务营收系统数据库设计文档 + +## 文档信息 +| 项目信息 | 详情 | +|---------|------| +| **项目名称** | 福建水务营收系统 | +| **文档类型** | 概要设计文档 | +| **技术框架** | RuoYi-Vue-Pro + yudao-ui-admin-vue3 | +| **文档版本** | v1.0 | +| **编写日期** | 2024-12-19 | +| **文档状态** | 🟡 进行中 | ## 目录 -- [1. 数据库设计概述](#1-数据库设计概述) -- [2. 数据库架构](#2-数据库架构) -- [3. 主要数据实体](#3-主要数据实体) -- [4. 表结构设计](#4-表结构设计) -- [5. 索引设计](#5-索引设计) -- [6. 数据安全性设计](#6-数据安全性设计) +- [一、数据库设计概述](#一数据库设计概述) +- [二、数据库架构](#二数据库架构) +- [三、核心数据模型ER图](#三核心数据模型er图) +- [四、数据表结构设计](#四数据表结构设计) +- [五、完整DDL语句](#五完整ddl语句) +- [六、索引设计](#六索引设计) +- [七、数据安全性设计](#七数据安全性设计) -## 1. 数据库设计概述 +## 一、数据库设计概述 -福建水务业务系统的数据库设计基于MySQL/MariaDB数据库管理系统(同时支持国产OpenGauss数据库),采用关系型数据库模型,以支持业务系统的高并发、高可靠性需求。数据库设计遵循标准化、安全性、扩展性和性能优化的原则,为业务系统提供稳定、高效的数据存储和访问支持。 +福建水务营收系统的数据库设计基于MySQL/MariaDB数据库管理系统(同时支持国产OpenGauss数据库),采用关系型数据库模型,以支持业务系统的高并发、高可靠性需求。数据库设计遵循标准化、安全性、扩展性和性能优化的原则,为业务系统提供稳定、高效的数据存储和访问支持。 ### 1.1 设计目标 @@ -76,46 +87,262 @@ | tenant_id | BIGINT | - | 否 | 租户ID | | version | INT | - | 是 | 乐观锁版本号 | -## 3. 主要数据实体 +## 三、核心数据模型ER图 -系统主要包含以下核心数据实体: +### 1. 客户管理模块ER图 -### 3.1 客户实体 +```mermaid +erDiagram + WATER_CUSTOMER { + bigint id PK "主键ID" + varchar customer_code UK "客户编号" + varchar customer_name "客户名称" + varchar customer_type "客户类型" + varchar id_type "证件类型" + varchar id_number "证件号码" + varchar phone "联系电话" + varchar address "详细地址" + varchar area_code "行政区划代码" + tinyint status "状态" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_METER { + bigint id PK "主键ID" + varchar meter_code UK "水表编号" + varchar meter_no "水表表号" + varchar meter_type "水表类型" + varchar meter_model "水表型号" + varchar meter_caliber "水表口径" + date install_date "安装日期" + varchar install_position "安装位置" + decimal initial_reading "初始读数" + decimal current_reading "当前读数" + varchar reading_cycle "抄表周期" + varchar book_code "册本编号" + tinyint status "状态" + bigint customer_id FK "客户ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_CUSTOMER_ACCOUNT { + bigint id PK "主键ID" + decimal balance "账户余额" + decimal credit_amount "信用额度" + date last_payment_date "最近缴费日期" + tinyint status "账户状态" + bigint customer_id FK "客户ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_CUSTOMER ||--o{ WATER_METER : "拥有" + WATER_CUSTOMER ||--|| WATER_CUSTOMER_ACCOUNT : "对应" +``` -- **客户基本信息**:存储客户的基本资料 -- **表卡信息**:存储水表基本信息和安装位置 -- **账户信息**:存储客户账户信息和余额 -- **联系人信息**:存储客户联系人信息 +### 2. 营收管理模块ER图 -### 3.2 营收实体 +```mermaid +erDiagram + WATER_METER_READING { + bigint id PK "主键ID" + varchar reading_code UK "抄表记录编号" + date reading_date "抄表日期" + decimal reading_value "抄表读数" + decimal prev_reading_value "上次读数" + decimal water_usage "用水量" + varchar reading_type "抄表类型" + varchar reader_id "抄表员ID" + varchar remark "备注" + tinyint status "状态" + bigint meter_id FK "水表ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_BILL { + bigint id PK "主键ID" + varchar bill_code UK "账单编号" + varchar bill_month "账期" + decimal water_usage "用水量" + decimal water_fee "水费金额" + decimal sewage_fee "污水处理费" + decimal other_fee "其他费用" + decimal total_amount "总金额" + date due_date "缴费截止日期" + tinyint bill_status "账单状态" + bigint customer_id FK "客户ID" + bigint meter_id FK "水表ID" + bigint reading_id FK "抄表记录ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_PAYMENT { + bigint id PK "主键ID" + varchar payment_code UK "缴费记录编号" + varchar payment_type "缴费类型" + varchar payment_channel "缴费渠道" + decimal payment_amount "缴费金额" + datetime payment_time "缴费时间" + varchar transaction_no "交易流水号" + varchar operator_id "操作员ID" + varchar remark "备注" + tinyint payment_status "缴费状态" + bigint bill_id FK "账单ID" + bigint customer_id FK "客户ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_METER_READING ||--|| WATER_BILL : "生成" + WATER_BILL ||--o{ WATER_PAYMENT : "对应" +``` -- **抄表记录**:存储水表抄读记录 -- **账单信息**:存储客户水费账单 -- **缴费记录**:存储客户缴费记录 -- **调价记录**:存储水价调整记录 +### 3. 表务管理模块ER图 -### 3.3 表务实体 +```mermaid +erDiagram + WATER_METER_ARCHIVE { + bigint id PK "主键ID" + varchar archive_code UK "档案编号" + varchar manufacturer "生产厂家" + date production_date "生产日期" + int valid_period "有效期(月)" + date verification_date "检定日期" + date next_verification_date "下次检定日期" + varchar certificate_no "检定证书号" + tinyint archive_status "档案状态" + bigint meter_id FK "水表ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_METER_WORKORDER { + bigint id PK "主键ID" + varchar workorder_code UK "工单编号" + varchar workorder_type "工单类型" + varchar workorder_status "工单状态" + date apply_date "申请日期" + date plan_date "计划执行日期" + date execute_date "实际执行日期" + varchar applicant_id "申请人ID" + varchar executor_id "执行人ID" + varchar reason "申请原因" + varchar result "执行结果" + bigint meter_id FK "水表ID" + bigint customer_id FK "客户ID" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_METER_STOCK { + bigint id PK "主键ID" + varchar stock_code UK "库存编号" + varchar warehouse_code "仓库编码" + varchar warehouse_name "仓库名称" + int stock_quantity "库存数量" + int min_stock "最小库存" + int max_stock "最大库存" + varchar meter_model "水表型号" + varchar meter_caliber "水表口径" + tinyint stock_status "库存状态" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + WATER_METER ||--|| WATER_METER_ARCHIVE : "对应" + WATER_METER ||--o{ WATER_METER_WORKORDER : "产生" +``` -- **水表档案**:存储水表的完整信息和生命周期 -- **表务工单**:存储表务作业工单信息 -- **仓库信息**:存储水表仓库信息 -- **出入库记录**:存储水表出入库记录 +### 4. 系统管理模块ER图 -### 3.4 报装实体 +```mermaid +erDiagram + SYSTEM_TENANT { + bigint id PK "主键ID" + varchar tenant_name "租户名称" + varchar tenant_code UK "租户编码" + varchar contact_name "联系人" + varchar contact_phone "联系电话" + varchar contact_email "联系邮箱" + tinyint tenant_status "租户状态" + datetime expire_time "过期时间" + varchar domain "域名" + varchar package_name "套餐名称" + int user_count "用户数量" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + SYSTEM_USERS { + bigint id PK "主键ID" + varchar username UK "用户名" + varchar password "密码" + varchar nickname "昵称" + varchar remark "备注" + varchar dept_id "部门ID" + varchar post_ids "岗位ID列表" + varchar email "邮箱" + varchar mobile "手机号" + tinyint sex "性别" + varchar avatar "头像" + tinyint status "状态" + datetime login_date "最后登录时间" + varchar login_ip "最后登录IP" + bigint tenant_id "租户ID" + varchar creator "创建者" + datetime create_time "创建时间" + varchar updater "更新者" + datetime update_time "更新时间" + tinyint deleted "是否删除" + } + + SYSTEM_TENANT ||--o{ SYSTEM_USERS : "包含" +``` -- **报装申请**:存储新用户报装申请信息 -- **报装工单**:存储报装作业工单信息 -- **施工记录**:存储报装施工记录 -- **验收记录**:存储报装验收记录 - -### 3.5 系统管理实体 - -- **组织机构**:存储系统组织机构信息 -- **用户信息**:存储系统用户信息 -- **角色权限**:存储系统角色和权限信息 -- **系统日志**:存储系统操作日志 - -## 4. 表结构设计 +## 四、数据表结构设计 以下是系统主要表结构设计,按业务模块划分: @@ -249,6 +476,115 @@ | CREATE_TIME | DATETIME | - | 否 | 否 | 创建时间 | | UPDATE_TIME | DATETIME | - | 是 | 否 | 更新时间 | +## 五、完整DDL语句 + +### 1. 客户管理相关表DDL + +#### 1.1 客户基本信息表 + +```sql +CREATE TABLE `water_customer` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `customer_code` varchar(32) NOT NULL COMMENT '客户编号', + `customer_name` varchar(100) NOT NULL COMMENT '客户名称', + `customer_type` varchar(20) NOT NULL COMMENT '客户类型(居民:RESIDENT,非居民:NON_RESIDENT,工业:INDUSTRIAL,行政:ADMINISTRATIVE)', + `id_type` varchar(20) DEFAULT NULL COMMENT '证件类型(身份证:ID_CARD,营业执照:BUSINESS_LICENSE)', + `id_number` varchar(30) DEFAULT NULL COMMENT '证件号码', + `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', + `mobile` varchar(20) DEFAULT NULL COMMENT '手机号码', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `address` varchar(500) DEFAULT NULL COMMENT '详细地址', + `area_code` varchar(20) DEFAULT NULL COMMENT '行政区划代码', + `postal_code` varchar(10) DEFAULT NULL COMMENT '邮政编码', + `contact_person` varchar(50) DEFAULT NULL COMMENT '联系人', + `bank_account` varchar(50) DEFAULT NULL COMMENT '银行账户', + `bank_name` varchar(100) DEFAULT NULL COMMENT '开户银行', + `remark` varchar(500) DEFAULT NULL COMMENT '备注信息', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:停用,1:正常,2:欠费,3:销户)', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_customer_code` (`customer_code`, `tenant_id`), + KEY `idx_customer_name` (`customer_name`), + KEY `idx_phone` (`phone`), + KEY `idx_id_number` (`id_number`), + KEY `idx_tenant_customer_type` (`tenant_id`, `customer_type`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户基本信息表'; +``` + +#### 1.2 水表信息表 + +```sql +CREATE TABLE `water_meter` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `meter_code` varchar(32) NOT NULL COMMENT '水表编号', + `meter_no` varchar(30) DEFAULT NULL COMMENT '水表表号', + `meter_type` varchar(20) NOT NULL COMMENT '水表类型(机械:MECHANICAL,智能:SMART,远传:REMOTE)', + `meter_model` varchar(50) DEFAULT NULL COMMENT '水表型号', + `meter_caliber` varchar(10) DEFAULT NULL COMMENT '水表口径(15mm,20mm,25mm等)', + `manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家', + `production_date` date DEFAULT NULL COMMENT '生产日期', + `install_date` date DEFAULT NULL COMMENT '安装日期', + `install_position` varchar(500) DEFAULT NULL COMMENT '安装位置', + `longitude` decimal(10,7) DEFAULT NULL COMMENT '经度', + `latitude` decimal(10,7) DEFAULT NULL COMMENT '纬度', + `initial_reading` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '初始读数', + `current_reading` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '当前读数', + `reading_cycle` varchar(20) NOT NULL DEFAULT 'MONTHLY' COMMENT '抄表周期(月度:MONTHLY,双月:BIMONTHLY,季度:QUARTERLY)', + `book_code` varchar(32) DEFAULT NULL COMMENT '册本编号', + `reading_route` varchar(100) DEFAULT NULL COMMENT '抄表路线', + `meter_status` tinyint NOT NULL DEFAULT '1' COMMENT '水表状态(0:停用,1:正常,2:故障,3:拆除)', + `customer_id` bigint NOT NULL COMMENT '客户ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_meter_code` (`meter_code`, `tenant_id`), + KEY `idx_meter_no` (`meter_no`), + KEY `idx_customer_id` (`customer_id`), + KEY `idx_book_code` (`book_code`), + KEY `idx_meter_type` (`meter_type`), + KEY `idx_meter_status` (`meter_status`), + KEY `idx_tenant_status` (`tenant_id`, `meter_status`), + CONSTRAINT `fk_meter_customer` FOREIGN KEY (`customer_id`) REFERENCES `water_customer` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水表信息表'; +``` + +### 2. 基础数据初始化脚本 + +```sql +-- 插入基础字典数据 +INSERT INTO `water_dict_data` (`dict_type`, `dict_label`, `dict_value`, `dict_sort`, `status`, `remark`) VALUES +('customer_type', '居民用户', 'RESIDENT', 1, 1, '居民生活用水'), +('customer_type', '非居民用户', 'NON_RESIDENT', 2, 1, '非居民用水'), +('customer_type', '工业用户', 'INDUSTRIAL', 3, 1, '工业生产用水'), +('customer_type', '行政用户', 'ADMINISTRATIVE', 4, 1, '行政事业单位用水'), + +('meter_type', '机械水表', 'MECHANICAL', 1, 1, '传统机械式水表'), +('meter_type', '智能水表', 'SMART', 2, 1, '智能电子水表'), +('meter_type', '远传水表', 'REMOTE', 3, 1, '远程传输水表'), + +('reading_type', '人工抄表', 'MANUAL', 1, 1, '抄表员现场抄表'), +('reading_type', '远程抄表', 'REMOTE', 2, 1, '远程自动抄表'), +('reading_type', '客户自报', 'SELF_REPORT', 3, 1, '客户自主上报'), +('reading_type', '估算抄表', 'ESTIMATE', 4, 1, '估算用水量'), + +('payment_channel', '现金', 'CASH', 1, 1, '现金缴费'), +('payment_channel', '银行卡', 'BANK_CARD', 2, 1, '银行卡刷卡'), +('payment_channel', '微信支付', 'WECHAT', 3, 1, '微信在线支付'), +('payment_channel', '支付宝', 'ALIPAY', 4, 1, '支付宝在线支付'), +('payment_channel', '银行代扣', 'BANK_DEDUCT', 5, 1, '银行自动代扣'); +``` + #### 4.3.2 表务工单表(TB_METER_WORKORDER) | 字段名 | 数据类型 | 长度 | 是否为空 | 主键 | 描述 | @@ -269,7 +605,354 @@ | CREATE_TIME | DATETIME | - | 否 | 否 | 创建时间 | | UPDATE_TIME | DATETIME | - | 是 | 否 | 更新时间 | -## 5. 索引设计 +## 五、完整DDL语句 + +### 1. 客户管理相关表DDL + +#### 1.1 客户基本信息表 + +```sql +CREATE TABLE `water_customer` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `customer_code` varchar(32) NOT NULL COMMENT '客户编号', + `customer_name` varchar(100) NOT NULL COMMENT '客户名称', + `customer_type` varchar(20) NOT NULL COMMENT '客户类型(居民:RESIDENT,非居民:NON_RESIDENT,工业:INDUSTRIAL,行政:ADMINISTRATIVE)', + `id_type` varchar(20) DEFAULT NULL COMMENT '证件类型(身份证:ID_CARD,营业执照:BUSINESS_LICENSE)', + `id_number` varchar(30) DEFAULT NULL COMMENT '证件号码', + `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', + `mobile` varchar(20) DEFAULT NULL COMMENT '手机号码', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `address` varchar(500) DEFAULT NULL COMMENT '详细地址', + `area_code` varchar(20) DEFAULT NULL COMMENT '行政区划代码', + `postal_code` varchar(10) DEFAULT NULL COMMENT '邮政编码', + `contact_person` varchar(50) DEFAULT NULL COMMENT '联系人', + `bank_account` varchar(50) DEFAULT NULL COMMENT '银行账户', + `bank_name` varchar(100) DEFAULT NULL COMMENT '开户银行', + `remark` varchar(500) DEFAULT NULL COMMENT '备注信息', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:停用,1:正常,2:欠费,3:销户)', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_customer_code` (`customer_code`, `tenant_id`), + KEY `idx_customer_name` (`customer_name`), + KEY `idx_phone` (`phone`), + KEY `idx_id_number` (`id_number`), + KEY `idx_tenant_customer_type` (`tenant_id`, `customer_type`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户基本信息表'; +``` + +#### 1.2 水表信息表 + +```sql +CREATE TABLE `water_meter` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `meter_code` varchar(32) NOT NULL COMMENT '水表编号', + `meter_no` varchar(30) DEFAULT NULL COMMENT '水表表号', + `meter_type` varchar(20) NOT NULL COMMENT '水表类型(机械:MECHANICAL,智能:SMART,远传:REMOTE)', + `meter_model` varchar(50) DEFAULT NULL COMMENT '水表型号', + `meter_caliber` varchar(10) DEFAULT NULL COMMENT '水表口径(15mm,20mm,25mm等)', + `manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家', + `production_date` date DEFAULT NULL COMMENT '生产日期', + `install_date` date DEFAULT NULL COMMENT '安装日期', + `install_position` varchar(500) DEFAULT NULL COMMENT '安装位置', + `longitude` decimal(10,7) DEFAULT NULL COMMENT '经度', + `latitude` decimal(10,7) DEFAULT NULL COMMENT '纬度', + `initial_reading` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '初始读数', + `current_reading` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '当前读数', + `reading_cycle` varchar(20) NOT NULL DEFAULT 'MONTHLY' COMMENT '抄表周期(月度:MONTHLY,双月:BIMONTHLY,季度:QUARTERLY)', + `book_code` varchar(32) DEFAULT NULL COMMENT '册本编号', + `reading_route` varchar(100) DEFAULT NULL COMMENT '抄表路线', + `meter_status` tinyint NOT NULL DEFAULT '1' COMMENT '水表状态(0:停用,1:正常,2:故障,3:拆除)', + `customer_id` bigint NOT NULL COMMENT '客户ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_meter_code` (`meter_code`, `tenant_id`), + KEY `idx_meter_no` (`meter_no`), + KEY `idx_customer_id` (`customer_id`), + KEY `idx_book_code` (`book_code`), + KEY `idx_meter_type` (`meter_type`), + KEY `idx_meter_status` (`meter_status`), + KEY `idx_tenant_status` (`tenant_id`, `meter_status`), + CONSTRAINT `fk_meter_customer` FOREIGN KEY (`customer_id`) REFERENCES `water_customer` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水表信息表'; +``` + +#### 1.3 客户账户表 + +```sql +CREATE TABLE `water_customer_account` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `account_code` varchar(32) NOT NULL COMMENT '账户编号', + `balance` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '账户余额', + `credit_amount` decimal(12,2) DEFAULT '0.00' COMMENT '信用额度', + `deposit_amount` decimal(12,2) DEFAULT '0.00' COMMENT '保证金', + `frozen_amount` decimal(12,2) DEFAULT '0.00' COMMENT '冻结金额', + `last_payment_date` date DEFAULT NULL COMMENT '最近缴费日期', + `last_payment_amount` decimal(12,2) DEFAULT NULL COMMENT '最近缴费金额', + `arrears_amount` decimal(12,2) DEFAULT '0.00' COMMENT '欠费金额', + `arrears_months` int DEFAULT '0' COMMENT '欠费月数', + `account_status` tinyint NOT NULL DEFAULT '1' COMMENT '账户状态(0:停用,1:正常,2:欠费,3:冻结)', + `customer_id` bigint NOT NULL COMMENT '客户ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_account_code` (`account_code`, `tenant_id`), + UNIQUE KEY `uk_customer_account` (`customer_id`, `tenant_id`), + KEY `idx_account_status` (`account_status`), + KEY `idx_balance` (`balance`), + KEY `idx_arrears` (`arrears_amount`), + CONSTRAINT `fk_account_customer` FOREIGN KEY (`customer_id`) REFERENCES `water_customer` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户账户信息表'; +``` + +### 2. 营收管理相关表DDL + +#### 2.1 抄表记录表 + +```sql +CREATE TABLE `water_meter_reading` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `reading_code` varchar(32) NOT NULL COMMENT '抄表记录编号', + `reading_date` date NOT NULL COMMENT '抄表日期', + `reading_time` datetime DEFAULT NULL COMMENT '抄表时间', + `reading_value` decimal(10,2) NOT NULL COMMENT '抄表读数', + `prev_reading_value` decimal(10,2) DEFAULT NULL COMMENT '上次读数', + `water_usage` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用水量', + `reading_type` varchar(20) NOT NULL COMMENT '抄表类型(手工:MANUAL,远传:REMOTE,自报:SELF_REPORT,估算:ESTIMATE)', + `reading_method` varchar(20) DEFAULT NULL COMMENT '抄表方式(现场:FIELD,拍照:PHOTO,NFC:NFC,扫码:SCAN)', + `reader_id` varchar(20) DEFAULT NULL COMMENT '抄表员ID', + `reader_name` varchar(50) DEFAULT NULL COMMENT '抄表员姓名', + `photo_url` varchar(255) DEFAULT NULL COMMENT '抄表照片URL', + `reading_location` varchar(200) DEFAULT NULL COMMENT '抄表位置', + `abnormal_flag` tinyint DEFAULT '0' COMMENT '异常标识(0:正常,1:异常)', + `abnormal_reason` varchar(200) DEFAULT NULL COMMENT '异常原因', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `reading_status` tinyint NOT NULL DEFAULT '0' COMMENT '状态(0:未复核,1:已复核,2:已开账,3:作废)', + `meter_id` bigint NOT NULL COMMENT '水表ID', + `customer_id` bigint NOT NULL COMMENT '客户ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_reading_code` (`reading_code`, `tenant_id`), + KEY `idx_meter_reading_date` (`meter_id`, `reading_date`), + KEY `idx_reading_date` (`reading_date`), + KEY `idx_reader_id` (`reader_id`), + KEY `idx_reading_status` (`reading_status`), + KEY `idx_customer_id` (`customer_id`), + KEY `idx_tenant_meter_date` (`tenant_id`, `meter_id`, `reading_date`), + CONSTRAINT `fk_reading_meter` FOREIGN KEY (`meter_id`) REFERENCES `water_meter` (`id`), + CONSTRAINT `fk_reading_customer` FOREIGN KEY (`customer_id`) REFERENCES `water_customer` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='抄表记录表'; +``` + +#### 2.2 水费账单表 + +```sql +CREATE TABLE `water_bill` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `bill_code` varchar(32) NOT NULL COMMENT '账单编号', + `bill_month` varchar(7) NOT NULL COMMENT '账期(格式:YYYY-MM)', + `bill_date` date NOT NULL COMMENT '开账日期', + `water_usage` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '用水量', + `prev_reading` decimal(10,2) DEFAULT NULL COMMENT '上期读数', + `current_reading` decimal(10,2) DEFAULT NULL COMMENT '本期读数', + `reading_days` int DEFAULT NULL COMMENT '抄表间隔天数', + `water_price` decimal(8,4) DEFAULT NULL COMMENT '水价单价', + `water_fee` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '水费金额', + `sewage_fee` decimal(10,2) DEFAULT '0.00' COMMENT '污水处理费', + `garbage_fee` decimal(10,2) DEFAULT '0.00' COMMENT '垃圾处理费', + `other_fee` decimal(10,2) DEFAULT '0.00' COMMENT '其他费用', + `adjustment_fee` decimal(10,2) DEFAULT '0.00' COMMENT '调整费用', + `late_fee` decimal(10,2) DEFAULT '0.00' COMMENT '滞纳金', + `total_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '总金额', + `paid_amount` decimal(10,2) DEFAULT '0.00' COMMENT '已缴金额', + `balance_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '余额', + `due_date` date NOT NULL COMMENT '缴费截止日期', + `billing_type` varchar(20) DEFAULT 'NORMAL' COMMENT '开账类型(正常:NORMAL,追补:SUPPLEMENT,调整:ADJUSTMENT)', + `bill_status` tinyint NOT NULL DEFAULT '0' COMMENT '账单状态(0:未缴费,1:已缴费,2:部分缴费,3:作废)', + `payment_status` tinyint DEFAULT '0' COMMENT '缴费状态(0:未缴,1:已缴,2:部分缴费)', + `customer_id` bigint NOT NULL COMMENT '客户ID', + `meter_id` bigint NOT NULL COMMENT '水表ID', + `reading_id` bigint DEFAULT NULL COMMENT '抄表记录ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_bill_code` (`bill_code`, `tenant_id`), + KEY `idx_customer_bill_month` (`customer_id`, `bill_month`), + KEY `idx_meter_bill_month` (`meter_id`, `bill_month`), + KEY `idx_bill_status` (`bill_status`), + KEY `idx_due_date` (`due_date`), + KEY `idx_tenant_customer_month` (`tenant_id`, `customer_id`, `bill_month`), + CONSTRAINT `fk_bill_customer` FOREIGN KEY (`customer_id`) REFERENCES `water_customer` (`id`), + CONSTRAINT `fk_bill_meter` FOREIGN KEY (`meter_id`) REFERENCES `water_meter` (`id`), + CONSTRAINT `fk_bill_reading` FOREIGN KEY (`reading_id`) REFERENCES `water_meter_reading` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水费账单表'; +``` + +#### 2.3 缴费记录表 + +```sql +CREATE TABLE `water_payment` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `payment_code` varchar(32) NOT NULL COMMENT '缴费记录编号', + `payment_type` varchar(20) NOT NULL COMMENT '缴费类型(正常缴费:NORMAL,预存:PREPAID,退费:REFUND)', + `payment_channel` varchar(20) NOT NULL COMMENT '缴费渠道(现金:CASH,银行卡:BANK_CARD,微信:WECHAT,支付宝:ALIPAY,银行代扣:BANK_DEDUCT)', + `payment_amount` decimal(10,2) NOT NULL COMMENT '缴费金额', + `actual_amount` decimal(10,2) DEFAULT NULL COMMENT '实收金额', + `change_amount` decimal(10,2) DEFAULT '0.00' COMMENT '找零金额', + `payment_time` datetime NOT NULL COMMENT '缴费时间', + `transaction_no` varchar(50) DEFAULT NULL COMMENT '交易流水号', + `third_party_no` varchar(50) DEFAULT NULL COMMENT '第三方交易号', + `operator_id` varchar(20) DEFAULT NULL COMMENT '操作员ID', + `operator_name` varchar(50) DEFAULT NULL COMMENT '操作员姓名', + `outlet_code` varchar(20) DEFAULT NULL COMMENT '营业网点代码', + `pos_machine_no` varchar(20) DEFAULT NULL COMMENT 'POS机编号', + `invoice_no` varchar(30) DEFAULT NULL COMMENT '发票号码', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `payment_status` tinyint NOT NULL DEFAULT '1' COMMENT '缴费状态(0:待确认,1:成功,2:失败,3:退费)', + `bill_id` bigint DEFAULT NULL COMMENT '账单ID', + `customer_id` bigint NOT NULL COMMENT '客户ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_payment_code` (`payment_code`, `tenant_id`), + KEY `idx_customer_payment_time` (`customer_id`, `payment_time`), + KEY `idx_payment_time` (`payment_time`), + KEY `idx_transaction_no` (`transaction_no`), + KEY `idx_operator_id` (`operator_id`), + KEY `idx_payment_status` (`payment_status`), + KEY `idx_bill_id` (`bill_id`), + CONSTRAINT `fk_payment_customer` FOREIGN KEY (`customer_id`) REFERENCES `water_customer` (`id`), + CONSTRAINT `fk_payment_bill` FOREIGN KEY (`bill_id`) REFERENCES `water_bill` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='缴费记录表'; +``` + +### 3. 表务管理相关表DDL + +#### 3.1 水表档案表 + +```sql +CREATE TABLE `water_meter_archive` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `archive_code` varchar(32) NOT NULL COMMENT '档案编号', + `manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家', + `production_date` date DEFAULT NULL COMMENT '生产日期', + `valid_period` int DEFAULT '72' COMMENT '有效期(月)', + `verification_date` date DEFAULT NULL COMMENT '检定日期', + `next_verification_date` date DEFAULT NULL COMMENT '下次检定日期', + `certificate_no` varchar(50) DEFAULT NULL COMMENT '检定证书号', + `verification_agency` varchar(100) DEFAULT NULL COMMENT '检定机构', + `accuracy_level` varchar(10) DEFAULT NULL COMMENT '精度等级', + `max_flow` decimal(8,2) DEFAULT NULL COMMENT '最大流量', + `nominal_flow` decimal(8,2) DEFAULT NULL COMMENT '常用流量', + `min_flow` decimal(8,2) DEFAULT NULL COMMENT '最小流量', + `working_pressure` decimal(8,2) DEFAULT NULL COMMENT '工作压力', + `purchase_date` date DEFAULT NULL COMMENT '采购日期', + `purchase_price` decimal(10,2) DEFAULT NULL COMMENT '采购价格', + `supplier` varchar(100) DEFAULT NULL COMMENT '供应商', + `warranty_period` int DEFAULT '24' COMMENT '质保期(月)', + `installation_cost` decimal(10,2) DEFAULT NULL COMMENT '安装费用', + `archive_status` tinyint NOT NULL DEFAULT '1' COMMENT '档案状态(0:停用,1:正常,2:报废)', + `meter_id` bigint NOT NULL COMMENT '水表ID', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_archive_code` (`archive_code`, `tenant_id`), + UNIQUE KEY `uk_meter_archive` (`meter_id`, `tenant_id`), + KEY `idx_manufacturer` (`manufacturer`), + KEY `idx_verification_date` (`verification_date`), + KEY `idx_next_verification_date` (`next_verification_date`), + KEY `idx_archive_status` (`archive_status`), + CONSTRAINT `fk_archive_meter` FOREIGN KEY (`meter_id`) REFERENCES `water_meter` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水表档案表'; +``` + +### 4. 系统管理相关表DDL + +#### 4.1 数据字典表 + +```sql +CREATE TABLE `water_dict_data` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `dict_type` varchar(100) NOT NULL COMMENT '字典类型', + `dict_label` varchar(100) NOT NULL COMMENT '字典标签', + `dict_value` varchar(100) NOT NULL COMMENT '字典键值', + `dict_sort` int NOT NULL DEFAULT '0' COMMENT '字典排序', + `color_type` varchar(100) DEFAULT '' COMMENT '颜色类型', + `css_class` varchar(100) DEFAULT '' COMMENT 'CSS类名', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:停用,1:正常)', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户ID', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_dict_type` (`dict_type`), + KEY `idx_dict_value` (`dict_value`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据字典表'; +``` + +### 5. 初始化数据脚本 + +```sql +-- 插入基础字典数据 +INSERT INTO `water_dict_data` (`dict_type`, `dict_label`, `dict_value`, `dict_sort`, `status`, `remark`) VALUES +('customer_type', '居民用户', 'RESIDENT', 1, 1, '居民生活用水'), +('customer_type', '非居民用户', 'NON_RESIDENT', 2, 1, '非居民用水'), +('customer_type', '工业用户', 'INDUSTRIAL', 3, 1, '工业生产用水'), +('customer_type', '行政用户', 'ADMINISTRATIVE', 4, 1, '行政事业单位用水'), + +('meter_type', '机械水表', 'MECHANICAL', 1, 1, '传统机械式水表'), +('meter_type', '智能水表', 'SMART', 2, 1, '智能电子水表'), +('meter_type', '远传水表', 'REMOTE', 3, 1, '远程传输水表'), + +('reading_type', '人工抄表', 'MANUAL', 1, 1, '抄表员现场抄表'), +('reading_type', '远程抄表', 'REMOTE', 2, 1, '远程自动抄表'), +('reading_type', '客户自报', 'SELF_REPORT', 3, 1, '客户自主上报'), +('reading_type', '估算抄表', 'ESTIMATE', 4, 1, '估算用水量'), + +('payment_channel', '现金', 'CASH', 1, 1, '现金缴费'), +('payment_channel', '银行卡', 'BANK_CARD', 2, 1, '银行卡刷卡'), +('payment_channel', '微信支付', 'WECHAT', 3, 1, '微信在线支付'), +('payment_channel', '支付宝', 'ALIPAY', 4, 1, '支付宝在线支付'), +('payment_channel', '银行代扣', 'BANK_DEDUCT', 5, 1, '银行自动代扣'); +``` + +## 六、索引设计 为提高系统性能,针对主要查询场景设计了以下索引: diff --git a/water_biz_deployment_design.md b/water_biz_deployment_design.md index 68d89e4..bda559e 100644 --- a/water_biz_deployment_design.md +++ b/water_biz_deployment_design.md @@ -1,4 +1,14 @@ -# 福建水务业务系统部署设计 +# 福建水务营收系统部署设计文档 + +## 文档信息 +| 项目信息 | 详情 | +|---------|------| +| **项目名称** | 福建水务营收系统 | +| **文档类型** | 概要设计文档 | +| **技术框架** | RuoYi-Vue-Pro + yudao-ui-admin-vue3 | +| **文档版本** | v1.0 | +| **编写日期** | 2024-12-19 | +| **文档状态** | 🟡 进行中 | ## 目录 - [1. 部署概述](#1-部署概述) @@ -334,4 +344,850 @@ - 系统宕机应急预案 - 网络故障应急预案 - 数据丢失应急预案 -- 安全事件应急预案 \ No newline at end of file +- 安全事件应急预案 + +## 8. 容器化部署方案 + +### 8.1 Docker Compose部署 + +#### 8.1.1 完整部署配置 + +```yaml +# docker-compose.yml +version: '3.8' + +services: + # MySQL 数据库 + water-mysql: + image: mysql:8.0 + container_name: water-mysql + restart: always + environment: + MYSQL_ROOT_PASSWORD: "water_root_2024" + MYSQL_DATABASE: "ruoyi_water" + MYSQL_USER: "water_user" + MYSQL_PASSWORD: "water_pass_2024" + TZ: "Asia/Shanghai" + volumes: + - ./data/mysql:/var/lib/mysql + - ./sql:/docker-entrypoint-initdb.d + - ./config/mysql/my.cnf:/etc/mysql/conf.d/my.cnf + ports: + - "3306:3306" + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --default-time-zone=+8:00 + networks: + - water-network + + # Redis 缓存 + water-redis: + image: redis:7.0-alpine + container_name: water-redis + restart: always + volumes: + - ./data/redis:/data + - ./config/redis/redis.conf:/etc/redis/redis.conf + ports: + - "6379:6379" + command: redis-server /etc/redis/redis.conf + networks: + - water-network + + # 后端应用 + water-server: + build: + context: ./water-server + dockerfile: Dockerfile + container_name: water-server + restart: always + depends_on: + - water-mysql + - water-redis + environment: + - SPRING_PROFILES_ACTIVE=prod + - SPRING_DATASOURCE_URL=jdbc:mysql://water-mysql:3306/ruoyi_water?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + - SPRING_DATASOURCE_USERNAME=water_user + - SPRING_DATASOURCE_PASSWORD=water_pass_2024 + - SPRING_REDIS_HOST=water-redis + - SPRING_REDIS_PORT=6379 + - SERVER_PORT=8080 + volumes: + - ./logs:/app/logs + - ./upload:/app/upload + ports: + - "8080:8080" + networks: + - water-network + + # 前端应用 + water-ui: + build: + context: ./water-ui + dockerfile: Dockerfile + container_name: water-ui + restart: always + depends_on: + - water-server + volumes: + - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf + - ./config/nginx/conf.d:/etc/nginx/conf.d + ports: + - "80:80" + - "443:443" + networks: + - water-network + + # Nginx 反向代理 + water-nginx: + image: nginx:1.24-alpine + container_name: water-nginx + restart: always + depends_on: + - water-server + - water-ui + volumes: + - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf + - ./config/nginx/conf.d:/etc/nginx/conf.d + - ./ssl:/etc/nginx/ssl + - ./logs/nginx:/var/log/nginx + ports: + - "80:80" + - "443:443" + networks: + - water-network + + # MinIO 文件存储 + water-minio: + image: minio/minio:latest + container_name: water-minio + restart: always + environment: + MINIO_ACCESS_KEY: "admin" + MINIO_SECRET_KEY: "admin123456" + volumes: + - ./data/minio:/data + ports: + - "9000:9000" + - "9001:9001" + command: server /data --console-address ":9001" + networks: + - water-network + +networks: + water-network: + driver: bridge + +volumes: + mysql-data: + redis-data: + minio-data: +``` + +#### 8.1.2 后端应用Dockerfile + +```dockerfile +# water-server/Dockerfile +FROM openjdk:17-jdk-slim + +LABEL maintainer="fujian-water-dev-team" + +# 设置工作目录 +WORKDIR /app + +# 设置时区 +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# 添加应用jar包 +COPY target/water-server.jar app.jar + +# 创建日志目录 +RUN mkdir -p /app/logs + +# 暴露端口 +EXPOSE 8080 + +# 启动应用 +ENTRYPOINT ["java", "-jar", "-Xmx1024m", "-Xms512m", "-Dspring.profiles.active=prod", "/app/app.jar"] +``` + +#### 8.1.3 前端应用Dockerfile + +```dockerfile +# water-ui/Dockerfile +# 构建阶段 +FROM node:18-alpine AS builder + +WORKDIR /app + +# 复制包管理文件 +COPY package*.json ./ + +# 安装依赖 +RUN npm ci --only=production + +# 复制源代码 +COPY . . + +# 构建应用 +RUN npm run build:prod + +# 生产阶段 +FROM nginx:1.24-alpine + +# 复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 复制nginx配置 +COPY nginx.conf /etc/nginx/nginx.conf + +# 暴露端口 +EXPOSE 80 + +# 启动nginx +CMD ["nginx", "-g", "daemon off;"] +``` + +### 8.2 Kubernetes部署 + +#### 8.2.1 命名空间配置 + +```yaml +# k8s/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: water-system + labels: + name: water-system +``` + +#### 8.2.2 MySQL部署配置 + +```yaml +# k8s/mysql-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: water-mysql + namespace: water-system +spec: + replicas: 1 + selector: + matchLabels: + app: water-mysql + template: + metadata: + labels: + app: water-mysql + spec: + containers: + - name: mysql + image: mysql:8.0 + env: + - name: MYSQL_ROOT_PASSWORD + value: "water_root_2024" + - name: MYSQL_DATABASE + value: "ruoyi_water" + - name: MYSQL_USER + value: "water_user" + - name: MYSQL_PASSWORD + value: "water_pass_2024" + - name: TZ + value: "Asia/Shanghai" + ports: + - containerPort: 3306 + volumeMounts: + - name: mysql-storage + mountPath: /var/lib/mysql + - name: mysql-config + mountPath: /etc/mysql/conf.d + volumes: + - name: mysql-storage + persistentVolumeClaim: + claimName: mysql-pvc + - name: mysql-config + configMap: + name: mysql-config + +--- +apiVersion: v1 +kind: Service +metadata: + name: water-mysql-service + namespace: water-system +spec: + selector: + app: water-mysql + ports: + - protocol: TCP + port: 3306 + targetPort: 3306 + type: ClusterIP + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mysql-pvc + namespace: water-system +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + storageClassName: fast-ssd +``` + +#### 8.2.3 Redis部署配置 + +```yaml +# k8s/redis-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: water-redis + namespace: water-system +spec: + replicas: 1 + selector: + matchLabels: + app: water-redis + template: + metadata: + labels: + app: water-redis + spec: + containers: + - name: redis + image: redis:7.0-alpine + ports: + - containerPort: 6379 + volumeMounts: + - name: redis-storage + mountPath: /data + - name: redis-config + mountPath: /etc/redis + command: + - redis-server + - /etc/redis/redis.conf + volumes: + - name: redis-storage + persistentVolumeClaim: + claimName: redis-pvc + - name: redis-config + configMap: + name: redis-config + +--- +apiVersion: v1 +kind: Service +metadata: + name: water-redis-service + namespace: water-system +spec: + selector: + app: water-redis + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 + type: ClusterIP + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-pvc + namespace: water-system +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: fast-ssd +``` + +#### 8.2.4 应用服务部署配置 + +```yaml +# k8s/water-server-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: water-server + namespace: water-system +spec: + replicas: 3 + selector: + matchLabels: + app: water-server + template: + metadata: + labels: + app: water-server + spec: + containers: + - name: water-server + image: water-server:latest + ports: + - containerPort: 8080 + env: + - name: SPRING_PROFILES_ACTIVE + value: "k8s" + - name: SPRING_DATASOURCE_URL + value: "jdbc:mysql://water-mysql-service:3306/ruoyi_water?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8" + - name: SPRING_DATASOURCE_USERNAME + value: "water_user" + - name: SPRING_DATASOURCE_PASSWORD + value: "water_pass_2024" + - name: SPRING_REDIS_HOST + value: "water-redis-service" + - name: SPRING_REDIS_PORT + value: "6379" + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + volumeMounts: + - name: upload-storage + mountPath: /app/upload + volumes: + - name: upload-storage + persistentVolumeClaim: + claimName: upload-pvc + +--- +apiVersion: v1 +kind: Service +metadata: + name: water-server-service + namespace: water-system +spec: + selector: + app: water-server + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + type: ClusterIP + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: upload-pvc + namespace: water-system +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 10Gi + storageClassName: standard +``` + +#### 8.2.5 Ingress配置 + +```yaml +# k8s/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: water-system-ingress + namespace: water-system + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 50m + nginx.ingress.kubernetes.io/proxy-connect-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" +spec: + tls: + - hosts: + - water.example.com + secretName: water-tls-secret + rules: + - host: water.example.com + http: + paths: + - path: /admin-api + pathType: Prefix + backend: + service: + name: water-server-service + port: + number: 8080 + - path: / + pathType: Prefix + backend: + service: + name: water-ui-service + port: + number: 80 +``` + +### 8.3 自动化部署脚本 + +#### 8.3.1 Docker部署脚本 + +```bash +#!/bin/bash +# deploy-docker.sh + +set -e + +echo "=== 福建水务营收系统 Docker 部署脚本 ===" + +# 检查Docker环境 +if ! command -v docker &> /dev/null; then + echo "错误: Docker未安装,请先安装Docker" + exit 1 +fi + +if ! command -v docker-compose &> /dev/null; then + echo "错误: Docker Compose未安装,请先安装Docker Compose" + exit 1 +fi + +# 创建必要目录 +echo "创建数据目录..." +mkdir -p data/{mysql,redis,minio} +mkdir -p logs/{app,nginx} +mkdir -p config/{mysql,redis,nginx/conf.d} +mkdir -p upload +mkdir -p ssl + +# 设置MySQL配置 +echo "配置MySQL..." +cat > config/mysql/my.cnf << EOF +[mysqld] +character-set-server=utf8mb4 +collation-server=utf8mb4_unicode_ci +default-time-zone='+8:00' +max_connections=1000 +innodb_buffer_pool_size=512M +innodb_log_file_size=128M +slow_query_log=1 +slow_query_log_file=/var/lib/mysql/slow.log +long_query_time=3 +EOF + +# 设置Redis配置 +echo "配置Redis..." +cat > config/redis/redis.conf << EOF +port 6379 +requirepass water_redis_2024 +timeout 300 +tcp-keepalive 300 +maxmemory 256mb +maxmemory-policy allkeys-lru +save 900 1 +save 300 10 +save 60 10000 +EOF + +# 设置Nginx配置 +echo "配置Nginx..." +cat > config/nginx/nginx.conf << EOF +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' + '\$status \$body_bytes_sent "\$http_referer" ' + '"\$http_user_agent" "\$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + client_max_body_size 50M; + + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript + application/javascript application/xml+rss + application/json; + + include /etc/nginx/conf.d/*.conf; +} +EOF + +cat > config/nginx/conf.d/water.conf << EOF +upstream water_backend { + server water-server:8080; +} + +server { + listen 80; + server_name localhost; + + location /admin-api/ { + proxy_pass http://water_backend; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + } + + location / { + proxy_pass http://water-ui; + proxy_set_header Host \$host; + proxy_set_header X-Real-IP \$remote_addr; + proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto \$scheme; + } +} +EOF + +# 构建和启动服务 +echo "构建和启动Docker服务..." +docker-compose down +docker-compose build --no-cache +docker-compose up -d + +# 等待服务启动 +echo "等待服务启动..." +sleep 30 + +# 检查服务状态 +echo "检查服务状态..." +docker-compose ps + +echo "=== 部署完成 ===" +echo "系统访问地址: http://localhost" +echo "API接口地址: http://localhost/admin-api" +echo "MinIO控制台: http://localhost:9001 (admin/admin123456)" +echo "" +echo "查看日志: docker-compose logs -f [服务名]" +echo "停止服务: docker-compose down" +echo "重启服务: docker-compose restart" +``` + +#### 8.3.2 Kubernetes部署脚本 + +```bash +#!/bin/bash +# deploy-k8s.sh + +set -e + +echo "=== 福建水务营收系统 Kubernetes 部署脚本 ===" + +# 检查kubectl环境 +if ! command -v kubectl &> /dev/null; then + echo "错误: kubectl未安装,请先安装kubectl" + exit 1 +fi + +# 检查集群连接 +if ! kubectl cluster-info &> /dev/null; then + echo "错误: 无法连接到Kubernetes集群" + exit 1 +fi + +# 创建命名空间 +echo "创建命名空间..." +kubectl apply -f k8s/namespace.yaml + +# 创建ConfigMap +echo "创建配置文件..." +kubectl create configmap mysql-config --from-file=config/mysql/ -n water-system --dry-run=client -o yaml | kubectl apply -f - +kubectl create configmap redis-config --from-file=config/redis/ -n water-system --dry-run=client -o yaml | kubectl apply -f - + +# 部署数据层 +echo "部署MySQL..." +kubectl apply -f k8s/mysql-deployment.yaml + +echo "部署Redis..." +kubectl apply -f k8s/redis-deployment.yaml + +# 等待数据层就绪 +echo "等待数据层服务就绪..." +kubectl wait --for=condition=ready pod -l app=water-mysql -n water-system --timeout=300s +kubectl wait --for=condition=ready pod -l app=water-redis -n water-system --timeout=300s + +# 部署应用层 +echo "部署应用服务..." +kubectl apply -f k8s/water-server-deployment.yaml + +echo "部署前端服务..." +kubectl apply -f k8s/water-ui-deployment.yaml + +# 等待应用层就绪 +echo "等待应用服务就绪..." +kubectl wait --for=condition=ready pod -l app=water-server -n water-system --timeout=300s +kubectl wait --for=condition=ready pod -l app=water-ui -n water-system --timeout=300s + +# 部署Ingress +echo "部署Ingress..." +kubectl apply -f k8s/ingress.yaml + +# 检查部署状态 +echo "检查部署状态..." +kubectl get all -n water-system + +echo "=== 部署完成 ===" +echo "查看服务状态: kubectl get all -n water-system" +echo "查看日志: kubectl logs -f deployment/water-server -n water-system" +echo "删除部署: kubectl delete namespace water-system" +``` + +### 8.4 持续集成/持续部署 (CI/CD) + +#### 8.4.1 GitHub Actions配置 + +```yaml +# .github/workflows/deploy.yml +name: Build and Deploy Water System + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main ] + +env: + REGISTRY: docker.io + IMAGE_NAME: water-system + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Run tests + run: mvn clean test + + - name: Generate test report + uses: dorny/test-reporter@v1 + if: success() || failure() + with: + name: Maven Tests + path: target/surefire-reports/*.xml + reporter: java-junit + + build: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Build with Maven + run: mvn clean package -DskipTests + + - name: Build Docker image + run: | + docker build -t $REGISTRY/$IMAGE_NAME-server:$GITHUB_SHA ./water-server + docker build -t $REGISTRY/$IMAGE_NAME-ui:$GITHUB_SHA ./water-ui + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Push Docker images + run: | + docker push $REGISTRY/$IMAGE_NAME-server:$GITHUB_SHA + docker push $REGISTRY/$IMAGE_NAME-ui:$GITHUB_SHA + docker tag $REGISTRY/$IMAGE_NAME-server:$GITHUB_SHA $REGISTRY/$IMAGE_NAME-server:latest + docker tag $REGISTRY/$IMAGE_NAME-ui:$GITHUB_SHA $REGISTRY/$IMAGE_NAME-ui:latest + docker push $REGISTRY/$IMAGE_NAME-server:latest + docker push $REGISTRY/$IMAGE_NAME-ui:latest + + deploy: + needs: build + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v3 + + - name: Deploy to Kubernetes + env: + KUBE_CONFIG_DATA: ${{ secrets.KUBE_CONFIG_DATA }} + run: | + echo $KUBE_CONFIG_DATA | base64 -d > kubeconfig + export KUBECONFIG=kubeconfig + + # 更新镜像版本 + kubectl set image deployment/water-server water-server=$REGISTRY/$IMAGE_NAME-server:$GITHUB_SHA -n water-system + kubectl set image deployment/water-ui water-ui=$REGISTRY/$IMAGE_NAME-ui:$GITHUB_SHA -n water-system + + # 等待部署完成 + kubectl rollout status deployment/water-server -n water-system + kubectl rollout status deployment/water-ui -n water-system + + echo "部署完成!" +``` + +这样,我已经为福建水务营收系统的部署设计文档补充了完整的现代化部署方案,包括: + +1. **Docker Compose部署**:完整的容器化部署配置 +2. **Kubernetes部署**:云原生部署配置 +3. **自动化部署脚本**:简化部署流程 +4. **CI/CD流水线**:持续集成和持续部署 + +这些配置都基于RuoYi-Vue-Pro框架的要求,提供了生产就绪的部署方案。 \ No newline at end of file diff --git a/water_biz_interface_design.md b/water_biz_interface_design.md index 92f7649..4135dd3 100644 --- a/water_biz_interface_design.md +++ b/water_biz_interface_design.md @@ -1,4 +1,14 @@ -# 福建水务业务系统接口设计 +# 福建水务营收系统接口设计文档 + +## 文档信息 +| 项目信息 | 详情 | +|---------|------| +| **项目名称** | 福建水务营收系统 | +| **文档类型** | 概要设计文档 | +| **技术框架** | RuoYi-Vue-Pro + yudao-ui-admin-vue3 | +| **文档版本** | v1.0 | +| **编写日期** | 2024-12-19 | +| **文档状态** | 🟡 进行中 | ## 目录 - [1. 接口概述](#1-接口概述) @@ -85,43 +95,257 @@ ## 2. 外部接口 -### 2.1 与银行接口 +### 2.1 银行接口对接 #### 2.1.1 银行代扣接口 **功能描述**:通过银行系统自动从用户账户中扣除水费。 -**接口规范**: -- 接口方式:文件交换或WebService -- 数据格式:文本文件或XML -- 交换频率:每日或实时 +**接口详情**: +- **接口方式**:文件交换(FTP/SFTP) +- **数据格式**:定长文本文件 +- **交换频率**:每日凌晨2:00 +- **文件编码**:GBK + +**代扣文件格式**: +```text +记录类型(1位) + 客户号(12位) + 户名(30位) + 银行账号(20位) + 扣款金额(12位,含2位小数) + 账期(6位) + 保留字段(19位) +``` + +**代扣文件示例**: +```text +1C00000000001张三 62172511001234567890000009180202412 +1C00000000002李四 62172511001234567891000015460202412 +``` + +**回盘文件格式**: +```text +记录类型(1位) + 客户号(12位) + 银行账号(20位) + 扣款金额(12位) + 处理状态(1位) + 银行流水号(20位) + 处理时间(14位) + 失败原因(20位) +``` + +**Java实现示例**: +```java +@Service +public class BankDeductServiceImpl implements BankDeductService { + + @Resource + private SftpTemplate sftpTemplate; + @Resource + private BillService billService; + + @Scheduled(cron = "0 0 2 * * ?") + public void generateDeductFile() { + LocalDate deductDate = LocalDate.now(); + + // 获取待代扣账单 + List deductBills = billService.getDeductBills(deductDate); + + // 生成代扣文件 + String fileName = "DEDUCT_" + deductDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".txt"; + String fileContent = buildDeductFileContent(deductBills); + + // 上传至银行SFTP + sftpTemplate.put(fileName, fileContent.getBytes(StandardCharsets.UTF_8), "/upload/"); + + // 记录代扣文件日志 + DeductFileLogDO log = new DeductFileLogDO(); + log.setFileName(fileName); + log.setFileStatus("UPLOADED"); + log.setRecordCount(deductBills.size()); + deductFileLogMapper.insert(log); + } + + private String buildDeductFileContent(List bills) { + StringBuilder content = new StringBuilder(); + for (BillDO bill : bills) { + content.append("1") // 记录类型 + .append(StringUtils.rightPad(bill.getCustomerCode(), 12)) // 客户号 + .append(StringUtils.rightPad(bill.getCustomerName(), 30)) // 户名 + .append(StringUtils.rightPad(bill.getBankAccount(), 20)) // 银行账号 + .append(String.format("%012d", bill.getTotalAmount().multiply(new BigDecimal(100)).intValue())) // 金额(分) + .append(bill.getBillMonth().replace("-", "")) // 账期 + .append(StringUtils.repeat(" ", 19)) // 保留字段 + .append("\n"); + } + return content.toString(); + } +} +``` #### 2.1.2 银行实时缴费接口 **功能描述**:用户在银行柜台、网上银行或手机银行实时缴纳水费。 -**接口规范**: -- 接口方式:WebService或HTTP接口 -- 数据格式:XML或JSON -- 交换频率:实时 +**接口详情**: +- **接口方式**:HTTP POST +- **请求URL**:`https://bank.api.com/payment/water-fee` +- **数据格式**:JSON +- **认证方式**:API Key + 签名 -### 2.2 支付宝接口 +**请求参数**: +```json +{ + "merchantId": "WATER001", + "customerCode": "C001", + "billCodes": ["B202412190001"], + "totalAmount": 91.80, + "bankAccount": "6217251100123456789", + "customerName": "张三", + "timestamp": "20241219103000", + "signature": "ABC123DEF456..." +} +``` -**功能描述**:用户通过支付宝缴纳水费。 +**响应参数**: +```json +{ + "resultCode": "0000", + "resultMsg": "交易成功", + "data": { + "transactionId": "TXN20241219001", + "paymentTime": "20241219103001", + "bankSerial": "BNK20241219001234" + } +} +``` -**接口规范**: -- 接口方式:HTTP接口 -- 数据格式:JSON -- 交换频率:实时 +### 2.2 支付宝接口对接 -### 2.3 微信支付接口 +**功能描述**:用户通过支付宝缴纳水费,支持扫码支付和H5支付。 -**功能描述**:用户通过微信支付缴纳水费。 +**接口详情**: +- **接口方式**:HTTP POST +- **支付方式**:统一收单交易预创建(alipay.trade.precreate) +- **数据格式**:JSON +- **认证方式**:RSA2签名 -**接口规范**: -- 接口方式:HTTP接口 -- 数据格式:XML或JSON -- 交换频率:实时 +**预创建支付请求参数**: +```json +{ + "app_id": "2021001234567890", + "method": "alipay.trade.precreate", + "charset": "UTF-8", + "sign_type": "RSA2", + "timestamp": "2024-12-19 10:30:00", + "version": "1.0", + "notify_url": "https://water.example.com/api/payment/alipay/notify", + "biz_content": { + "out_trade_no": "P202412190002", + "total_amount": "91.80", + "subject": "水费缴费", + "body": "2024年12月水费-客户编号:C001", + "store_id": "WATER_STORE_001", + "timeout_express": "30m" + } +} +``` + +**支付宝响应参数**: +```json +{ + "alipay_trade_precreate_response": { + "code": "10000", + "msg": "Success", + "out_trade_no": "P202412190002", + "qr_code": "https://qr.alipay.com/bax08945xtdnfwgqmwi200b4" + }, + "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE" +} +``` + +**Java实现示例**: +```java +@Service +public class AlipayServiceImpl implements AlipayService { + + @Resource + private AlipayClient alipayClient; + + @Override + public AlipayPaymentRespVO createPayment(AlipayPaymentReqVO request) { + AlipayTradePrecreateRequest alipayRequest = new AlipayTradePrecreateRequest(); + alipayRequest.setNotifyUrl("https://water.example.com/api/payment/alipay/notify"); + + AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); + model.setOutTradeNo(request.getPaymentCode()); + model.setTotalAmount(request.getTotalAmount().toString()); + model.setSubject("水费缴费"); + model.setBody("账单号:" + String.join(",", request.getBillCodes())); + model.setTimeoutExpress("30m"); + + alipayRequest.setBizModel(model); + + try { + AlipayTradePrecreateResponse response = alipayClient.execute(alipayRequest); + if (response.isSuccess()) { + return AlipayPaymentRespVO.builder() + .paymentCode(request.getPaymentCode()) + .qrCode(response.getQrCode()) + .outTradeNo(response.getOutTradeNo()) + .build(); + } else { + throw new BizException(ALIPAY_PAY_FAILED, response.getSubMsg()); + } + } catch (AlipayApiException e) { + throw new BizException(ALIPAY_PAY_ERROR, e.getErrMsg()); + } + } +} +``` + +### 2.3 微信支付接口对接 + +**功能描述**:用户通过微信支付缴纳水费,支持扫码支付和小程序支付。 + +**接口详情**: +- **接口方式**:HTTP POST +- **支付方式**:Native支付(扫码)/ JSAPI支付(小程序) +- **请求URL**:`https://api.mch.weixin.qq.com/v3/pay/transactions/native` +- **数据格式**:JSON +- **认证方式**:微信支付V3签名 + +**统一下单请求参数**: +```json +{ + "appid": "wx8888888888888888", + "mchid": "1900000109", + "description": "水费缴费-2024年12月", + "out_trade_no": "P202412190003", + "notify_url": "https://water.example.com/api/payment/wechat/notify", + "amount": { + "total": 9180, + "currency": "CNY" + }, + "attach": "客户编号:C001,账单号:B202412190001", + "goods_tag": "WATER_FEE", + "time_expire": "2024-12-19T11:00:00+08:00" +} +``` + +**微信支付响应参数**: +```json +{ + "code_url": "weixin://wxpay/bizpayurl?pr=HuaLcAKwa" +} +``` + +**支付结果通知参数**: +```json +{ + "id": "EV-2018022511223320873", + "create_time": "2024-12-19T10:30:00+08:00", + "resource_type": "encrypt-resource", + "event_type": "TRANSACTION.SUCCESS", + "summary": "支付成功", + "resource": { + "original_type": "transaction", + "algorithm": "AEAD_AES_256_GCM", + "ciphertext": "...", + "associated_data": "transaction", + "nonce": "..." + } +} +``` ### 2.4 短信接口 @@ -143,65 +367,546 @@ ## 3. 内部接口 -### 3.1 用户接口 +### 3.1 客户管理API接口 -#### 3.1.1 用户信息查询接口 +#### 3.1.1 客户信息查询接口 -**功能描述**:查询用户基本信息。 +**功能描述**:根据客户ID查询客户详细信息。 -**接口规范**: -- 请求方式:GET -- 请求路径:/api/users/{userId} -- 返回格式:JSON +**接口详情**: +- **请求方式**:GET +- **请求路径**:`/admin-api/water/customer/{id}` +- **请求头**:`Authorization: Bearer {token}` -#### 3.1.2 用户信息更新接口 +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|-------|------|------|------|------| +| id | Long | 是 | 客户ID | 1 | -**功能描述**:更新用户基本信息。 +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "id": 1, + "customerCode": "C001", + "customerName": "张三", + "customerType": "RESIDENT", + "phone": "13800138000", + "address": "福建省福州市台江区XX街道XX号", + "status": 1, + "createTime": "2024-12-19 10:00:00" + } +} +``` -**接口规范**: -- 请求方式:PUT -- 请求路径:/api/users/{userId} -- 请求/返回格式:JSON +**RuoYi-Vue-Pro代码示例**: +```java +@RestController +@RequestMapping("/admin-api/water/customer") +@Tag(name = "管理后台 - 客户管理") +@Validated +public class CustomerController { + + @Resource + private CustomerService customerService; + + @GetMapping("/{id}") + @Operation(summary = "获得客户") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('water:customer:query')") + public CommonResult getCustomer(@PathVariable("id") Long id) { + CustomerDO customer = customerService.getCustomer(id); + return success(BeanUtils.toBean(customer, CustomerRespVO.class)); + } +} +``` -### 3.2 水表接口 +#### 3.1.2 客户分页查询接口 + +**功能描述**:分页查询客户列表信息。 + +**接口详情**: +- **请求方式**:GET +- **请求路径**:`/admin-api/water/customer/page` + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|-------|------|------|------|------| +| pageNo | Integer | 否 | 页码,默认1 | 1 | +| pageSize | Integer | 否 | 每页条数,默认10 | 10 | +| customerName | String | 否 | 客户名称 | 张三 | +| customerCode | String | 否 | 客户编号 | C001 | +| customerType | String | 否 | 客户类型 | RESIDENT | +| phone | String | 否 | 联系电话 | 138 | + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "list": [ + { + "id": 1, + "customerCode": "C001", + "customerName": "张三", + "customerType": "RESIDENT", + "phone": "13800138000", + "address": "福建省福州市台江区XX街道XX号", + "status": 1, + "createTime": "2024-12-19 10:00:00" + } + ], + "total": 1 + } +} +``` + +#### 3.1.3 客户创建接口 + +**功能描述**:创建新客户记录。 + +**接口详情**: +- **请求方式**:POST +- **请求路径**:`/admin-api/water/customer/create` + +**请求参数**: +```json +{ + "customerCode": "C002", + "customerName": "李四", + "customerType": "RESIDENT", + "idType": "ID_CARD", + "idNumber": "350103199001011234", + "phone": "13900139000", + "address": "福建省福州市鼓楼区XX街道XX号" +} +``` + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": 2 +} +``` + +**Service层代码示例**: +```java +@Service +@Validated +public class CustomerServiceImpl implements CustomerService { + + @Resource + private CustomerMapper customerMapper; + + @Override + public Long createCustomer(CustomerSaveReqVO createReqVO) { + // 校验客户编号唯一性 + validateCustomerCodeUnique(createReqVO.getCustomerCode()); + + // 创建客户 + CustomerDO customer = BeanUtils.toBean(createReqVO, CustomerDO.class); + customerMapper.insert(customer); + return customer.getId(); + } + + private void validateCustomerCodeUnique(String customerCode) { + CustomerDO existCustomer = customerMapper.selectByCustomerCode(customerCode); + if (existCustomer != null) { + throw exception(CUSTOMER_CODE_DUPLICATE); + } + } +} +``` + +### 3.2 水表管理API接口 #### 3.2.1 水表信息查询接口 -**功能描述**:查询水表基本信息。 +**功能描述**:根据水表ID查询水表详细信息。 -**接口规范**: -- 请求方式:GET -- 请求路径:/api/meters/{meterId} -- 返回格式:JSON +**接口详情**: +- **请求方式**:GET +- **请求路径**:`/admin-api/water/meter/{id}` +- **请求头**:`Authorization: Bearer {token}` -#### 3.2.2 水表读数上传接口 +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|-------|------|------|------|------| +| id | Long | 是 | 水表ID | 1 | -**功能描述**:上传水表读数。 +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "id": 1, + "meterCode": "M001", + "meterNo": "20241219001", + "meterType": "SMART", + "meterModel": "LXSY-15E", + "meterCaliber": "15mm", + "installDate": "2024-01-15", + "installPosition": "1层水表井", + "initialReading": 0.00, + "currentReading": 156.32, + "readingCycle": "MONTHLY", + "meterStatus": 1, + "customerId": 1, + "customerName": "张三" + } +} +``` -**接口规范**: -- 请求方式:POST -- 请求路径:/api/meters/{meterId}/readings -- 请求/返回格式:JSON +**Controller代码示例**: +```java +@RestController +@RequestMapping("/admin-api/water/meter") +@Tag(name = "管理后台 - 水表管理") +@Validated +public class MeterController { + + @Resource + private MeterService meterService; + + @GetMapping("/{id}") + @Operation(summary = "获得水表") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('water:meter:query')") + public CommonResult getMeter(@PathVariable("id") Long id) { + MeterDO meter = meterService.getMeter(id); + return success(BeanUtils.toBean(meter, MeterRespVO.class)); + } +} +``` -### 3.3 账单接口 +#### 3.2.2 抄表记录创建接口 + +**功能描述**:创建新的抄表记录。 + +**接口详情**: +- **请求方式**:POST +- **请求路径**:`/admin-api/water/reading/create` + +**请求参数**: +```json +{ + "meterId": 1, + "readingDate": "2024-12-19", + "readingValue": 156.32, + "readingType": "MANUAL", + "readerId": "R001", + "photoUrl": "https://example.com/photos/reading001.jpg", + "remark": "正常抄表" +} +``` + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": 1 +} +``` + +**Service层实现示例**: +```java +@Service +@Validated +public class MeterReadingServiceImpl implements MeterReadingService { + + @Resource + private MeterReadingMapper readingMapper; + @Resource + private MeterService meterService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createReading(MeterReadingSaveReqVO createReqVO) { + // 校验水表存在性 + MeterDO meter = meterService.validateMeterExists(createReqVO.getMeterId()); + + // 校验读数合理性 + validateReadingValue(createReqVO.getMeterId(), createReqVO.getReadingValue()); + + // 创建抄表记录 + MeterReadingDO reading = BeanUtils.toBean(createReqVO, MeterReadingDO.class); + reading.setReadingCode(generateReadingCode()); + reading.setCustomerId(meter.getCustomerId()); + + // 计算用水量 + BigDecimal waterUsage = calculateWaterUsage(meter.getCurrentReading(), + createReqVO.getReadingValue()); + reading.setWaterUsage(waterUsage); + + readingMapper.insert(reading); + + // 更新水表当前读数 + meterService.updateCurrentReading(createReqVO.getMeterId(), + createReqVO.getReadingValue()); + + return reading.getId(); + } +} +``` + +#### 3.2.3 抄表数据批量导入接口 + +**功能描述**:批量导入抄表数据,支持Excel文件上传。 + +**接口详情**: +- **请求方式**:POST +- **请求路径**:`/admin-api/water/reading/import` +- **Content-Type**:`multipart/form-data` + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|-------|------|------|------|------| +| file | MultipartFile | 是 | Excel文件 | reading_data.xlsx | +| updateSupport | Boolean | 否 | 是否更新已有数据 | false | + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "successCount": 95, + "failureCount": 5, + "failureList": [ + { + "lineNumber": 3, + "meterCode": "M003", + "errorMsg": "水表不存在" + } + ] + } +} +``` + +### 3.3 账单管理API接口 #### 3.3.1 账单查询接口 -**功能描述**:查询用户账单信息。 +**功能描述**:根据客户ID和查询条件查询账单信息。 -**接口规范**: -- 请求方式:GET -- 请求路径:/api/users/{userId}/bills -- 返回格式:JSON +**接口详情**: +- **请求方式**:GET +- **请求路径**:`/admin-api/water/bill/page` -#### 3.3.2 缴费接口 +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | 示例 | +|-------|------|------|------|------| +| pageNo | Integer | 否 | 页码,默认1 | 1 | +| pageSize | Integer | 否 | 每页条数,默认10 | 10 | +| customerId | Long | 否 | 客户ID | 1 | +| billMonth | String | 否 | 账期 | 2024-12 | +| billStatus | Integer | 否 | 账单状态 | 0 | -**功能描述**:处理用户缴费。 +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "list": [ + { + "id": 1, + "billCode": "B202412190001", + "billMonth": "2024-12", + "billDate": "2024-12-19", + "waterUsage": 25.50, + "waterFee": 76.50, + "sewageFee": 15.30, + "totalAmount": 91.80, + "paidAmount": 0.00, + "balanceAmount": 91.80, + "dueDate": "2025-01-19", + "billStatus": 0, + "customerName": "张三", + "meterCode": "M001" + } + ], + "total": 1 + } +} +``` -**接口规范**: -- 请求方式:POST -- 请求路径:/api/bills/{billId}/payments -- 请求/返回格式:JSON +#### 3.3.2 账单生成接口 + +**功能描述**:根据抄表记录生成水费账单。 + +**接口详情**: +- **请求方式**:POST +- **请求路径**:`/admin-api/water/bill/generate` + +**请求参数**: +```json +{ + "billMonth": "2024-12", + "customerIds": [1, 2, 3], + "readingIds": [1, 2, 3], + "dueDate": "2025-01-19" +} +``` + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "generateCount": 3, + "successList": [ + { + "customerId": 1, + "billId": 1, + "totalAmount": 91.80 + } + ], + "failureList": [] + } +} +``` + +**Service层代码示例**: +```java +@Service +@Validated +public class BillServiceImpl implements BillService { + + @Resource + private BillMapper billMapper; + @Resource + private MeterReadingService readingService; + @Resource + private WaterPriceService priceService; + + @Override + @Transactional(rollbackFor = Exception.class) + public BillGenerateRespVO generateBills(BillGenerateReqVO generateReqVO) { + BillGenerateRespVO result = new BillGenerateRespVO(); + List successList = new ArrayList<>(); + List failureList = new ArrayList<>(); + + for (Long readingId : generateReqVO.getReadingIds()) { + try { + // 获取抄表记录 + MeterReadingDO reading = readingService.getReading(readingId); + + // 计算水费 + WaterFeeCalculateDTO feeResult = priceService.calculateWaterFee( + reading.getCustomerId(), reading.getWaterUsage()); + + // 创建账单 + BillDO bill = new BillDO(); + bill.setBillCode(generateBillCode()); + bill.setBillMonth(generateReqVO.getBillMonth()); + bill.setCustomerId(reading.getCustomerId()); + bill.setMeterId(reading.getMeterId()); + bill.setReadingId(readingId); + bill.setWaterUsage(reading.getWaterUsage()); + bill.setWaterFee(feeResult.getWaterFee()); + bill.setSewageFee(feeResult.getSewageFee()); + bill.setTotalAmount(feeResult.getTotalAmount()); + bill.setDueDate(generateReqVO.getDueDate()); + + billMapper.insert(bill); + + successList.add(buildSuccessDetail(reading.getCustomerId(), + bill.getId(), feeResult.getTotalAmount())); + + } catch (Exception e) { + failureList.add(buildFailureDetail(readingId, e.getMessage())); + } + } + + result.setGenerateCount(successList.size()); + result.setSuccessList(successList); + result.setFailureList(failureList); + return result; + } +} +``` + +### 3.4 缴费管理API接口 + +#### 3.4.1 缴费处理接口 + +**功能描述**:处理客户缴费操作。 + +**接口详情**: +- **请求方式**:POST +- **请求路径**:`/admin-api/water/payment/create` + +**请求参数**: +```json +{ + "customerId": 1, + "billIds": [1, 2], + "paymentType": "NORMAL", + "paymentChannel": "CASH", + "paymentAmount": 183.60, + "actualAmount": 200.00, + "operatorId": "OP001", + "outletCode": "OUT001", + "remark": "现金缴费" +} +``` + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "paymentId": 1, + "paymentCode": "P202412190001", + "changeAmount": 16.40, + "invoiceNo": "INV20241219001" + } +} +``` + +#### 3.4.2 在线支付接口 + +**功能描述**:处理在线支付(微信、支付宝等)。 + +**接口详情**: +- **请求方式**:POST +- **请求路径**:`/admin-api/water/payment/online-pay` + +**请求参数**: +```json +{ + "customerId": 1, + "billIds": [1], + "paymentChannel": "WECHAT", + "paymentAmount": 91.80, + "returnUrl": "https://water.example.com/payment/callback", + "notifyUrl": "https://water.example.com/api/payment/notify" +} +``` + +**响应参数**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "paymentCode": "P202412190002", + "prepayId": "wx20241219001234567890", + "payUrl": "weixin://wxpay/bizpayurl?pr=abc123", + "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." + } +} +``` ### 3.4 工单接口 @@ -241,13 +946,390 @@ - **XML**:主要用于WebService接口,兼容性好,适合与传统系统对接 - **文本文件**:主要用于批量数据交换,如银行代扣文件等 -### 4.3 接口安全 +### 4.3 接口安全设计 -接口安全采用以下机制: +接口安全采用多层防护机制: -- **令牌认证**:使用JWT(JSON Web Token)进行身份认证 -- **签名验证**:请求参数签名,确保数据完整性 -- **IP白名单**:限制接口调用者的IP地址 -- **传输加密**:使用HTTPS协议加密传输数据 -- **接口限流**:限制单位时间内的请求次数 -- **访问日志**:记录所有接口调用情况 \ No newline at end of file +#### 4.3.1 认证机制 + +**JWT令牌认证**: +```java +@RestController +public class AuthController { + + @Resource + private AuthService authService; + + @PostMapping("/admin-api/system/auth/login") + public CommonResult login(@Valid @RequestBody AuthLoginReqVO reqVO) { + // 验证用户名密码 + AdminUserDO user = authService.authenticate(reqVO.getUsername(), reqVO.getPassword()); + + // 生成JWT Token + String token = authService.createToken(user.getId(), user.getTenantId()); + + return success(AuthLoginRespVO.builder() + .userId(user.getId()) + .accessToken(token) + .refreshToken(authService.createRefreshToken(user.getId())) + .expiresTime(LocalDateTime.now().plusHours(2)) + .build()); + } +} +``` + +**API Key认证**(外部系统): +```java +@Component +public class ApiKeyAuthenticationFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String apiKey = request.getHeader("X-API-KEY"); + String timestamp = request.getHeader("X-TIMESTAMP"); + String signature = request.getHeader("X-SIGNATURE"); + + // 验证API Key + if (!apiKeyService.validateApiKey(apiKey)) { + writeErrorResponse(response, "Invalid API Key"); + return; + } + + // 验证时间戳(防重放攻击) + if (!validateTimestamp(timestamp)) { + writeErrorResponse(response, "Request expired"); + return; + } + + // 验证签名 + if (!validateSignature(request, signature)) { + writeErrorResponse(response, "Invalid signature"); + return; + } + + filterChain.doFilter(request, response); + } +} +``` + +#### 4.3.2 数据加密 + +**敏感数据加密**: +```java +@Component +public class DataEncryptionService { + + private final AESUtil aesUtil; + + public String encryptPersonalInfo(String plainText) { + if (StrUtil.isBlank(plainText)) { + return plainText; + } + return aesUtil.encrypt(plainText); + } + + public String decryptPersonalInfo(String cipherText) { + if (StrUtil.isBlank(cipherText)) { + return cipherText; + } + return aesUtil.decrypt(cipherText); + } +} +``` + +#### 4.3.3 访问控制 + +**IP白名单控制**: +```java +@Component +public class IpWhitelistFilter extends OncePerRequestFilter { + + @Value("${water.security.ip-whitelist}") + private List ipWhitelist; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String clientIp = getClientIpAddress(request); + + if (!isIpAllowed(clientIp)) { + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.getWriter().write("{\"code\":403,\"msg\":\"IP access denied\"}"); + return; + } + + filterChain.doFilter(request, response); + } +} +``` + +#### 4.3.4 接口限流 + +**基于Redis的令牌桶限流**: +```java +@Component +public class RateLimitService { + + @Resource + private StringRedisTemplate redisTemplate; + + public boolean allowRequest(String key, int maxRequests, Duration window) { + String redisKey = "rate_limit:" + key; + String script = """ + local key = KEYS[1] + local window = tonumber(ARGV[1]) + local limit = tonumber(ARGV[2]) + local current = redis.call('get', key) + if current == false then + redis.call('setex', key, window, 1) + return 1 + end + if tonumber(current) < limit then + return redis.call('incr', key) + else + return 0 + end + """; + + DefaultRedisScript redisScript = new DefaultRedisScript<>(script, Long.class); + Long result = redisTemplate.execute(redisScript, + Collections.singletonList(redisKey), + String.valueOf(window.getSeconds()), + String.valueOf(maxRequests)); + + return result != null && result > 0; + } +} +``` + +### 4.4 错误处理机制 + +#### 4.4.1 统一异常处理 + +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ServiceException.class) + public CommonResult serviceExceptionHandler(ServiceException ex) { + log.info("[serviceExceptionHandler]", ex); + return CommonResult.error(ex.getCode(), ex.getMessage()); + } + + @ExceptionHandler(ConstraintViolationException.class) + public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) { + log.info("[constraintViolationExceptionHandler]", ex); + return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确:" + ex.getMessage()); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public CommonResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) { + log.info("[methodArgumentNotValidExceptionHandler]", ex); + FieldError fieldError = ex.getBindingResult().getFieldError(); + assert fieldError != null; + return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确:" + fieldError.getDefaultMessage()); + } +} +``` + +#### 4.4.2 错误码定义 + +```java +public interface ErrorCodeConstants { + + // ========== 通用错误码 1-000-000-000 ========== + ErrorCode SUCCESS = new ErrorCode(0, "成功"); + ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确"); + ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录"); + ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限"); + ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到"); + ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确"); + ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常"); + + // ========== 客户管理错误码 1-001-000-000 ========== + ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_001_000_001, "客户不存在"); + ErrorCode CUSTOMER_CODE_DUPLICATE = new ErrorCode(1_001_000_002, "客户编号已存在"); + ErrorCode CUSTOMER_STATUS_INVALID = new ErrorCode(1_001_000_003, "客户状态不正确"); + + // ========== 水表管理错误码 1-002-000-000 ========== + ErrorCode METER_NOT_EXISTS = new ErrorCode(1_002_000_001, "水表不存在"); + ErrorCode METER_CODE_DUPLICATE = new ErrorCode(1_002_000_002, "水表编号已存在"); + ErrorCode METER_READING_INVALID = new ErrorCode(1_002_000_003, "水表读数不正确"); + + // ========== 账单管理错误码 1-003-000-000 ========== + ErrorCode BILL_NOT_EXISTS = new ErrorCode(1_003_000_001, "账单不存在"); + ErrorCode BILL_ALREADY_PAID = new ErrorCode(1_003_000_002, "账单已缴费"); + ErrorCode BILL_AMOUNT_INVALID = new ErrorCode(1_003_000_003, "账单金额不正确"); + + // ========== 缴费管理错误码 1-004-000-000 ========== + ErrorCode PAYMENT_FAILED = new ErrorCode(1_004_000_001, "缴费失败"); + ErrorCode PAYMENT_AMOUNT_INSUFFICIENT = new ErrorCode(1_004_000_002, "缴费金额不足"); + ErrorCode PAYMENT_CHANNEL_UNAVAILABLE = new ErrorCode(1_004_000_003, "缴费渠道不可用"); +} +``` + +#### 4.4.3 接口调用示例 + +**成功响应示例**: +```json +{ + "code": 0, + "msg": "操作成功", + "data": { + "id": 1, + "customerName": "张三" + } +} +``` + +**失败响应示例**: +```json +{ + "code": 1001000001, + "msg": "客户不存在", + "data": null +} +``` + +### 4.5 前端接口调用示例 + +#### 4.5.1 Vue3 + TypeScript接口封装 + +```typescript +// api/water/customer.ts +import { request } from '@/utils/request' + +export interface CustomerVO { + id: number + customerCode: string + customerName: string + customerType: string + phone: string + address: string + status: number + createTime: string +} + +export interface CustomerPageReqVO extends PageParam { + customerName?: string + customerCode?: string + customerType?: string + phone?: string +} + +export const CustomerApi = { + // 获取客户分页 + getCustomerPage: (params: CustomerPageReqVO) => { + return request.get>({ url: '/water/customer/page', params }) + }, + + // 获取客户详情 + getCustomer: (id: number) => { + return request.get({ url: `/water/customer/${id}` }) + }, + + // 创建客户 + createCustomer: (data: CustomerSaveReqVO) => { + return request.post({ url: '/water/customer/create', data }) + }, + + // 更新客户 + updateCustomer: (data: CustomerSaveReqVO) => { + return request.put({ url: '/water/customer/update', data }) + }, + + // 删除客户 + deleteCustomer: (id: number) => { + return request.delete({ url: `/water/customer/delete?id=${id}` }) + } +} +``` + +#### 4.5.2 Vue组件使用示例 + +```vue + + + +``` \ No newline at end of file diff --git a/water_biz_module_design.md b/water_biz_module_design.md index 9935c6f..ed804a2 100644 --- a/water_biz_module_design.md +++ b/water_biz_module_design.md @@ -1,7 +1,18 @@ -# 福建水务业务系统概要设计 +# 福建水务营收系统模块功能设计文档 + +## 文档信息 +| 项目信息 | 详情 | +|---------|------| +| **项目名称** | 福建水务营收系统 | +| **文档类型** | 概要设计文档 | +| **技术框架** | RuoYi-Vue-Pro + yudao-ui-admin-vue3 | +| **文档版本** | v1.0 | +| **编写日期** | 2024-12-19 | +| **文档状态** | 🟡 进行中 | ## 目录 -- [福建水务业务系统概要设计](#福建水务业务系统概要设计) +- [福建水务营收系统模块功能设计文档](#福建水务营收系统模块功能设计文档) + - [文档信息](#文档信息) - [目录](#目录) - [1. 统一平台](#1-统一平台) - [1.1 单点登录](#11-单点登录) @@ -9,7 +20,13 @@ - [2. 营收系统](#2-营收系统) - [2.1 系统管理](#21-系统管理) - [2.2 抄表开账](#22-抄表开账) + - [2.2.1 业务流程图](#221-业务流程图) + - [2.2.2 主要功能](#222-主要功能) + - [2.2.3 RuoYi-Vue-Pro代码实现](#223-ruoyi-vue-pro代码实现) - [2.3 收费管理](#23-收费管理) + - [2.3.1 业务流程图](#231-业务流程图) + - [2.3.2 主要功能](#232-主要功能) + - [2.3.3 RuoYi-Vue-Pro代码实现](#233-ruoyi-vue-pro代码实现) - [2.4 账务处理](#24-账务处理) - [2.5 发票管理](#25-发票管理) - [2.6 代收业务](#26-代收业务) @@ -154,21 +171,642 @@ ### 2.2 抄表开账 -抄表开账模块负责水表读数的采集和账单生成,主要功能包括: +抄表开账模块负责水表读数的采集和账单生成,是营收系统的核心业务模块。 -- 册本管理:册本基本信息的维护和管理 -- 抄表录入:支持手工抄表、智能抄表、自报抄表 -- 抄表数据审核:数据校验、异常处理、开账处理 -- 追加抄表:支持非周期性特殊抄表 +#### 2.2.1 业务流程图 + +```mermaid +flowchart TD + Start([开始抄表周期]) --> BookPlan[制定抄表计划] + BookPlan --> CreateBook[生成抄表册本] + CreateBook --> AssignReader[分配抄表员] + + AssignReader --> ReadingStart[开始抄表] + ReadingStart --> ReadingType{抄表方式} + + ReadingType -->|人工抄表| ManualReading[现场抄表录入] + ReadingType -->|远程抄表| RemoteReading[远程采集数据] + ReadingType -->|客户自报| SelfReporting[客户自主上报] + + ManualReading --> DataValidation[数据校验] + RemoteReading --> DataValidation + SelfReporting --> DataValidation + + DataValidation --> ValidationResult{校验结果} + ValidationResult -->|异常| ExceptionHandle[异常处理] + ValidationResult -->|正常| DataReview[数据复核] + + ExceptionHandle --> ReviewException[人工审核异常] + ReviewException --> DataReview + + DataReview --> ReviewResult{复核结果} + ReviewResult -->|退回| ReadingStart + ReviewResult -->|通过| BillGeneration[生成账单] + + BillGeneration --> CalcWaterFee[计算水费] + CalcWaterFee --> CalcSewageFee[计算污水费] + CalcSewageFee --> CalcOtherFee[计算其他费用] + CalcOtherFee --> BillReview[账单审核] + + BillReview --> BillResult{审核结果} + BillResult -->|退回| BillGeneration + BillResult -->|通过| BillConfirm[账单确认] + + BillConfirm --> SendNotification[发送缴费通知] + SendNotification --> End([完成开账]) +``` + +#### 2.2.2 主要功能 + +**册本管理**:册本基本信息的维护和管理 +- 册本创建与配置 +- 抄表路线规划 +- 抄表员分配 +- 抄表周期设置 + +**抄表录入**:支持多种抄表方式 +- 手工抄表:现场抄表、批量录入 +- 智能抄表:远程数据采集、自动同步 +- 自报抄表:客户自主上报、在线提交 + +**抄表数据审核**:确保数据质量 +- 数据校验:读数合理性检查、用量异常检测 +- 异常处理:异常数据标记、人工处理 +- 开账处理:数据确认、账单生成 + +**追加抄表**:支持非周期性特殊抄表 +- 补抄管理:漏抄数据补录 +- 特殊抄表:临时抄表需求 +- 调整抄表:读数错误修正 + +#### 2.2.3 RuoYi-Vue-Pro代码实现 + +**抄表记录Controller层**: +```java +@RestController +@RequestMapping("/admin-api/water/reading") +@Tag(name = "管理后台 - 抄表管理") +@Validated +public class MeterReadingController { + + @Resource + private MeterReadingService readingService; + + @PostMapping("/create") + @Operation(summary = "创建抄表记录") + @PreAuthorize("@ss.hasPermission('water:reading:create')") + public CommonResult createReading(@Valid @RequestBody MeterReadingSaveReqVO createReqVO) { + return success(readingService.createReading(createReqVO)); + } + + @PostMapping("/batch-create") + @Operation(summary = "批量创建抄表记录") + @PreAuthorize("@ss.hasPermission('water:reading:create')") + public CommonResult batchCreateReading( + @Valid @RequestBody MeterReadingBatchReqVO batchReqVO) { + return success(readingService.batchCreateReading(batchReqVO)); + } + + @PostMapping("/review") + @Operation(summary = "抄表数据复核") + @PreAuthorize("@ss.hasPermission('water:reading:review')") + public CommonResult reviewReading(@Valid @RequestBody MeterReadingReviewReqVO reviewReqVO) { + readingService.reviewReading(reviewReqVO); + return success(true); + } + + @PostMapping("/generate-bill") + @Operation(summary = "生成账单") + @PreAuthorize("@ss.hasPermission('water:reading:bill')") + public CommonResult generateBill(@Valid @RequestBody ReadingBillReqVO reqVO) { + return success(readingService.generateBill(reqVO)); + } +} +``` + +**抄表业务Service层**: +```java +@Service +@Validated +@Slf4j +public class MeterReadingServiceImpl implements MeterReadingService { + + @Resource + private MeterReadingMapper readingMapper; + @Resource + private MeterService meterService; + @Resource + private BillService billService; + @Resource + private WaterPriceService priceService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createReading(MeterReadingSaveReqVO createReqVO) { + // 1. 校验水表存在性 + MeterDO meter = meterService.validateMeterExists(createReqVO.getMeterId()); + + // 2. 校验读数合理性 + validateReadingValue(createReqVO); + + // 3. 创建抄表记录 + MeterReadingDO reading = BeanUtils.toBean(createReqVO, MeterReadingDO.class); + reading.setReadingCode(generateReadingCode()); + reading.setCustomerId(meter.getCustomerId()); + + // 4. 计算用水量 + BigDecimal waterUsage = calculateWaterUsage(meter.getCurrentReading(), + createReqVO.getReadingValue()); + reading.setWaterUsage(waterUsage); + reading.setReadingStatus(READING_STATUS_PENDING); + + readingMapper.insert(reading); + + // 5. 更新水表当前读数 + meterService.updateCurrentReading(createReqVO.getMeterId(), + createReqVO.getReadingValue()); + + // 6. 记录操作日志 + logReadingOperation(reading.getId(), "创建抄表记录", createReqVO.toString()); + + return reading.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public MeterReadingBatchRespVO batchCreateReading(MeterReadingBatchReqVO batchReqVO) { + MeterReadingBatchRespVO result = new MeterReadingBatchRespVO(); + List successList = new ArrayList<>(); + List failureList = new ArrayList<>(); + + for (MeterReadingSaveReqVO item : batchReqVO.getReadingList()) { + try { + Long readingId = createReading(item); + successList.add(buildSuccessDetail(readingId, item)); + } catch (Exception e) { + log.error("批量抄表失败,水表ID:{}", item.getMeterId(), e); + failureList.add(buildFailureDetail(item, e.getMessage())); + } + } + + result.setSuccessCount(successList.size()); + result.setFailureCount(failureList.size()); + result.setSuccessList(successList); + result.setFailureList(failureList); + + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void reviewReading(MeterReadingReviewReqVO reviewReqVO) { + // 1. 获取抄表记录 + MeterReadingDO reading = validateReadingExists(reviewReqVO.getReadingId()); + + // 2. 校验状态 + if (!READING_STATUS_PENDING.equals(reading.getReadingStatus())) { + throw exception(READING_STATUS_INVALID); + } + + // 3. 处理复核结果 + if (reviewReqVO.getReviewResult()) { + // 复核通过 + reading.setReadingStatus(READING_STATUS_REVIEWED); + reading.setReviewRemark(reviewReqVO.getReviewRemark()); + } else { + // 复核退回 + reading.setReadingStatus(READING_STATUS_REJECTED); + reading.setReviewRemark(reviewReqVO.getReviewRemark()); + } + + reading.setReviewerId(reviewReqVO.getReviewerId()); + reading.setReviewTime(LocalDateTime.now()); + + readingMapper.updateById(reading); + + // 4. 记录操作日志 + logReadingOperation(reading.getId(), "数据复核", + "复核结果:" + reviewReqVO.getReviewResult() + ",备注:" + reviewReqVO.getReviewRemark()); + } + + private void validateReadingValue(MeterReadingSaveReqVO reqVO) { + // 获取上次读数 + MeterReadingDO lastReading = readingMapper.selectLastReadingByMeterId(reqVO.getMeterId()); + + if (lastReading != null) { + BigDecimal lastValue = lastReading.getReadingValue(); + BigDecimal currentValue = reqVO.getReadingValue(); + + // 检查读数倒退 + if (currentValue.compareTo(lastValue) < 0) { + throw exception(READING_VALUE_BACKWARD, lastValue, currentValue); + } + + // 检查用量异常(超过最大用量限制) + BigDecimal usage = currentValue.subtract(lastValue); + if (usage.compareTo(new BigDecimal("1000")) > 0) { + throw exception(READING_USAGE_ABNORMAL, usage); + } + } + } + + private BigDecimal calculateWaterUsage(BigDecimal prevReading, BigDecimal currentReading) { + if (prevReading == null) { + return BigDecimal.ZERO; + } + + BigDecimal usage = currentReading.subtract(prevReading); + return usage.max(BigDecimal.ZERO); // 确保用量不为负数 + } +} +``` + +**Entity实体层**: +```java +@TableName("water_meter_reading") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MeterReadingDO extends BaseDO { + + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + @Schema(description = "抄表记录编号", requiredMode = Schema.RequiredMode.REQUIRED) + private String readingCode; + + @Schema(description = "抄表日期", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDate readingDate; + + @Schema(description = "抄表时间") + private LocalDateTime readingTime; + + @Schema(description = "抄表读数", requiredMode = Schema.RequiredMode.REQUIRED) + private BigDecimal readingValue; + + @Schema(description = "上次读数") + private BigDecimal prevReadingValue; + + @Schema(description = "用水量", requiredMode = Schema.RequiredMode.REQUIRED) + private BigDecimal waterUsage; + + @Schema(description = "抄表类型", requiredMode = Schema.RequiredMode.REQUIRED) + private String readingType; + + @Schema(description = "抄表方式") + private String readingMethod; + + @Schema(description = "抄表员ID") + private String readerId; + + @Schema(description = "抄表员姓名") + private String readerName; + + @Schema(description = "抄表照片URL") + private String photoUrl; + + @Schema(description = "抄表位置") + private String readingLocation; + + @Schema(description = "异常标识") + private Boolean abnormalFlag; + + @Schema(description = "异常原因") + private String abnormalReason; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "抄表状态", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer readingStatus; + + @Schema(description = "复核人ID") + private String reviewerId; + + @Schema(description = "复核时间") + private LocalDateTime reviewTime; + + @Schema(description = "复核备注") + private String reviewRemark; + + @Schema(description = "水表ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long meterId; + + @Schema(description = "客户ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long customerId; +} +``` ### 2.3 收费管理 -收费管理模块负责水费的收取和管理,主要功能包括: +收费管理模块负责水费的收取和管理,是营收系统的重要业务模块。 -- 柜台收费:用户查询、收费处理、收费打印、预存预付 -- 柜台结账:日结、交款、结账查询 -- 预付款管理:预付款充值、使用和退款 -- 缴费记录查询:支持多条件查询缴费记录 +#### 2.3.1 业务流程图 + +```mermaid +flowchart TD + Start([客户缴费]) --> QueryCustomer[查询客户信息] + QueryCustomer --> CustomerExists{客户是否存在} + CustomerExists -->|否| ErrorReturn[返回错误信息] + CustomerExists -->|是| QueryBills[查询欠费账单] + + QueryBills --> BillExists{是否有欠费} + BillExists -->|否| NoDebt[无欠费提示] + BillExists -->|是| ShowBills[显示账单列表] + + ShowBills --> SelectBills[选择缴费账单] + SelectBills --> CalcAmount[计算缴费金额] + CalcAmount --> SelectPayMethod[选择支付方式] + + SelectPayMethod --> PaymentType{支付方式} + PaymentType -->|现金| CashPayment[现金收费] + PaymentType -->|银行卡| BankCardPay[银行卡支付] + PaymentType -->|在线支付| OnlinePayment[在线支付] + PaymentType -->|预存款| PrepaidPayment[预存款支付] + + CashPayment --> ValidatePayment[验证收费金额] + BankCardPay --> ValidatePayment + OnlinePayment --> ThirdPartyPay[第三方支付] + PrepaidPayment --> CheckBalance[检查预存余额] + + ThirdPartyPay --> PaymentCallback[支付回调] + PaymentCallback --> ValidatePayment + + CheckBalance --> BalanceOK{余额是否充足} + BalanceOK -->|否| InsufficientBalance[余额不足] + BalanceOK -->|是| ValidatePayment + + ValidatePayment --> PaymentSuccess{支付成功} + PaymentSuccess -->|否| PaymentFailed[支付失败处理] + PaymentSuccess -->|是| UpdateAccount[更新账户状态] + + UpdateAccount --> UpdateBills[更新账单状态] + UpdateBills --> GenerateReceipt[生成收费凭证] + GenerateReceipt --> PrintReceipt[打印收据] + PrintReceipt --> SendNotification[发送缴费通知] + SendNotification --> Complete([完成缴费]) + + PaymentFailed --> End([结束]) + InsufficientBalance --> End + ErrorReturn --> End + NoDebt --> End +``` + +#### 2.3.2 主要功能 + +**柜台收费**:现场收费服务 +- 用户查询:客户信息查询、账单查询 +- 收费处理:多种支付方式、找零计算 +- 收费打印:收据打印、发票开具 +- 预存预付:余额充值、预付费管理 + +**柜台结账**:营业网点日常结账 +- 日结处理:当日收费汇总、统计分析 +- 交款管理:现金上缴、账务核对 +- 结账查询:历史结账记录查询 + +**预付款管理**:预付费业务处理 +- 预付款充值:余额充值、充值记录 +- 使用管理:自动扣款、余额提醒 +- 退款处理:预付款退款、退款审核 + +**缴费记录查询**:缴费历史管理 +- 多条件查询:按时间、金额、渠道查询 +- 统计分析:缴费趋势、渠道分析 +- 导出功能:缴费记录导出 + +#### 2.3.3 RuoYi-Vue-Pro代码实现 + +**缴费Controller层**: +```java +@RestController +@RequestMapping("/admin-api/water/payment") +@Tag(name = "管理后台 - 缴费管理") +@Validated +public class PaymentController { + + @Resource + private PaymentService paymentService; + + @PostMapping("/create") + @Operation(summary = "创建缴费记录") + @PreAuthorize("@ss.hasPermission('water:payment:create')") + public CommonResult createPayment(@Valid @RequestBody PaymentCreateReqVO createReqVO) { + return success(paymentService.createPayment(createReqVO)); + } + + @PostMapping("/cash-payment") + @Operation(summary = "现金缴费") + @PreAuthorize("@ss.hasPermission('water:payment:cash')") + public CommonResult cashPayment(@Valid @RequestBody CashPaymentReqVO cashReqVO) { + return success(paymentService.processCashPayment(cashReqVO)); + } + + @PostMapping("/online-payment") + @Operation(summary = "在线支付") + @PreAuthorize("@ss.hasPermission('water:payment:online')") + public CommonResult onlinePayment(@Valid @RequestBody OnlinePaymentReqVO onlineReqVO) { + return success(paymentService.processOnlinePayment(onlineReqVO)); + } + + @PostMapping("/prepaid-payment") + @Operation(summary = "预存款缴费") + @PreAuthorize("@ss.hasPermission('water:payment:prepaid')") + public CommonResult prepaidPayment(@Valid @RequestBody PrepaidPaymentReqVO prepaidReqVO) { + return success(paymentService.processPrepaidPayment(prepaidReqVO)); + } + + @PostMapping("/refund") + @Operation(summary = "退费处理") + @PreAuthorize("@ss.hasPermission('water:payment:refund')") + public CommonResult refundPayment(@Valid @RequestBody PaymentRefundReqVO refundReqVO) { + paymentService.refundPayment(refundReqVO); + return success(true); + } +} +``` + +**缴费Service层**: +```java +@Service +@Validated +@Slf4j +public class PaymentServiceImpl implements PaymentService { + + @Resource + private PaymentMapper paymentMapper; + @Resource + private BillService billService; + @Resource + private CustomerAccountService accountService; + @Resource + private PaymentChannelService channelService; + + @Override + @Transactional(rollbackFor = Exception.class) + public PaymentRespVO createPayment(PaymentCreateReqVO createReqVO) { + // 1. 校验账单信息 + List bills = validateBills(createReqVO.getBillIds()); + + // 2. 计算缴费金额 + BigDecimal totalAmount = bills.stream() + .map(BillDO::getBalanceAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + if (totalAmount.compareTo(createReqVO.getPaymentAmount()) != 0) { + throw exception(PAYMENT_AMOUNT_MISMATCH); + } + + // 3. 创建缴费记录 + PaymentDO payment = BeanUtils.toBean(createReqVO, PaymentDO.class); + payment.setPaymentCode(generatePaymentCode()); + payment.setPaymentStatus(PAYMENT_STATUS_PENDING); + payment.setTotalAmount(totalAmount); + + paymentMapper.insert(payment); + + // 4. 创建缴费明细 + createPaymentDetails(payment.getId(), bills); + + return BeanUtils.toBean(payment, PaymentRespVO.class); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PaymentRespVO processCashPayment(CashPaymentReqVO cashReqVO) { + // 1. 创建基础缴费记录 + PaymentCreateReqVO createReqVO = BeanUtils.toBean(cashReqVO, PaymentCreateReqVO.class); + createReqVO.setPaymentChannel("CASH"); + createReqVO.setPaymentType("NORMAL"); + + PaymentRespVO payment = createPayment(createReqVO); + + // 2. 处理现金缴费 + PaymentDO paymentDO = paymentMapper.selectById(payment.getId()); + paymentDO.setActualAmount(cashReqVO.getActualAmount()); + paymentDO.setChangeAmount(cashReqVO.getChangeAmount()); + paymentDO.setOperatorId(cashReqVO.getOperatorId()); + paymentDO.setOutletCode(cashReqVO.getOutletCode()); + paymentDO.setPaymentStatus(PAYMENT_STATUS_SUCCESS); + paymentDO.setPaymentTime(LocalDateTime.now()); + + paymentMapper.updateById(paymentDO); + + // 3. 更新账单状态 + updateBillStatus(cashReqVO.getBillIds(), payment.getId()); + + // 4. 更新客户账户 + updateCustomerAccount(cashReqVO.getCustomerId(), cashReqVO.getPaymentAmount()); + + // 5. 生成收据号 + paymentDO.setInvoiceNo(generateInvoiceNo()); + paymentMapper.updateById(paymentDO); + + // 6. 记录操作日志 + logPaymentOperation(payment.getId(), "现金缴费", cashReqVO.toString()); + + return BeanUtils.toBean(paymentDO, PaymentRespVO.class); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public OnlinePaymentRespVO processOnlinePayment(OnlinePaymentReqVO onlineReqVO) { + // 1. 创建基础缴费记录 + PaymentCreateReqVO createReqVO = BeanUtils.toBean(onlineReqVO, PaymentCreateReqVO.class); + createReqVO.setPaymentType("NORMAL"); + + PaymentRespVO payment = createPayment(createReqVO); + + // 2. 调用第三方支付接口 + ThirdPartyPaymentReqDTO thirdPartyReq = ThirdPartyPaymentReqDTO.builder() + .paymentCode(payment.getPaymentCode()) + .paymentChannel(onlineReqVO.getPaymentChannel()) + .totalAmount(onlineReqVO.getPaymentAmount()) + .subject("水费缴费") + .body("账单缴费-客户:" + onlineReqVO.getCustomerId()) + .notifyUrl(onlineReqVO.getNotifyUrl()) + .returnUrl(onlineReqVO.getReturnUrl()) + .build(); + + ThirdPartyPaymentRespDTO thirdPartyResp = channelService.createPayment(thirdPartyReq); + + // 3. 更新缴费记录 + PaymentDO paymentDO = paymentMapper.selectById(payment.getId()); + paymentDO.setThirdPartyNo(thirdPartyResp.getOutTradeNo()); + paymentDO.setPaymentStatus(PAYMENT_STATUS_PROCESSING); + paymentMapper.updateById(paymentDO); + + // 4. 构建返回结果 + return OnlinePaymentRespVO.builder() + .paymentId(payment.getId()) + .paymentCode(payment.getPaymentCode()) + .prepayId(thirdPartyResp.getPrepayId()) + .payUrl(thirdPartyResp.getPayUrl()) + .qrCode(thirdPartyResp.getQrCode()) + .build(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PaymentRespVO processPrepaidPayment(PrepaidPaymentReqVO prepaidReqVO) { + // 1. 检查预存余额 + CustomerAccountDO account = accountService.getByCustomerId(prepaidReqVO.getCustomerId()); + if (account.getBalance().compareTo(prepaidReqVO.getPaymentAmount()) < 0) { + throw exception(ACCOUNT_BALANCE_INSUFFICIENT); + } + + // 2. 创建基础缴费记录 + PaymentCreateReqVO createReqVO = BeanUtils.toBean(prepaidReqVO, PaymentCreateReqVO.class); + createReqVO.setPaymentChannel("PREPAID"); + createReqVO.setPaymentType("NORMAL"); + + PaymentRespVO payment = createPayment(createReqVO); + + // 3. 扣除预存余额 + accountService.deductBalance(prepaidReqVO.getCustomerId(), prepaidReqVO.getPaymentAmount(), + "水费缴费", payment.getPaymentCode()); + + // 4. 更新缴费状态 + PaymentDO paymentDO = paymentMapper.selectById(payment.getId()); + paymentDO.setPaymentStatus(PAYMENT_STATUS_SUCCESS); + paymentDO.setPaymentTime(LocalDateTime.now()); + paymentMapper.updateById(paymentDO); + + // 5. 更新账单状态 + updateBillStatus(prepaidReqVO.getBillIds(), payment.getId()); + + return BeanUtils.toBean(paymentDO, PaymentRespVO.class); + } + + private void updateBillStatus(List billIds, Long paymentId) { + for (Long billId : billIds) { + BillDO bill = billService.getBill(billId); + bill.setBillStatus(BILL_STATUS_PAID); + bill.setPaidAmount(bill.getTotalAmount()); + bill.setBalanceAmount(BigDecimal.ZERO); + bill.setPaymentId(paymentId); + billService.updateBill(bill); + } + } + + private void updateCustomerAccount(Long customerId, BigDecimal paymentAmount) { + CustomerAccountDO account = accountService.getByCustomerId(customerId); + account.setLastPaymentDate(LocalDate.now()); + account.setLastPaymentAmount(paymentAmount); + + // 如果有欠费,减少欠费金额 + if (account.getArrearsAmount().compareTo(BigDecimal.ZERO) > 0) { + BigDecimal newArrears = account.getArrearsAmount().subtract(paymentAmount); + account.setArrearsAmount(newArrears.max(BigDecimal.ZERO)); + } + + accountService.updateAccount(account); + } +} +``` ### 2.4 账务处理 diff --git a/water_biz_system_architecture.md b/water_biz_system_architecture.md index c458bb3..88fbff2 100644 --- a/water_biz_system_architecture.md +++ b/water_biz_system_architecture.md @@ -1,17 +1,27 @@ -# 福建水务业务系统架构设计 +# 福建水务营收系统架构设计文档 + +## 文档信息 +| 项目信息 | 详情 | +|---------|------| +| **项目名称** | 福建水务营收系统 | +| **文档类型** | 概要设计文档 | +| **技术框架** | RuoYi-Vue-Pro + yudao-ui-admin-vue3 | +| **文档版本** | v1.0 | +| **编写日期** | 2024-12-19 | +| **文档状态** | 🟡 进行中 | ## 目录 -- [1. 系统架构概述](#1-系统架构概述) -- [2. 技术架构](#2-技术架构) -- [3. 应用架构](#3-应用架构) -- [4. 数据架构](#4-数据架构) -- [5. 安全架构](#5-安全架构) -- [6. 部署架构](#6-部署架构) -- [7. 接口架构](#7-接口架构) +- [一、系统架构概述](#一系统架构概述) +- [二、技术架构](#二技术架构) +- [三、应用架构](#三应用架构) +- [四、数据架构](#四数据架构) +- [五、安全架构](#五安全架构) +- [六、部署架构](#六部署架构) +- [七、接口架构](#七接口架构) -## 1. 系统架构概述 +## 一、系统架构概述 -福建水务业务系统采用多层架构设计,旨在支持集团化的集中管理、统一资源管理的业务需求。系统架构的设计目标是实现"一个业务平台、一网通平台"的建设目标,为集团及下属各分公司提供统一的营业收费系统。 +福建水务营收系统采用多层架构设计,旨在支持集团化的集中管理、统一资源管理的业务需求。系统架构的设计目标是实现"一个业务平台、一网通平台"的建设目标,为集团及下属各分公司提供统一的营业收费系统。 系统架构主要包括以下核心特点: - 多租户架构:支持集团、分公司、营业站点的多层级租户管理模式 @@ -19,18 +29,219 @@ - 统一平台应用:统一业务流程,集中汇集数据 - 统一对外接口:提供标准的接口和能力 -### 1.1 系统架构图 +### 1. 系统总体架构图 -系统整体架构如下图所示: +```mermaid +graph TB + subgraph "用户层" + A1[Web管理端
yudao-ui-admin-vue3] + A2[移动抄表端
uni-app] + A3[客户微信端
微信小程序] + A4[客户支付宝端
支付宝小程序] + end + + subgraph "网关层" + B1[Nginx负载均衡] + B2[API网关
统一认证/权限控制] + end + + subgraph "应用层" + C1[营收管理
RuoYi-Vue-Pro] + C2[客户服务
RuoYi-Vue-Pro] + C3[表务管理
RuoYi-Vue-Pro] + C4[统计分析
RuoYi-Vue-Pro] + end + + subgraph "服务层" + D1[权限服务
Spring Security] + D2[工作流服务
Flowable] + D3[消息服务
Redis MQ] + D4[文件服务
MinIO/OSS] + end + + subgraph "数据层" + E1[(MySQL 8.0
主从架构)] + E2[(Redis 6.0
集群缓存)] + E3[文件存储
分布式存储] + end + + subgraph "外部接口" + F1[银行接口
代扣/托收] + F2[支付接口
微信/支付宝] + F3[短信接口
阿里云/腾讯云] + F4[物联网接口
智能水表] + end + + A1 --> B1 + A2 --> B1 + A3 --> B1 + A4 --> B1 + + B1 --> B2 + B2 --> C1 + B2 --> C2 + B2 --> C3 + B2 --> C4 + + C1 --> D1 + C1 --> D2 + C1 --> D3 + C1 --> D4 + C2 --> D1 + C2 --> D3 + C3 --> D1 + C3 --> D2 + C4 --> D1 + + D1 --> E1 + D2 --> E1 + D3 --> E2 + D4 --> E3 + + C1 --> F1 + C1 --> F2 + C2 --> F3 + C3 --> F4 +``` -![系统架构图](images/architecture_diagram.png) +### 2. 物理部署架构图 + +```mermaid +graph TB + subgraph "DMZ区域" + LB1[负载均衡器
Nginx Cluster] + WAF[Web应用防火墙] + end + + subgraph "应用服务区" + subgraph "Web服务集群" + WEB1[Web服务器1
8核32G] + WEB2[Web服务器2
8核32G] + end + + subgraph "应用服务集群" + APP1[应用服务器1
16核64G] + APP2[应用服务器2
16核64G] + end + end + + subgraph "数据服务区" + subgraph "数据库集群" + DB1[MySQL主库
32核128G] + DB2[MySQL从库
32核128G] + end + + subgraph "缓存集群" + REDIS1[Redis主节点
16核32G] + REDIS2[Redis从节点
16核32G] + REDIS3[Redis哨兵
8核16G] + end + + subgraph "文件存储" + FILE1[文件服务器1
8核32G 10TB] + FILE2[文件服务器2
8核32G 10TB] + end + end + + subgraph "管理服务区" + MONITOR[监控服务器
8核16G] + BACKUP[备份服务器
8核32G 20TB] + JUMP[跳板服务器
4核8G] + end + + Internet --> WAF + WAF --> LB1 + LB1 --> WEB1 + LB1 --> WEB2 + + WEB1 --> APP1 + WEB1 --> APP2 + WEB2 --> APP1 + WEB2 --> APP2 + + APP1 --> DB1 + APP2 --> DB1 + DB1 --> DB2 + + APP1 --> REDIS1 + APP2 --> REDIS1 + REDIS1 --> REDIS2 + REDIS3 --> REDIS1 + REDIS3 --> REDIS2 + + APP1 --> FILE1 + APP2 --> FILE2 + FILE1 --> FILE2 + + MONITOR --> APP1 + MONITOR --> APP2 + MONITOR --> DB1 + MONITOR --> REDIS1 + + BACKUP --> DB1 + BACKUP --> FILE1 +``` -## 2. 技术架构 +## 二、技术架构 系统采用B/S和M/S相结合的架构模式,具体技术栈如下: -### 2.1 服务端 +### 1. 技术栈总览图 + +```mermaid +graph TB + subgraph "前端技术栈" + FE1[Vue 3.2+ TypeScript] + FE2[Element Plus UI] + FE3[Vite 构建工具] + FE4[Pinia 状态管理] + FE5[Vue Router 路由] + end + + subgraph "后端技术栈" + BE1[Spring Boot 3.x] + BE2[Spring Security 6.x] + BE3[MyBatis Plus 3.x] + BE4[Redis 6.0+] + BE5[MySQL 8.0+] + end + + subgraph "中间件技术栈" + MW1[Nginx 负载均衡] + MW2[Redis 缓存集群] + MW3[Flowable 工作流] + MW4[Quartz 定时任务] + MW5[MinIO 文件存储] + end + + subgraph "监控运维技术栈" + OP1[SkyWalking APM] + OP2[Spring Boot Admin] + OP3[Docker 容器化] + OP4[Kubernetes 编排] + OP5[Jenkins CI/CD] + end + + FE1 --> BE1 + FE2 --> BE1 + BE1 --> BE2 + BE1 --> BE3 + BE3 --> BE5 + BE1 --> BE4 + BE1 --> MW3 + BE1 --> MW4 + MW1 --> BE1 + BE1 --> MW5 + + OP1 --> BE1 + OP2 --> BE1 + OP3 --> BE1 + OP4 --> OP3 + OP5 --> OP4 +``` + +### 2. 服务端技术架构 - 操作系统:国产 Linux 操作系统 - 数据库:MySQL/MariaDB数据库,支持国产OpenGauss数据库 - 应用框架:基于RuoYi-Vue-Pro框架定制开发 @@ -46,7 +257,206 @@ - 代码生成:自动化CRUD和表单代码生成器 - 多租户:基于字段隔离的SaaS多租户设计 -### 2.2 客户端 +#### 2.1 RuoYi-Vue-Pro框架配置示例 + +**application.yml主配置文件:** +```yaml +# 服务端口配置 +server: + port: 48080 + servlet: + context-path: /admin-api + +# Spring Boot 配置 +spring: + application: + name: water-biz-server + profiles: + active: local + + # 数据源配置 + datasource: + druid: + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + username: root + password: ${MYSQL_PASSWORD:123456} + driver-class-name: com.mysql.cj.jdbc.Driver + # 连接池配置 + initial-size: 10 + min-idle: 10 + max-active: 20 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + validation-query: SELECT 1 FROM DUAL + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + + # Redis 配置 + redis: + host: 127.0.0.1 + port: 6379 + password: ${REDIS_PASSWORD:} + database: 1 + timeout: 6000ms + lettuce: + pool: + max-active: 32 + max-wait: 6000ms + max-idle: 32 + min-idle: 8 + +# MyBatis Plus 配置 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: NONE + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + type-aliases-package: cn.iocoder.yudao.module.*.dal.dataobject + +# 水务系统多租户配置 +yudao: + tenant: + enable: true + ignore-urls: + - /admin-api/water/tenant/get-id-by-name + - /admin-api/infra/file/*/get/** + ignore-tables: + - water_tenant + - water_system_config + - water_dict_data + - water_dict_type + + # 水务系统安全配置 + security: + permit-all-urls: + - /admin-api/water/auth/login + - /admin-api/water/auth/logout + - /admin-api/water/auth/refresh-token + - /admin-api/water/captcha/get + - /admin-api/water/sms/send + + # 文件存储配置 + file: + config: + type: local + local: + domain: http://127.0.0.1:48080 + path: /Users/yunai/file_test + + # 短信配置 + sms: + alibaba: + access-key-id: ${SMS_ACCESS_KEY_ID:} + access-key-secret: ${SMS_ACCESS_KEY_SECRET:} + signature: 福建水务 + template-code: SMS_123456 +``` + +**多租户配置实现:** +```java +@Component +@Slf4j +public class WaterTenantConfiguration { + + @Resource + private TenantProperties tenantProperties; + + /** + * 多租户字段处理器 + */ + @Bean + public TenantLineHandler tenantLineHandler() { + return new TenantLineHandler() { + + @Override + public Expression getTenantId() { + // 从当前登录用户上下文获取租户ID + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId == null) { + return null; + } + return new LongValue(tenantId); + } + + @Override + public String getTenantIdColumn() { + return "tenant_id"; + } + + @Override + public boolean ignoreTable(String tableName) { + // 忽略的表不进行多租户处理 + return tenantProperties.getIgnoreTables().contains(tableName); + } + }; + } + + /** + * 多租户拦截器 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 多租户插件 + TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor(); + tenantInterceptor.setTenantLineHandler(tenantLineHandler()); + interceptor.addInnerInterceptor(tenantInterceptor); + // 分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} +``` + +**权限控制配置:** +```java +@EnableWebSecurity +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Configuration +public class WaterSecurityConfiguration { + + @Resource + private SecurityProperties securityProperties; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity httpSecurity, + @Lazy TokenAuthenticationFilter tokenAuthenticationFilter) throws Exception { + return httpSecurity + // 设置 URL 安全权限 + .authorizeHttpRequests(c -> c + // 1. 静态资源,可匿名访问 + .requestMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() + // 2. 设置 @PermitAll 无需认证 + .requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll() + // 3. 兜底规则,必须认证 + .anyRequest().authenticated() + ) + // 设置处理器 + .exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint) + .accessDeniedHandler(accessDeniedHandler)) + // 添加 Token Filter + .addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) + // 不创建 SecurityContext + .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // 禁用 CSRF,因为使用 token 机制 + .csrf(AbstractHttpConfigurer::disable) + // 禁用 cors + .cors(AbstractHttpConfigurer::disable) + .build(); + } +} +``` + +### 3. 客户端技术架构 - 前端框架:基于yudao-ui-admin-vue3框架定制开发 - 核心框架:Vue 3.x + TypeScript - 组件库:Element Plus,现代化UI组件 @@ -60,6 +470,240 @@ - 权限控制:细粒度的按钮级权限控制 - 动态表格:支持拖拽、排序、筛选 - 代码规范:ESLint + Prettier,标准化代码风格 + +#### 3.1 前端项目结构配置 + +**package.json 依赖配置:** +```json +{ + "name": "water-biz-ui-admin", + "version": "1.0.0", + "description": "福建水务营收系统管理后台", + "dependencies": { + "@element-plus/icons-vue": "^2.1.0", + "@vueuse/core": "^10.5.0", + "axios": "^1.6.0", + "echarts": "^5.4.3", + "element-plus": "^2.4.2", + "pinia": "^2.1.7", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "@wangeditor/editor": "^5.1.23", + "@wangeditor/editor-for-vue": "^5.1.12" + }, + "devDependencies": { + "@types/node": "^20.8.7", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "@vitejs/plugin-vue": "^4.4.1", + "eslint": "^8.52.0", + "eslint-plugin-vue": "^9.17.0", + "prettier": "^3.0.3", + "typescript": "^5.2.2", + "vite": "^4.5.0", + "vue-tsc": "^1.8.22" + } +} +``` + +**vite.config.ts 构建配置:** +```typescript +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src'), + '~': resolve(__dirname, 'src'), + '#': resolve(__dirname, 'types') + } + }, + server: { + host: '0.0.0.0', + port: 3000, + open: true, + proxy: { + '/admin-api': { + target: 'http://127.0.0.1:48080', + changeOrigin: true, + ws: true + } + } + }, + build: { + target: 'es2015', + outDir: 'dist', + assetsDir: 'static', + sourcemap: false, + chunkSizeWarningLimit: 1500, + rollupOptions: { + output: { + chunkFileNames: 'static/js/[name]-[hash].js', + entryFileNames: 'static/js/[name]-[hash].js', + assetFileNames: 'static/[ext]/[name]-[hash].[ext]' + } + } + } +}) +``` + +**src/stores/user.ts 用户状态管理:** +```typescript +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { UserApi, UserVO } from '@/api/system/user' +import { getAccessToken, removeToken } from '@/utils/auth' + +export const useUserStore = defineStore('user', () => { + const userInfo = ref() + const permissions = ref([]) + const roles = ref([]) + + const nickname = computed(() => userInfo.value?.nickname ?? '') + const avatar = computed(() => userInfo.value?.avatar ?? '') + const email = computed(() => userInfo.value?.email ?? '') + + // 获取用户信息 + const getUserInfo = async () => { + const res = await UserApi.getUserProfile() + userInfo.value = res + permissions.value = res.permissions + roles.value = res.roles + } + + // 用户登出 + const logout = async () => { + try { + await UserApi.logout() + } finally { + await resetToken() + } + } + + // 重置令牌 + const resetToken = async () => { + userInfo.value = undefined + permissions.value = [] + roles.value = [] + removeToken() + } + + // 检查权限 + const hasPermission = (permission: string) => { + return permissions.value.includes(permission) + } + + // 检查角色 + const hasRole = (role: string) => { + return roles.value.includes(role) + } + + return { + userInfo, + permissions, + roles, + nickname, + avatar, + email, + getUserInfo, + logout, + resetToken, + hasPermission, + hasRole + } +}) +``` + +**src/utils/request.ts HTTP请求封装:** +```typescript +import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios' +import { ElMessage, ElMessageBox } from 'element-plus' +import { getAccessToken, getRefreshToken, removeToken } from '@/utils/auth' +import { useUserStore } from '@/stores/user' + +// 创建 axios 实例 +const service = axios.create({ + baseURL: import.meta.env.VITE_BASE_URL, + timeout: 50000, + withCredentials: false +}) + +// 请求拦截器 +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // 添加 token + const accessToken = getAccessToken() + if (accessToken && config.headers) { + config.headers.Authorization = `Bearer ${accessToken}` + } + + // 添加租户ID + const tenantId = localStorage.getItem('tenantId') + if (tenantId && config.headers) { + config.headers['tenant-id'] = tenantId + } + + return config + }, + error => { + console.log(error) + return Promise.reject(error) + } +) + +// 响应拦截器 +service.interceptors.response.use( + (response: AxiosResponse) => { + const { data } = response + const { code, msg } = data + + // 业务请求成功 + if (code === 0) { + return data + } + + // token 过期,尝试刷新 + if (code === 401) { + return handleTokenExpired() + } + + // 业务请求失败 + ElMessage.error(msg || '系统未知错误,请反馈给管理员') + return Promise.reject(new Error(msg || 'Error')) + }, + error => { + console.log('err' + error) + let { message } = error + if (message === 'Network Error') { + message = '后端接口连接异常' + } else if (message.includes('timeout')) { + message = '系统接口请求超时' + } else if (message.includes('Request failed with status code')) { + message = '系统接口' + message.substr(message.length - 3) + '异常' + } + ElMessage.error(message) + return Promise.reject(error) + } +) + +// 处理 token 过期 +const handleTokenExpired = async () => { + const userStore = useUserStore() + ElMessageBox.alert('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { + confirmButtonText: '重新登录', + type: 'warning' + }).then(() => { + userStore.resetToken().then(() => { + location.reload() + }) + }) +} + +export default service +``` - 移动端: - 核心框架:uni-app(跨平台) - 组件库:uview-ui