更新福建水务营收系统项目管理文档,简化多个文档中的代码示例,保持概要设计的抽象层次,提升文档可读性和一致性。同时,更新项目进度文档,记录文档优化和代码简化的进展,确保所有文档符合甲方A级交付标准。调整模块设计文档和接口设计文档的状态为已完成,完成度提升至100%,质量评级调整为A级。
This commit is contained in:
parent
ea705d1458
commit
67db982cfa
@ -18,12 +18,12 @@
|
|||||||
| 文档名称 | 状态 | 完成度 | 质量评级 | 最后更新 | 备注 |
|
| 文档名称 | 状态 | 完成度 | 质量评级 | 最后更新 | 备注 |
|
||||||
|---------|------|--------|----------|----------|------|
|
|---------|------|--------|----------|----------|------|
|
||||||
| `water_biz_overview_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 新增引言文档,包含编写目的、背景、定义等 |
|
| `water_biz_overview_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 新增引言文档,包含编写目的、背景、定义等 |
|
||||||
| `water_biz_system_architecture.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已全面适配OpenGauss,架构图完整 |
|
| `water_biz_system_architecture.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已简化配置代码,突出架构设计要点 |
|
||||||
| `water_biz_module_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已完成代码示例优化,符合概要设计标准 |
|
| `water_biz_module_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已简化代码示例,符合概要设计抽象层次 |
|
||||||
| `water_biz_database_design.md` | ✅ 已完成 | 100% | A+级 | 2024-12-19 | 已适配OpenGauss,完整DDL和安全设计 |
|
| `water_biz_database_design.md` | ✅ 已完成 | 100% | A+级 | 2024-12-19 | 已适配OpenGauss,完整DDL和安全设计 |
|
||||||
| `water_biz_interface_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已补充详细接口参数、代码示例和安全设计 |
|
| `water_biz_interface_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已简化代码示例,保持接口设计抽象层次 |
|
||||||
| `water_biz_deployment_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已适配OpenGauss,专注Docker Compose部署 |
|
| `water_biz_deployment_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已适配OpenGauss,专注Docker Compose部署 |
|
||||||
| `water_biz_security_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 等保三级安全设计,基于OpenGauss |
|
| `water_biz_security_design.md` | ✅ 已完成 | 100% | A级 | 2024-12-19 | 已简化代码示例,突出安全设计要点 |
|
||||||
|
|
||||||
### 补充文档 (可选交付)
|
### 补充文档 (可选交付)
|
||||||
|
|
||||||
@ -126,6 +126,7 @@
|
|||||||
| 2024-12-19 | 快速导出工具 | 创建快速统一导出工具quick_unified_export.sh | 解决统一导出工具卡住问题,稳定高效 | 正面影响,完美解决所有问题 |
|
| 2024-12-19 | 快速导出工具 | 创建快速统一导出工具quick_unified_export.sh | 解决统一导出工具卡住问题,稳定高效 | 正面影响,完美解决所有问题 |
|
||||||
| 2024-12-19 | 分离文档导出 | 修改unified_export.sh支持分离文档导出,创建manage_separated_docs.sh管理工具 | 用户需求:将每个文档分别导出为不同格式,而不是合并成一个大文档 | 正面影响,提供更灵活的文档导出选项 |
|
| 2024-12-19 | 分离文档导出 | 修改unified_export.sh支持分离文档导出,创建manage_separated_docs.sh管理工具 | 用户需求:将每个文档分别导出为不同格式,而不是合并成一个大文档 | 正面影响,提供更灵活的文档导出选项 |
|
||||||
| 2024-12-19 | 新增引言文档 | 创建water_biz_overview_design.md引言文档 | 用户需求:添加标准的第一章内容(编写目的、背景、定义、参考资料) | 正面影响,完善文档体系结构 |
|
| 2024-12-19 | 新增引言文档 | 创建water_biz_overview_design.md引言文档 | 用户需求:添加标准的第一章内容(编写目的、背景、定义、参考资料) | 正面影响,完善文档体系结构 |
|
||||||
|
| 2024-12-19 | 代码简化优化 | 删除文档中过于详细的代码示例,保持概要设计抽象层次 | 用户反馈:删除过多详细的代码 | 正面影响,符合概要设计标准,提升文档可读性 |
|
||||||
|
|
||||||
## 项目完成总结
|
## 项目完成总结
|
||||||
|
|
||||||
|
|||||||
@ -123,54 +123,12 @@
|
|||||||
记录类型(1位) + 客户号(12位) + 银行账号(20位) + 扣款金额(12位) + 处理状态(1位) + 银行流水号(20位) + 处理时间(14位) + 失败原因(20位)
|
记录类型(1位) + 客户号(12位) + 银行账号(20位) + 扣款金额(12位) + 处理状态(1位) + 银行流水号(20位) + 处理时间(14位) + 失败原因(20位)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Java实现示例**:
|
**代扣文件生成流程**:
|
||||||
```java
|
1. 每日凌晨2点自动生成代扣文件
|
||||||
@Service
|
2. 查询当日待代扣账单数据
|
||||||
public class BankDeductServiceImpl implements BankDeductService {
|
3. 按银行要求格式生成文件内容
|
||||||
|
4. 通过SFTP上传至银行服务器
|
||||||
@Resource
|
5. 记录文件生成和上传日志
|
||||||
private SftpTemplate sftpTemplate;
|
|
||||||
@Resource
|
|
||||||
private BillService billService;
|
|
||||||
|
|
||||||
@Scheduled(cron = "0 0 2 * * ?")
|
|
||||||
public void generateDeductFile() {
|
|
||||||
LocalDate deductDate = LocalDate.now();
|
|
||||||
|
|
||||||
// 获取待代扣账单
|
|
||||||
List<BillDO> 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<BillDO> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 银行实时缴费接口
|
#### 银行实时缴费接口
|
||||||
|
|
||||||
@ -253,45 +211,12 @@ public class BankDeductServiceImpl implements BankDeductService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Java实现示例**:
|
**支付宝支付集成流程**:
|
||||||
```java
|
1. 调用支付宝预创建接口生成支付二维码
|
||||||
@Service
|
2. 前端展示二维码供用户扫码支付
|
||||||
public class AlipayServiceImpl implements AlipayService {
|
3. 支付完成后支付宝发送异步通知
|
||||||
|
4. 系统验证通知签名并更新订单状态
|
||||||
@Resource
|
5. 记录支付日志和账务处理
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 微信支付接口对接
|
### 微信支付接口对接
|
||||||
|
|
||||||
@ -494,34 +419,10 @@ public class CustomerController {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 水表管理API接口
|
### 水表管理API接口
|
||||||
|
|
||||||
@ -563,27 +464,10 @@ public class CustomerServiceImpl implements CustomerService {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Controller代码示例**:
|
**水表管理控制器要点**:
|
||||||
```java
|
- 支持权限控制和参数校验
|
||||||
@RestController
|
- 标准的RESTful接口设计
|
||||||
@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<MeterRespVO> getMeter(@PathVariable("id") Long id) {
|
|
||||||
MeterDO meter = meterService.getMeter(id);
|
|
||||||
return success(BeanUtils.toBean(meter, MeterRespVO.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 抄表记录创建接口
|
#### 抄表记录创建接口
|
||||||
|
|
||||||
@ -615,46 +499,11 @@ public class MeterController {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 抄表数据批量导入接口
|
#### 抄表数据批量导入接口
|
||||||
|
|
||||||
|
|||||||
@ -14,84 +14,82 @@
|
|||||||
- [福建水务营收系统模块功能设计文档](#福建水务营收系统模块功能设计文档)
|
- [福建水务营收系统模块功能设计文档](#福建水务营收系统模块功能设计文档)
|
||||||
- [文档信息](#文档信息)
|
- [文档信息](#文档信息)
|
||||||
- [目录](#目录)
|
- [目录](#目录)
|
||||||
- [一、系统整体架构](#一系统整体架构)
|
- [系统整体架构](#系统整体架构)
|
||||||
- [1.1 系统架构图](#11-系统架构图)
|
- [系统架构图](#系统架构图)
|
||||||
- [1.2 技术架构图](#12-技术架构图)
|
- [技术架构图](#技术架构图)
|
||||||
- [1.3 业务架构图](#13-业务架构图)
|
- [业务架构图](#业务架构图)
|
||||||
- [二、统一平台](#二统一平台)
|
- [统一平台](#统一平台)
|
||||||
- [2.1 单点登录](#21-单点登录)
|
- [单点登录](#单点登录)
|
||||||
- [2.2 系统管理](#22-系统管理)
|
- [系统管理](#系统管理)
|
||||||
- [三、营收系统](#三营收系统)
|
- [营收系统](#营收系统)
|
||||||
- [3.1 系统管理](#31-系统管理)
|
- [系统管理](#系统管理-1)
|
||||||
- [3.2 抄表开账](#32-抄表开账)
|
- [抄表开账](#抄表开账)
|
||||||
- [3.2.1 业务流程图](#321-业务流程图)
|
- [业务流程图](#业务流程图)
|
||||||
- [3.2.2 主要功能](#322-主要功能)
|
- [主要功能](#主要功能)
|
||||||
- [3.2.3 核心接口定义](#323-核心接口定义)
|
- [核心接口定义](#核心接口定义)
|
||||||
- [3.2.4 前端界面设计](#324-前端界面设计)
|
- [前端界面设计](#前端界面设计)
|
||||||
- [3.3 收费管理](#33-收费管理)
|
- [收费管理](#收费管理)
|
||||||
- [3.3.1 业务流程图](#331-业务流程图)
|
- [业务流程图](#业务流程图-1)
|
||||||
- [3.3.2 主要功能](#332-主要功能)
|
- [主要功能](#主要功能-1)
|
||||||
- [3.3.3 核心接口定义](#333-核心接口定义)
|
- [核心接口定义](#核心接口定义-1)
|
||||||
- [3.4 账务处理](#34-账务处理)
|
- [主要功能](#主要功能-2)
|
||||||
- [3.4.1 业务流程图](#341-业务流程图)
|
- [核心接口定义](#核心接口定义-2)
|
||||||
- [3.4.2 主要功能](#342-主要功能)
|
- [发票管理](#发票管理)
|
||||||
- [3.4.3 核心接口定义](#343-核心接口定义)
|
- [业务流程图](#业务流程图-2)
|
||||||
- [3.5 发票管理](#35-发票管理)
|
- [核心接口定义](#核心接口定义-3)
|
||||||
- [3.5.1 业务流程图](#351-业务流程图)
|
- [代收业务](#代收业务)
|
||||||
- [3.5.2 核心接口定义](#352-核心接口定义)
|
- [环卫系统](#环卫系统)
|
||||||
- [3.6 代收业务](#36-代收业务)
|
- [业务工单](#业务工单)
|
||||||
- [3.7 环卫系统](#37-环卫系统)
|
- [表务系统](#表务系统)
|
||||||
- [3.8 业务工单](#38-业务工单)
|
- [表务工单](#表务工单)
|
||||||
- [四、表务系统](#四表务系统)
|
- [表务仓库](#表务仓库)
|
||||||
- [4.1 表务工单](#41-表务工单)
|
- [水表参数与基础信息](#水表参数与基础信息)
|
||||||
- [4.2 表务仓库](#42-表务仓库)
|
- [物联网对接与数据同步](#物联网对接与数据同步)
|
||||||
- [4.3 水表参数与基础信息](#43-水表参数与基础信息)
|
- [报装系统](#报装系统)
|
||||||
- [4.4 物联网对接与数据同步](#44-物联网对接与数据同步)
|
- [报装流程](#报装流程)
|
||||||
- [五、报装系统](#五报装系统)
|
- [一户一表管理](#一户一表管理)
|
||||||
- [5.1 报装流程](#51-报装流程)
|
- [客户服务](#客户服务)
|
||||||
- [5.2 一户一表管理](#52-一户一表管理)
|
- [微信、支付宝服务窗](#微信支付宝服务窗)
|
||||||
- [六、客户服务](#六客户服务)
|
- [历史账单](#历史账单)
|
||||||
- [6.1 微信、支付宝服务窗](#61-微信支付宝服务窗)
|
- [电子发票](#电子发票)
|
||||||
- [6.2 历史账单](#62-历史账单)
|
- [营业网点](#营业网点)
|
||||||
- [6.3 电子发票](#63-电子发票)
|
- [账户流水](#账户流水)
|
||||||
- [6.4 营业网点](#64-营业网点)
|
- [微网厅](#微网厅)
|
||||||
- [6.5 账户流水](#65-账户流水)
|
- [系统配置](#系统配置)
|
||||||
- [6.6 微网厅](#66-微网厅)
|
- [水表参数](#水表参数)
|
||||||
- [七、系统配置](#七系统配置)
|
- [地址参数](#地址参数)
|
||||||
- [7.1 水表参数](#71-水表参数)
|
- [价格体系](#价格体系)
|
||||||
- [7.2 地址参数](#72-地址参数)
|
- [基本配置](#基本配置)
|
||||||
- [7.3 价格体系](#73-价格体系)
|
- [催缴管理](#催缴管理)
|
||||||
- [7.4 基本配置](#74-基本配置)
|
- [用户权限](#用户权限)
|
||||||
- [7.5 催缴管理](#75-催缴管理)
|
- [定时任务](#定时任务)
|
||||||
- [7.6 用户权限](#76-用户权限)
|
- [系统接口](#系统接口)
|
||||||
- [7.7 定时任务](#77-定时任务)
|
- [银行接口](#银行接口)
|
||||||
- [八、系统接口](#八系统接口)
|
- [支付宝/微信接口](#支付宝微信接口)
|
||||||
- [8.1 银行接口](#81-银行接口)
|
- [短信接口](#短信接口)
|
||||||
- [8.2 支付宝/微信接口](#82-支付宝微信接口)
|
- [集抄系统接口](#集抄系统接口)
|
||||||
- [8.3 短信接口](#83-短信接口)
|
- [政务系统接口](#政务系统接口)
|
||||||
- [8.4 集抄系统接口](#84-集抄系统接口)
|
- [消火栓系统接口](#消火栓系统接口)
|
||||||
- [8.5 政务系统接口](#85-政务系统接口)
|
- [其他系统对接](#其他系统对接)
|
||||||
- [8.6 消火栓系统接口](#86-消火栓系统接口)
|
- [统计分析](#统计分析)
|
||||||
- [8.7 其他系统对接](#87-其他系统对接)
|
- [报表查询](#报表查询)
|
||||||
- [九、统计分析](#九统计分析)
|
- [欠费查询](#欠费查询)
|
||||||
- [9.1 报表查询](#91-报表查询)
|
- [缴费记录](#缴费记录)
|
||||||
- [9.2 欠费查询](#92-欠费查询)
|
- [用水分析](#用水分析)
|
||||||
- [9.3 缴费记录](#93-缴费记录)
|
- [工程管理](#工程管理)
|
||||||
- [9.4 用水分析](#94-用水分析)
|
- [工程申请](#工程申请)
|
||||||
- [十、工程管理](#十工程管理)
|
- [工程施工](#工程施工)
|
||||||
- [10.1 工程申请](#101-工程申请)
|
- [工程验收](#工程验收)
|
||||||
- [10.2 工程施工](#102-工程施工)
|
- [工程查询](#工程查询)
|
||||||
- [10.3 工程验收](#103-工程验收)
|
- [抄表APP](#抄表app)
|
||||||
- [10.4 工程查询](#104-工程查询)
|
- [首页功能](#首页功能)
|
||||||
- [十一、抄表APP](#十一抄表app)
|
- [抄表功能](#抄表功能)
|
||||||
- [11.1 首页功能](#111-首页功能)
|
- [工单管理](#工单管理)
|
||||||
- [11.2 抄表功能](#112-抄表功能)
|
- [接口服务](#接口服务)
|
||||||
- [11.3 工单管理](#113-工单管理)
|
- [API市场](#api市场)
|
||||||
- [十二、接口服务](#十二接口服务)
|
- [API管理](#api管理)
|
||||||
- [12.1 API市场](#121-api市场)
|
- [接口权限管理](#接口权限管理)
|
||||||
- [12.2 API管理](#122-api管理)
|
- [系统对外接口](#系统对外接口)
|
||||||
- [12.3 接口权限管理](#123-接口权限管理)
|
|
||||||
- [12.4 系统对外接口](#124-系统对外接口)
|
|
||||||
- [系统集成架构](#系统集成架构)
|
- [系统集成架构](#系统集成架构)
|
||||||
- [前后端集成架构](#前后端集成架构)
|
- [前后端集成架构](#前后端集成架构)
|
||||||
- [技术栈整合方案](#技术栈整合方案)
|
- [技术栈整合方案](#技术栈整合方案)
|
||||||
@ -498,29 +496,13 @@ flowchart TD
|
|||||||
#### 核心接口定义
|
#### 核心接口定义
|
||||||
|
|
||||||
**抄表管理主要接口**:
|
**抄表管理主要接口**:
|
||||||
```java
|
|
||||||
@RestController
|
| 接口名称 | 请求方式 | 功能描述 |
|
||||||
@RequestMapping("/admin-api/water/reading")
|
|---------|---------|---------|
|
||||||
@Tag(name = "管理后台 - 抄表管理")
|
| `/admin-api/water/reading/create` | POST | 创建抄表记录 |
|
||||||
public class MeterReadingController {
|
| `/admin-api/water/reading/batch-create` | POST | 批量创建抄表记录 |
|
||||||
|
| `/admin-api/water/reading/review` | POST | 抄表数据复核 |
|
||||||
@PostMapping("/create")
|
| `/admin-api/water/reading/generate-bill` | POST | 生成账单 |
|
||||||
@Operation(summary = "创建抄表记录")
|
|
||||||
public CommonResult<Long> createReading(@Valid @RequestBody MeterReadingSaveReqVO createReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/batch-create")
|
|
||||||
@Operation(summary = "批量创建抄表记录")
|
|
||||||
public CommonResult<MeterReadingBatchRespVO> batchCreateReading(@Valid @RequestBody MeterReadingBatchReqVO batchReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/review")
|
|
||||||
@Operation(summary = "抄表数据复核")
|
|
||||||
public CommonResult<Boolean> reviewReading(@Valid @RequestBody MeterReadingReviewReqVO reviewReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/generate-bill")
|
|
||||||
@Operation(summary = "生成账单")
|
|
||||||
public CommonResult<BillGenerateRespVO> generateBill(@Valid @RequestBody ReadingBillReqVO reqVO);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**接口设计要点**:
|
**接口设计要点**:
|
||||||
- 遵循RESTful设计规范,统一的请求响应格式
|
- 遵循RESTful设计规范,统一的请求响应格式
|
||||||
@ -530,58 +512,12 @@ public class MeterReadingController {
|
|||||||
|
|
||||||
#### 前端界面设计
|
#### 前端界面设计
|
||||||
|
|
||||||
**抄表管理页面结构**:
|
**前端页面功能设计**:
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<ContentWrap>
|
|
||||||
<!-- 查询条件 -->
|
|
||||||
<el-form class="search-form" inline>
|
|
||||||
<el-form-item label="抄表日期">
|
|
||||||
<el-date-picker v-model="queryParams.readingDate" type="daterange" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="抄表状态">
|
|
||||||
<el-select v-model="queryParams.status">
|
|
||||||
<el-option label="待复核" value="pending" />
|
|
||||||
<el-option label="已通过" value="approved" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
**页面组件结构**:
|
||||||
<el-row class="mb-10px">
|
- 查询条件区域:抄表日期范围选择、抄表状态筛选
|
||||||
<el-button type="primary" @click="handleAdd">新增抄表</el-button>
|
- 操作按钮区域:新增抄表、批量抄表、数据导出
|
||||||
<el-button type="success" @click="handleBatchAdd">批量抄表</el-button>
|
- 数据表格区域:抄表记录列表展示和操作
|
||||||
<el-button type="warning" @click="handleExport">导出数据</el-button>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
|
||||||
<el-table v-loading="loading" :data="readingList">
|
|
||||||
<el-table-column prop="readingCode" label="抄表编号" width="180" />
|
|
||||||
<el-table-column prop="customerName" label="客户名称" />
|
|
||||||
<el-table-column prop="meterCode" label="水表编号" />
|
|
||||||
<el-table-column prop="readingValue" label="本次读数" />
|
|
||||||
<el-table-column prop="waterUsage" label="用水量" />
|
|
||||||
<el-table-column prop="readingDate" label="抄表日期" />
|
|
||||||
<el-table-column prop="status" label="状态">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<dict-tag :type="DICT_TYPE.READING_STATUS" :value="row.status" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button link @click="handleUpdate(row)">编辑</el-button>
|
|
||||||
<el-button link @click="handleReview(row)">复核</el-button>
|
|
||||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</ContentWrap>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
**前端页面功能特性**:
|
**前端页面功能特性**:
|
||||||
- 响应式设计:基于Element Plus的现代化UI组件
|
- 响应式设计:基于Element Plus的现代化UI组件
|
||||||
@ -671,29 +607,13 @@ flowchart TD
|
|||||||
#### 核心接口定义
|
#### 核心接口定义
|
||||||
|
|
||||||
**缴费管理主要接口**:
|
**缴费管理主要接口**:
|
||||||
```java
|
|
||||||
@RestController
|
| 接口名称 | 请求方式 | 功能描述 |
|
||||||
@RequestMapping("/admin-api/water/payment")
|
|---------|---------|---------|
|
||||||
@Tag(name = "管理后台 - 缴费管理")
|
| `/admin-api/water/payment/create` | POST | 创建缴费记录 |
|
||||||
public class PaymentController {
|
| `/admin-api/water/payment/cash-payment` | POST | 现金缴费 |
|
||||||
|
| `/admin-api/water/payment/online-payment` | POST | 在线支付 |
|
||||||
@PostMapping("/create")
|
| `/admin-api/water/payment/prepaid-payment` | POST | 预存款缴费 |
|
||||||
@Operation(summary = "创建缴费记录")
|
|
||||||
public CommonResult<PaymentRespVO> createPayment(@Valid @RequestBody PaymentCreateReqVO createReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/cash-payment")
|
|
||||||
@Operation(summary = "现金缴费")
|
|
||||||
public CommonResult<PaymentRespVO> cashPayment(@Valid @RequestBody CashPaymentReqVO cashReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/online-payment")
|
|
||||||
@Operation(summary = "在线支付")
|
|
||||||
public CommonResult<OnlinePaymentRespVO> onlinePayment(@Valid @RequestBody OnlinePaymentReqVO onlineReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/prepaid-payment")
|
|
||||||
@Operation(summary = "预存款缴费")
|
|
||||||
public CommonResult<PaymentRespVO> prepaidPayment(@Valid @RequestBody PrepaidPaymentReqVO prepaidReqVO);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**接口设计特点**:
|
**接口设计特点**:
|
||||||
- 支持多种缴费方式:现金、银行卡、在线支付、预存款
|
- 支持多种缴费方式:现金、银行卡、在线支付、预存款
|
||||||
@ -704,48 +624,12 @@ public class PaymentController {
|
|||||||
|
|
||||||
#### 前端界面设计
|
#### 前端界面设计
|
||||||
|
|
||||||
**缴费管理页面结构**:
|
**缴费管理页面功能设计**:
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<ContentWrap>
|
|
||||||
<!-- 客户查询 -->
|
|
||||||
<el-form class="search-form" inline>
|
|
||||||
<el-form-item label="客户编号">
|
|
||||||
<el-input v-model="queryParams.customerCode" placeholder="请输入客户编号" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="客户姓名">
|
|
||||||
<el-input v-model="queryParams.customerName" placeholder="请输入客户姓名" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<!-- 账单信息 -->
|
**页面组件结构**:
|
||||||
<el-table v-loading="loading" :data="billList" @selection-change="handleSelectionChange">
|
- 客户查询区域:客户编号输入、客户姓名输入
|
||||||
<el-table-column type="selection" width="55" />
|
- 账单信息区域:待缴费账单列表展示和选择
|
||||||
<el-table-column prop="billCode" label="账单编号" />
|
- 缴费操作区域:金额统计和多种缴费方式选择
|
||||||
<el-table-column prop="billDate" label="账期" />
|
|
||||||
<el-table-column prop="waterUsage" label="用水量" />
|
|
||||||
<el-table-column prop="totalAmount" label="应缴金额" />
|
|
||||||
<el-table-column prop="balanceAmount" label="欠费金额" />
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 缴费操作 -->
|
|
||||||
<el-row class="payment-actions">
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-statistic title="选中金额" :value="selectedAmount" prefix="¥" />
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12" class="text-right">
|
|
||||||
<el-button type="success" @click="handleCashPayment">现金缴费</el-button>
|
|
||||||
<el-button type="primary" @click="handleOnlinePayment">在线支付</el-button>
|
|
||||||
<el-button type="warning" @click="handlePrepaidPayment">预存扣费</el-button>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</ContentWrap>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 账务处理
|
### 账务处理
|
||||||
|
|
||||||
@ -811,29 +695,13 @@ flowchart TD
|
|||||||
|
|
||||||
#### 核心接口定义
|
#### 核心接口定义
|
||||||
|
|
||||||
```java
|
**账务处理主要接口**:
|
||||||
@RestController
|
|
||||||
@RequestMapping("/admin-api/water/account")
|
| 接口名称 | 请求方式 | 功能描述 |
|
||||||
@Tag(name = "管理后台 - 账务处理")
|
|---------|---------|---------|
|
||||||
@Validated
|
| `/admin-api/water/account/adjust` | POST | 账务调整 |
|
||||||
public class AccountProcessController {
|
| `/admin-api/water/account/refund` | POST | 退款处理 |
|
||||||
|
| `/admin-api/water/account/write-off` | POST | 销账处理 |
|
||||||
@PostMapping("/adjust")
|
|
||||||
@Operation(summary = "账务调整")
|
|
||||||
@PreAuthorize("@ss.hasPermission('water:account:adjust')")
|
|
||||||
public CommonResult<Boolean> adjustAccount(@Valid @RequestBody AccountAdjustReqVO adjustReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/refund")
|
|
||||||
@Operation(summary = "退款处理")
|
|
||||||
@PreAuthorize("@ss.hasPermission('water:account:refund')")
|
|
||||||
public CommonResult<Boolean> processRefund(@Valid @RequestBody RefundProcessReqVO refundReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/write-off")
|
|
||||||
@Operation(summary = "销账处理")
|
|
||||||
@PreAuthorize("@ss.hasPermission('water:account:write-off')")
|
|
||||||
public CommonResult<Boolean> writeOffAccount(@Valid @RequestBody WriteOffReqVO writeOffReqVO);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 发票管理
|
### 发票管理
|
||||||
|
|
||||||
@ -869,26 +737,13 @@ flowchart TD
|
|||||||
|
|
||||||
#### 核心接口定义
|
#### 核心接口定义
|
||||||
|
|
||||||
```java
|
**发票管理主要接口**:
|
||||||
@RestController
|
|
||||||
@RequestMapping("/admin-api/water/invoice")
|
| 接口名称 | 请求方式 | 功能描述 |
|
||||||
@Tag(name = "管理后台 - 发票管理")
|
|---------|---------|---------|
|
||||||
@Validated
|
| `/admin-api/water/invoice/generate` | POST | 生成发票 |
|
||||||
public class InvoiceController {
|
| `/admin-api/water/invoice/print` | POST | 打印发票 |
|
||||||
|
| `/admin-api/water/invoice/cancel` | POST | 发票作废 |
|
||||||
@PostMapping("/generate")
|
|
||||||
@Operation(summary = "生成发票")
|
|
||||||
public CommonResult<InvoiceRespVO> generateInvoice(@Valid @RequestBody InvoiceGenerateReqVO generateReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/print")
|
|
||||||
@Operation(summary = "打印发票")
|
|
||||||
public CommonResult<Boolean> printInvoice(@Valid @RequestBody InvoicePrintReqVO printReqVO);
|
|
||||||
|
|
||||||
@PostMapping("/cancel")
|
|
||||||
@Operation(summary = "发票作废")
|
|
||||||
public CommonResult<Boolean> cancelInvoice(@Valid @RequestBody InvoiceCancelReqVO cancelReqVO);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 代收业务
|
### 代收业务
|
||||||
|
|
||||||
|
|||||||
@ -349,78 +349,20 @@ GRANT SELECT ON water_customer_masked TO water_normal_user;
|
|||||||
|
|
||||||
### Spring Security安全配置
|
### Spring Security安全配置
|
||||||
|
|
||||||
#### 认证配置
|
#### 认证配置要点
|
||||||
```java
|
**Spring Security核心配置**:
|
||||||
@Configuration
|
- 使用国密SM3哈希算法进行密码加密
|
||||||
@EnableWebSecurity
|
- 配置JWT身份验证过滤器
|
||||||
@EnableMethodSecurity(prePostEnabled = true)
|
- 设置CSRF防护和HttpOnly Cookie
|
||||||
public class SecurityConfig {
|
- 配置请求授权规则和无状态会话管理
|
||||||
|
- 启用方法级安全注解支持
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
// 使用国密SM3哈希算法
|
|
||||||
return new SM3PasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
|
|
||||||
return new JwtAuthenticationTokenFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
||||||
return http
|
|
||||||
// CSRF防护
|
|
||||||
.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
|
|
||||||
// 请求授权
|
|
||||||
.authorizeHttpRequests(auth -> auth
|
|
||||||
.requestMatchers("/api/login", "/api/register").permitAll()
|
|
||||||
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
// JWT过滤器
|
|
||||||
.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
|
|
||||||
// 会话管理
|
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 多因素认证
|
#### 多因素认证实现
|
||||||
```java
|
**短信验证码服务要点**:
|
||||||
@Service
|
- 生成6位随机验证码并缓存到Redis
|
||||||
public class MFAService {
|
- 设置5分钟过期时间防止验证码滥用
|
||||||
|
- 集成短信服务提供商发送验证码
|
||||||
@Autowired
|
- 验证时比对缓存验证码并及时清理
|
||||||
private SmsService smsService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
|
||||||
|
|
||||||
public boolean sendSmsCode(String phone) {
|
|
||||||
String code = generateRandomCode();
|
|
||||||
String key = "mfa:sms:" + phone;
|
|
||||||
|
|
||||||
// 存储验证码,5分钟过期
|
|
||||||
redisTemplate.opsForValue().set(key, code, Duration.ofMinutes(5));
|
|
||||||
|
|
||||||
// 发送短信
|
|
||||||
return smsService.send(phone, "您的验证码是:" + code + ",5分钟内有效。");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean verifySmsCode(String phone, String code) {
|
|
||||||
String key = "mfa:sms:" + phone;
|
|
||||||
String storedCode = redisTemplate.opsForValue().get(key);
|
|
||||||
|
|
||||||
if (storedCode != null && storedCode.equals(code)) {
|
|
||||||
redisTemplate.delete(key);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据传输安全
|
### 数据传输安全
|
||||||
|
|
||||||
@ -438,90 +380,28 @@ server:
|
|||||||
protocols: TLSv1.2,TLSv1.3
|
protocols: TLSv1.2,TLSv1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 敏感数据加密
|
#### 敏感数据加密实现
|
||||||
```java
|
**数据加密服务要点**:
|
||||||
@Component
|
- 采用国密SM4对称加密算法
|
||||||
public class DataEncryptionService {
|
- 实现敏感数据的加密和解密方法
|
||||||
|
- 统一的异常处理和错误提示
|
||||||
private final SM4Cipher sm4Cipher = new SM4Cipher();
|
- 支持身份证号、手机号等敏感信息加密
|
||||||
|
|
||||||
public String encryptSensitiveData(String plaintext) {
|
|
||||||
try {
|
|
||||||
return sm4Cipher.encrypt(plaintext);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SecurityException("数据加密失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String decryptSensitiveData(String ciphertext) {
|
|
||||||
try {
|
|
||||||
return sm4Cipher.decrypt(ciphertext);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SecurityException("数据解密失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 接口安全防护
|
### 接口安全防护
|
||||||
|
|
||||||
#### 接口签名验证
|
#### 接口签名验证实现
|
||||||
```java
|
**API签名验证要点**:
|
||||||
@Component
|
- 基于时间戳、随机数和请求体生成签名
|
||||||
public class ApiSignatureValidator {
|
- 使用国密SM3哈希算法计算签名值
|
||||||
|
- 检查时间戳有效性防止重放攻击
|
||||||
public boolean validateSignature(HttpServletRequest request) {
|
- 比对客户端签名和服务端计算签名
|
||||||
String timestamp = request.getHeader("X-Timestamp");
|
|
||||||
String nonce = request.getHeader("X-Nonce");
|
|
||||||
String signature = request.getHeader("X-Signature");
|
|
||||||
String body = getRequestBody(request);
|
|
||||||
|
|
||||||
// 检查时间戳,防止重放攻击
|
|
||||||
if (isTimestampExpired(timestamp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成签名
|
|
||||||
String expectedSignature = generateSignature(timestamp, nonce, body);
|
|
||||||
|
|
||||||
// 验证签名
|
|
||||||
return signature.equals(expectedSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateSignature(String timestamp, String nonce, String body) {
|
|
||||||
String message = timestamp + nonce + body;
|
|
||||||
return SM3Utils.hash(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 接口限流防护
|
#### 接口限流防护实现
|
||||||
```java
|
**限流服务要点**:
|
||||||
@Component
|
- 基于Redis实现分布式限流控制
|
||||||
public class RateLimitService {
|
- 支持按IP、用户、接口等维度限流
|
||||||
|
- 采用滑动窗口算法统计请求频率
|
||||||
@Autowired
|
- 超过限制时返回429状态码
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
|
||||||
|
|
||||||
public boolean isAllowed(String key, int limit, Duration window) {
|
|
||||||
String redisKey = "rate_limit:" + key;
|
|
||||||
String current = redisTemplate.opsForValue().get(redisKey);
|
|
||||||
|
|
||||||
if (current == null) {
|
|
||||||
redisTemplate.opsForValue().set(redisKey, "1", window);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int count = Integer.parseInt(current);
|
|
||||||
if (count < limit) {
|
|
||||||
redisTemplate.opsForValue().increment(redisKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 网络安全设计
|
## 网络安全设计
|
||||||
|
|
||||||
|
|||||||
@ -478,100 +478,19 @@ yudao:
|
|||||||
template-code: SMS_123456
|
template-code: SMS_123456
|
||||||
```
|
```
|
||||||
|
|
||||||
**多租户配置实现:**
|
**多租户配置要点**:
|
||||||
```java
|
- 基于MyBatis-Plus的TenantLineInnerInterceptor实现数据隔离
|
||||||
@Component
|
- 自动在SQL中添加tenant_id条件过滤数据
|
||||||
@Slf4j
|
- 支持配置忽略多租户的系统表
|
||||||
public class WaterTenantConfiguration {
|
- 通过TenantContextHolder获取当前租户上下文
|
||||||
|
- 集成分页插件支持多租户分页查询
|
||||||
@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
|
- 基于Spring Security实现统一的权限控制
|
||||||
@EnableWebSecurity
|
- 支持方法级权限注解@PreAuthorize
|
||||||
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
- 配置静态资源和公开接口的匿名访问
|
||||||
@Configuration
|
- 集成JWT Token认证过滤器
|
||||||
public class WaterSecurityConfiguration {
|
- 采用无状态会话管理,禁用CSRF和CORS
|
||||||
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 客户端技术架构
|
### 客户端技术架构
|
||||||
- 前端框架:基于yudao-ui-admin-vue3框架定制开发
|
- 前端框架:基于yudao-ui-admin-vue3框架定制开发
|
||||||
@ -588,239 +507,35 @@ public class WaterSecurityConfiguration {
|
|||||||
- 动态表格:支持拖拽、排序、筛选
|
- 动态表格:支持拖拽、排序、筛选
|
||||||
- 代码规范:ESLint + Prettier,标准化代码风格
|
- 代码规范:ESLint + Prettier,标准化代码风格
|
||||||
|
|
||||||
#### 前端项目结构配置
|
#### 前端项目配置要点
|
||||||
|
|
||||||
**package.json 依赖配置:**
|
**核心依赖和技术栈**:
|
||||||
```json
|
- Vue 3.x + TypeScript:现代化前端框架
|
||||||
{
|
- Element Plus:企业级UI组件库
|
||||||
"name": "water-biz-ui-admin",
|
- Pinia:新一代状态管理
|
||||||
"version": "1.0.0",
|
- Vue Router:路由管理
|
||||||
"description": "福建水务营收系统管理后台",
|
- Axios:HTTP请求库
|
||||||
"dependencies": {
|
- ECharts:数据可视化图表
|
||||||
"@element-plus/icons-vue": "^2.1.0",
|
- Vite:快速构建工具
|
||||||
"@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'
|
- 设置开发服务器代理转发API请求
|
||||||
import vue from '@vitejs/plugin-vue'
|
- 优化构建输出,支持代码分割和资源压缩
|
||||||
import { resolve } from 'path'
|
- 配置静态资源处理和缓存策略
|
||||||
|
|
||||||
export default defineConfig({
|
**状态管理要点**:
|
||||||
plugins: [vue()],
|
- 使用Pinia管理用户信息、权限和角色
|
||||||
resolve: {
|
- 提供用户登录、登出和权限检查方法
|
||||||
alias: {
|
- 支持响应式的用户状态更新
|
||||||
'@': resolve(__dirname, 'src'),
|
- 集成Token管理和自动刷新机制
|
||||||
'~': 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 用户状态管理:**
|
**HTTP请求封装要点**:
|
||||||
```typescript
|
- 基于Axios封装统一的HTTP请求工具
|
||||||
import { defineStore } from 'pinia'
|
- 自动添加认证Token和租户ID头部
|
||||||
import { ref, computed } from 'vue'
|
- 实现请求和响应拦截器处理公共逻辑
|
||||||
import { UserApi, UserVO } from '@/api/system/user'
|
- 统一的错误处理和用户提示
|
||||||
import { getAccessToken, removeToken } from '@/utils/auth'
|
- 支持Token过期自动刷新机制
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', () => {
|
|
||||||
const userInfo = ref<UserVO>()
|
|
||||||
const permissions = ref<string[]>([])
|
|
||||||
const roles = ref<string[]>([])
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
### 前端技术架构详细设计
|
### 前端技术架构详细设计
|
||||||
|
|
||||||
#### Web管理端架构 (yudao-ui-admin-vue3)
|
#### Web管理端架构 (yudao-ui-admin-vue3)
|
||||||
@ -913,46 +628,11 @@ yudao-ui-admin-vue3/
|
|||||||
└── package.json # 项目依赖
|
└── package.json # 项目依赖
|
||||||
```
|
```
|
||||||
|
|
||||||
**核心配置示例:**
|
**核心配置要点**:
|
||||||
```typescript
|
- Vite构建工具配置,支持路径别名和代理
|
||||||
// vite.config.ts - Vite构建配置
|
- 开发服务器配置,代理后端API请求
|
||||||
import { defineConfig } from 'vite'
|
- 构建优化配置,支持代码分割和资源压缩
|
||||||
import vue from '@vitejs/plugin-vue'
|
- TypeScript配置,提供类型检查和智能提示
|
||||||
import { resolve } from 'path'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [vue()],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': resolve(__dirname, 'src'),
|
|
||||||
'@/api': resolve(__dirname, 'src/api'),
|
|
||||||
'@/components': resolve(__dirname, 'src/components'),
|
|
||||||
'@/utils': resolve(__dirname, 'src/utils')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
port: 80,
|
|
||||||
proxy: {
|
|
||||||
'/admin-api': {
|
|
||||||
target: 'http://127.0.0.1:48080',
|
|
||||||
changeOrigin: true,
|
|
||||||
rewrite: (path) => path.replace(/^\/admin-api/, '/admin-api')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
outDir: 'dist',
|
|
||||||
sourcemap: false,
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
chunkFileNames: 'js/[name]-[hash].js',
|
|
||||||
entryFileNames: 'js/[name]-[hash].js',
|
|
||||||
assetFileNames: '[ext]/[name]-[hash].[ext]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 移动端架构 (uni-app)
|
#### 移动端架构 (uni-app)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user