# 福建水务营收系统接口设计文档 ## 文档信息 | 项目信息 | 详情 | |---------|------| | **项目名称** | 福建水务营收系统 | | **文档类型** | 概要设计文档 | | **技术框架** | RuoYi-Vue-Pro + yudao-ui-admin-vue3 | | **文档版本** | v1.0 | | **编写日期** | 2024-12-19 | | **文档状态** | 🟡 进行中 | ## 目录 - [1. 接口概述](#1-接口概述) - [2. 外部接口](#2-外部接口) - [3. 内部接口](#3-内部接口) - [4. 接口标准](#4-接口标准) ## 1. 接口概述 福建水务业务系统提供丰富的接口,用于与外部系统集成以及系统内部各模块间的数据交换。接口设计遵循标准化、安全性、可扩展性的原则,基于RuoYi-Vue-Pro框架采用RESTful风格设计,支持JSON数据格式。 ### 1.1 接口设计原则 - **统一性**:所有接口遵循统一的设计规范和数据格式 - **安全性**:接口通过认证授权、参数校验等机制保障安全 - **可维护性**:接口文档完善,便于维护和升级 - **兼容性**:接口设计考虑版本兼容,支持平滑升级 - **性能优化**:接口设计考虑性能,支持缓存、分页等机制 ### 1.2 RESTful API规范 系统API接口采用RESTful风格设计,主要规范如下: #### 1.2.1 资源命名 - 使用名词复数表示资源集合,如`/users`、`/meters` - 使用资源ID标识特定资源,如`/users/1`、`/meters/123` - 资源层级关系通过路径嵌套表示,如`/users/1/meters` #### 1.2.2 HTTP方法 - GET:获取资源 - POST:创建资源 - PUT:更新资源(全量更新) - PATCH:部分更新资源 - DELETE:删除资源 #### 1.2.3 状态码 - 200 OK:请求成功 - 201 Created:资源创建成功 - 400 Bad Request:请求参数错误 - 401 Unauthorized:未授权 - 403 Forbidden:权限不足 - 404 Not Found:资源不存在 - 500 Internal Server Error:服务器内部错误 #### 1.2.4 响应格式 系统统一采用以下JSON格式响应: ```json { "code": 0, // 业务状态码,0表示成功,非0表示失败 "data": {}, // 响应数据 "msg": "success" // 响应消息 } ``` 分页查询响应格式: ```json { "code": 0, "data": { "list": [], // 数据列表 "total": 100, // 总记录数 "pageNum": 1, // 当前页码 "pageSize": 10 // 每页记录数 }, "msg": "success" } ``` ### 1.3 接口文档 系统使用Knife4j(基于Swagger)自动生成API文档,文档地址为:`http://{系统地址}/doc.html`。 主要特点: - 在线接口文档:支持在线查看接口定义 - 接口调试:支持在线调试接口 - 文档导出:支持导出OpenAPI规范文档 - 权限控制:支持对接口文档的访问控制 ## 2. 外部接口 ### 2.1 银行接口对接 #### 2.1.1 银行代扣接口 **功能描述**:通过银行系统自动从用户账户中扣除水费。 **接口详情**: - **接口方式**:文件交换(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 银行实时缴费接口 **功能描述**:用户在银行柜台、网上银行或手机银行实时缴纳水费。 **接口详情**: - **接口方式**:HTTP POST - **请求URL**:`https://bank.api.com/payment/water-fee` - **数据格式**:JSON - **认证方式**:API Key + 签名 **请求参数**: ```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" } } ``` ### 2.2 支付宝接口对接 **功能描述**:用户通过支付宝缴纳水费,支持扫码支付和H5支付。 **接口详情**: - **接口方式**:HTTP POST - **支付方式**:统一收单交易预创建(alipay.trade.precreate) - **数据格式**:JSON - **认证方式**:RSA2签名 **预创建支付请求参数**: ```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 短信接口 **功能描述**:向用户发送各类业务通知短信。 **接口规范**: - 接口方式:HTTP接口 - 数据格式:JSON - 交换频率:实时 ### 2.5 物联网集抄平台接口 **功能描述**:与物联网集抄平台交互,获取智能水表数据。 **接口规范**: - 接口方式:HTTP接口或WebService - 数据格式:JSON或XML - 交换频率:定时或实时 ## 3. 内部接口 ### 3.1 客户管理API接口 #### 3.1.1 客户信息查询接口 **功能描述**:根据客户ID查询客户详细信息。 **接口详情**: - **请求方式**:GET - **请求路径**:`/admin-api/water/customer/{id}` - **请求头**:`Authorization: Bearer {token}` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | 示例 | |-------|------|------|------|------| | 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" } } ``` **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.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 - **请求路径**:`/admin-api/water/meter/{id}` - **请求头**:`Authorization: Bearer {token}` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | 示例 | |-------|------|------|------|------| | 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": "张三" } } ``` **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.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 - **请求路径**:`/admin-api/water/bill/page` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | 示例 | |-------|------|------|------|------| | 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 } } ``` #### 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 工单接口 #### 3.4.1 工单创建接口 **功能描述**:创建业务工单。 **接口规范**: - 请求方式:POST - 请求路径:/api/workorders - 请求/返回格式:JSON #### 3.4.2 工单状态更新接口 **功能描述**:更新工单处理状态。 **接口规范**: - 请求方式:PUT - 请求路径:/api/workorders/{workorderId}/status - 请求/返回格式:JSON ## 4. 接口标准 ### 4.1 接口协议 系统接口主要采用以下协议: - **RESTful API**:适用于系统内部模块间的交互以及移动应用等轻量级客户端 - **WebService**:适用于与外部系统的集成,特别是银行等传统机构 - **消息队列**:适用于异步处理的场景,如批量数据处理、通知推送等 ### 4.2 数据格式 接口数据主要采用以下格式: - **JSON**:主要用于RESTful API接口,结构简单清晰,适合Web应用 - **XML**:主要用于WebService接口,兼容性好,适合与传统系统对接 - **文本文件**:主要用于批量数据交换,如银行代扣文件等 ### 4.3 接口安全设计 接口安全采用多层防护机制: #### 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