36 KiB
福建水务营收系统接口设计文档
文档信息
| 项目信息 | 详情 |
|---|---|
| 项目名称 | 福建水务营收系统 |
| 文档类型 | 概要设计文档 |
| 技术框架 | RuoYi-Vue-Pro + yudao-ui-admin-vue3 |
| 文档版本 | v1.0 |
| 编写日期 | 2024-12-19 |
| 文档状态 | 🟡 进行中 |
目录
接口概述
福建水务业务系统提供丰富的接口,用于与外部系统集成以及系统内部各模块间的数据交换。接口设计遵循标准化、安全性、可扩展性的原则,基于RuoYi-Vue-Pro框架采用RESTful风格设计,支持JSON数据格式。
接口设计原则
- 统一性:所有接口遵循统一的设计规范和数据格式
- 安全性:接口通过认证授权、参数校验等机制保障安全
- 可维护性:接口文档完善,便于维护和升级
- 兼容性:接口设计考虑版本兼容,支持平滑升级
- 性能优化:接口设计考虑性能,支持缓存、分页等机制
RESTful API规范
系统API接口采用RESTful风格设计,主要规范如下:
资源命名
- 使用名词复数表示资源集合,如
/users、/meters - 使用资源ID标识特定资源,如
/users/1、/meters/123 - 资源层级关系通过路径嵌套表示,如
/users/1/meters
HTTP方法
- GET:获取资源
- POST:创建资源
- PUT:更新资源(全量更新)
- PATCH:部分更新资源
- DELETE:删除资源
状态码
- 200 OK:请求成功
- 201 Created:资源创建成功
- 400 Bad Request:请求参数错误
- 401 Unauthorized:未授权
- 403 Forbidden:权限不足
- 404 Not Found:资源不存在
- 500 Internal Server Error:服务器内部错误
响应格式
系统统一采用以下JSON格式响应:
{
"code": 0, // 业务状态码,0表示成功,非0表示失败
"data": {}, // 响应数据
"msg": "success" // 响应消息
}
分页查询响应格式:
{
"code": 0,
"data": {
"list": [], // 数据列表
"total": 100, // 总记录数
"pageNum": 1, // 当前页码
"pageSize": 10 // 每页记录数
},
"msg": "success"
}
接口文档
系统使用Knife4j(基于Swagger)自动生成API文档,文档地址为:http://{系统地址}/doc.html。
主要特点:
- 在线接口文档:支持在线查看接口定义
- 接口调试:支持在线调试接口
- 文档导出:支持导出OpenAPI规范文档
- 权限控制:支持对接口文档的访问控制
外部接口
银行接口对接
银行代扣接口
功能描述:通过银行系统自动从用户账户中扣除水费。
接口详情:
- 接口方式:文件交换(FTP/SFTP)
- 数据格式:定长文本文件
- 交换频率:每日凌晨2:00
- 文件编码:GBK
代扣文件格式:
记录类型(1位) + 客户号(12位) + 户名(30位) + 银行账号(20位) + 扣款金额(12位,含2位小数) + 账期(6位) + 保留字段(19位)
代扣文件示例:
1C00000000001张三 62172511001234567890000009180202412
1C00000000002李四 62172511001234567891000015460202412
回盘文件格式:
记录类型(1位) + 客户号(12位) + 银行账号(20位) + 扣款金额(12位) + 处理状态(1位) + 银行流水号(20位) + 处理时间(14位) + 失败原因(20位)
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();
}
}
银行实时缴费接口
功能描述:用户在银行柜台、网上银行或手机银行实时缴纳水费。
接口详情:
- 接口方式:HTTP POST
- 请求URL:
https://bank.api.com/payment/water-fee - 数据格式:JSON
- 认证方式:API Key + 签名
请求参数:
{
"merchantId": "WATER001",
"customerCode": "C001",
"billCodes": ["B202412190001"],
"totalAmount": 91.80,
"bankAccount": "6217251100123456789",
"customerName": "张三",
"timestamp": "20241219103000",
"signature": "ABC123DEF456..."
}
响应参数:
{
"resultCode": "0000",
"resultMsg": "交易成功",
"data": {
"transactionId": "TXN20241219001",
"paymentTime": "20241219103001",
"bankSerial": "BNK20241219001234"
}
}
支付宝接口对接
功能描述:用户通过支付宝缴纳水费,支持扫码支付和H5支付。
接口详情:
- 接口方式:HTTP POST
- 支付方式:统一收单交易预创建(alipay.trade.precreate)
- 数据格式:JSON
- 认证方式:RSA2签名
预创建支付请求参数:
{
"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"
}
}
支付宝响应参数:
{
"alipay_trade_precreate_response": {
"code": "10000",
"msg": "Success",
"out_trade_no": "P202412190002",
"qr_code": "https://qr.alipay.com/bax08945xtdnfwgqmwi200b4"
},
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
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());
}
}
}
微信支付接口对接
功能描述:用户通过微信支付缴纳水费,支持扫码支付和小程序支付。
接口详情:
- 接口方式:HTTP POST
- 支付方式:Native支付(扫码)/ JSAPI支付(小程序)
- 请求URL:
https://api.mch.weixin.qq.com/v3/pay/transactions/native - 数据格式:JSON
- 认证方式:微信支付V3签名
统一下单请求参数:
{
"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"
}
微信支付响应参数:
{
"code_url": "weixin://wxpay/bizpayurl?pr=HuaLcAKwa"
}
支付结果通知参数:
{
"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": "..."
}
}
短信接口
功能描述:向用户发送各类业务通知短信。
接口规范:
- 接口方式:HTTP接口
- 数据格式:JSON
- 交换频率:实时
物联网集抄平台接口
功能描述:与物联网集抄平台交互,获取智能水表数据。
接口规范:
- 接口方式:HTTP接口或WebService
- 数据格式:JSON或XML
- 交换频率:定时或实时
内部接口
客户管理API接口
客户信息查询接口
功能描述:根据客户ID查询客户详细信息。
接口详情:
- 请求方式:GET
- 请求路径:
/admin-api/water/customer/{id} - 请求头:
Authorization: Bearer {token}
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| id | Long | 是 | 客户ID | 1 |
响应参数:
{
"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代码示例:
@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));
}
}
客户分页查询接口
功能描述:分页查询客户列表信息。
接口详情:
- 请求方式: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 |
响应参数:
{
"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
}
}
客户创建接口
功能描述:创建新客户记录。
接口详情:
- 请求方式:POST
- 请求路径:
/admin-api/water/customer/create
请求参数:
{
"customerCode": "C002",
"customerName": "李四",
"customerType": "RESIDENT",
"idType": "ID_CARD",
"idNumber": "350103199001011234",
"phone": "13900139000",
"address": "福建省福州市鼓楼区XX街道XX号"
}
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": 2
}
Service层代码示例:
@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接口
水表信息查询接口
功能描述:根据水表ID查询水表详细信息。
接口详情:
- 请求方式:GET
- 请求路径:
/admin-api/water/meter/{id} - 请求头:
Authorization: Bearer {token}
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| id | Long | 是 | 水表ID | 1 |
响应参数:
{
"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代码示例:
@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));
}
}
抄表记录创建接口
功能描述:创建新的抄表记录。
接口详情:
- 请求方式:POST
- 请求路径:
/admin-api/water/reading/create
请求参数:
{
"meterId": 1,
"readingDate": "2024-12-19",
"readingValue": 156.32,
"readingType": "MANUAL",
"readerId": "R001",
"photoUrl": "https://example.com/photos/reading001.jpg",
"remark": "正常抄表"
}
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": 1
}
Service层实现示例:
@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();
}
}
抄表数据批量导入接口
功能描述:批量导入抄表数据,支持Excel文件上传。
接口详情:
- 请求方式:POST
- 请求路径:
/admin-api/water/reading/import - Content-Type:
multipart/form-data
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| file | MultipartFile | 是 | Excel文件 | reading_data.xlsx |
| updateSupport | Boolean | 否 | 是否更新已有数据 | false |
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": {
"successCount": 95,
"failureCount": 5,
"failureList": [
{
"lineNumber": 3,
"meterCode": "M003",
"errorMsg": "水表不存在"
}
]
}
}
账单管理API接口
账单查询接口
功能描述:根据客户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 |
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": {
"list": [
{
"id": 1,
"billCode": "B202412190001",
"billMonth": "2024-12",
"billDate": "2024-12-19",
"waterUsage": 25.50,
"waterFee": 76.50,
"sewageFee": 15.30,
"totalAmount": 91.80,
"paidAmount": 0.00,
"balanceAmount": 91.80,
"dueDate": "2025-01-19",
"billStatus": 0,
"customerName": "张三",
"meterCode": "M001"
}
],
"total": 1
}
}
账单生成接口
功能描述:根据抄表记录生成水费账单。
接口详情:
- 请求方式:POST
- 请求路径:
/admin-api/water/bill/generate
请求参数:
{
"billMonth": "2024-12",
"customerIds": [1, 2, 3],
"readingIds": [1, 2, 3],
"dueDate": "2025-01-19"
}
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": {
"generateCount": 3,
"successList": [
{
"customerId": 1,
"billId": 1,
"totalAmount": 91.80
}
],
"failureList": []
}
}
Service层代码示例:
@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;
}
}
缴费管理API接口
缴费处理接口
功能描述:处理客户缴费操作。
接口详情:
- 请求方式:POST
- 请求路径:
/admin-api/water/payment/create
请求参数:
{
"customerId": 1,
"billIds": [1, 2],
"paymentType": "NORMAL",
"paymentChannel": "CASH",
"paymentAmount": 183.60,
"actualAmount": 200.00,
"operatorId": "OP001",
"outletCode": "OUT001",
"remark": "现金缴费"
}
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": {
"paymentId": 1,
"paymentCode": "P202412190001",
"changeAmount": 16.40,
"invoiceNo": "INV20241219001"
}
}
在线支付接口
功能描述:处理在线支付(微信、支付宝等)。
接口详情:
- 请求方式:POST
- 请求路径:
/admin-api/water/payment/online-pay
请求参数:
{
"customerId": 1,
"billIds": [1],
"paymentChannel": "WECHAT",
"paymentAmount": 91.80,
"returnUrl": "https://water.example.com/payment/callback",
"notifyUrl": "https://water.example.com/api/payment/notify"
}
响应参数:
{
"code": 0,
"msg": "操作成功",
"data": {
"paymentCode": "P202412190002",
"prepayId": "wx20241219001234567890",
"payUrl": "weixin://wxpay/bizpayurl?pr=abc123",
"qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
}
}
工单接口
工单创建接口
功能描述:创建业务工单。
接口规范:
- 请求方式:POST
- 请求路径:/api/workorders
- 请求/返回格式:JSON
工单状态更新接口
功能描述:更新工单处理状态。
接口规范:
- 请求方式:PUT
- 请求路径:/api/workorders/{workorderId}/status
- 请求/返回格式:JSON
接口标准
接口协议
系统接口主要采用以下协议:
- RESTful API:适用于系统内部模块间的交互以及移动应用等轻量级客户端
- WebService:适用于与外部系统的集成,特别是银行等传统机构
- 消息队列:适用于异步处理的场景,如批量数据处理、通知推送等
数据格式
接口数据主要采用以下格式:
- JSON:主要用于RESTful API接口,结构简单清晰,适合Web应用
- XML:主要用于WebService接口,兼容性好,适合与传统系统对接
- 文本文件:主要用于批量数据交换,如银行代扣文件等
接口安全设计
接口安全采用多层防护机制:
认证机制
JWT令牌认证:
@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认证(外部系统):
@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);
}
}
数据加密
敏感数据加密:
@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);
}
}
访问控制
IP白名单控制:
@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);
}
}
接口限流
基于Redis的令牌桶限流:
@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;
}
}
错误处理机制
统一异常处理
@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());
}
}
错误码定义
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, "缴费渠道不可用");
}
接口调用示例
成功响应示例:
{
"code": 0,
"msg": "操作成功",
"data": {
"id": 1,
"customerName": "张三"
}
}
失败响应示例:
{
"code": 1001000001,
"msg": "客户不存在",
"data": null
}
前端接口调用示例
Vue3 + 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}` })
}
}
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>