1335 lines
36 KiB
Markdown
1335 lines
36 KiB
Markdown
# 福建水务营收系统接口设计文档
|
||
|
||
## 文档信息
|
||
| 项目信息 | 详情 |
|
||
|---------|------|
|
||
| **项目名称** | 福建水务营收系统 |
|
||
| **文档类型** | 概要设计文档 |
|
||
| **技术框架** | 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<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();
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 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<CustomerRespVO> 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<MeterRespVO> 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<BillGenerateDetailVO> successList = new ArrayList<>();
|
||
List<BillGenerateDetailVO> 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<AuthLoginRespVO> 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<String> 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<Long> 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<PageResult<CustomerVO>>({ url: '/water/customer/page', params })
|
||
},
|
||
|
||
// 获取客户详情
|
||
getCustomer: (id: number) => {
|
||
return request.get<CustomerVO>({ url: `/water/customer/${id}` })
|
||
},
|
||
|
||
// 创建客户
|
||
createCustomer: (data: CustomerSaveReqVO) => {
|
||
return request.post<number>({ url: '/water/customer/create', data })
|
||
},
|
||
|
||
// 更新客户
|
||
updateCustomer: (data: CustomerSaveReqVO) => {
|
||
return request.put<void>({ url: '/water/customer/update', data })
|
||
},
|
||
|
||
// 删除客户
|
||
deleteCustomer: (id: number) => {
|
||
return request.delete<void>({ url: `/water/customer/delete?id=${id}` })
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4.5.2 Vue组件使用示例
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { CustomerApi, CustomerVO } from '@/api/water/customer'
|
||
import { formatDate } from '@/utils/formatTime'
|
||
|
||
const customerList = ref<CustomerVO[]>([])
|
||
const loading = ref(true)
|
||
const total = ref(0)
|
||
const queryParams = ref({
|
||
pageNo: 1,
|
||
pageSize: 10,
|
||
customerName: '',
|
||
customerCode: ''
|
||
})
|
||
|
||
const getCustomerList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const data = await CustomerApi.getCustomerPage(queryParams.value)
|
||
customerList.value = data.list
|
||
total.value = data.total
|
||
} catch (error) {
|
||
console.error('获取客户列表失败:', error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const handleQuery = () => {
|
||
queryParams.value.pageNo = 1
|
||
getCustomerList()
|
||
}
|
||
|
||
const handleReset = () => {
|
||
queryParams.value = {
|
||
pageNo: 1,
|
||
pageSize: 10,
|
||
customerName: '',
|
||
customerCode: ''
|
||
}
|
||
getCustomerList()
|
||
}
|
||
|
||
onMounted(() => {
|
||
getCustomerList()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="app-container">
|
||
<!-- 查询表单 -->
|
||
<el-form :model="queryParams" ref="queryFormRef" inline>
|
||
<el-form-item label="客户名称" prop="customerName">
|
||
<el-input v-model="queryParams.customerName" placeholder="请输入客户名称" />
|
||
</el-form-item>
|
||
<el-form-item label="客户编号" prop="customerCode">
|
||
<el-input v-model="queryParams.customerCode" placeholder="请输入客户编号" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 数据表格 -->
|
||
<el-table v-loading="loading" :data="customerList">
|
||
<el-table-column label="客户编号" prop="customerCode" />
|
||
<el-table-column label="客户名称" prop="customerName" />
|
||
<el-table-column label="联系电话" prop="phone" />
|
||
<el-table-column label="创建时间" prop="createTime" :formatter="formatDate" />
|
||
</el-table>
|
||
|
||
<!-- 分页组件 -->
|
||
<Pagination
|
||
:total="total"
|
||
v-model:page="queryParams.pageNo"
|
||
v-model:limit="queryParams.pageSize"
|
||
@pagination="getCustomerList"
|
||
/>
|
||
</div>
|
||
</template>
|
||
``` |