Files
loan-pricing/docs/superpowers/plans/2026-04-29-customer-map-selection-backend-plan.md

679 lines
24 KiB
Markdown

# Customer Map Selection Backend Implementation Plan
> **For agentic workers:** Steps use checkbox (`- [ ]`) syntax for tracking. Follow this repository's AGENTS.md rule: do not enable subagents or superpowers execution modes unless the user explicitly requests them for the implementation session.
**Goal:** Add backend customer-id-to-customer-internal-code query APIs for personal and corporate workflow creation, backed by local mock interfaces and profile configuration.
**Architecture:** Keep workflow creation APIs unchanged. Add a small customer-map service that reads separate personal/corporate URLs from configuration, calls them with GET `cust_id`, and returns mapping records with underscore JSON field names through the existing RuoYi `AjaxResult` response convention. Add mock endpoints under the existing mock controller so all active profiles can point to local mock URLs first.
**Tech Stack:** Spring Boot, RuoYi `AjaxResult`, Spring `RestTemplate`, Jackson `@JsonProperty`, JUnit 5, Mockito, Maven.
---
## File Structure
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVO.java`
- Holds one customer mapping record.
- Uses Java camelCase fields internally and `@JsonProperty` to serialize/deserialize underscore field names.
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java`
- Owns config URL selection, GET forwarding, parameter validation, and response parsing.
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
- Adds authenticated business endpoints used by the frontend.
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java`
- Adds anonymous local mock endpoints.
- Modify: `ruoyi-admin/src/main/resources/application-dev.yml`
- Adds `customer-map` mock URL config.
- Modify: `ruoyi-admin/src/main/resources/application-uat.yml`
- Adds `customer-map` mock URL config.
- Modify: `ruoyi-admin/src/main/resources/application-pro.yml`
- Adds `customer-map` mock URL config for the production profile, currently pointing to local mock as requested.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVOTest.java`
- Verifies JSON field names stay underscored.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java`
- Verifies personal/corporate URL routing, `cust_id` forwarding, response parsing, and missing customer-id errors.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java`
- Verifies mock endpoints return one or more underscore-field records.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerCustomerMapTest.java`
- Verifies business controller methods delegate to the service and preserve the result records.
## Task 1: Add Customer Map Record VO
**Files:**
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVO.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVOTest.java`
- [ ] **Step 1: Write the failing serialization test**
Create `CustomerMapRecordVOTest`:
```java
package com.ruoyi.loanpricing.domain.vo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
class CustomerMapRecordVOTest
{
@Test
void shouldSerializeCustomerMapFieldsWithUnderscoreNames() throws Exception
{
CustomerMapRecordVO record = new CustomerMapRecordVO();
record.setCustId("101330419198206033217");
record.setCustIsn("81033011438");
record.setCustName("张三");
record.setFaithDay("20");
record.setBalanceAvg("300000");
record.setLoanCountHis("2");
record.setLastLoanDate("2025-12-01");
String json = new ObjectMapper().writeValueAsString(record);
assertTrue(json.contains("\"cust_id\""));
assertTrue(json.contains("\"cust_isn\""));
assertTrue(json.contains("\"cust_name\""));
assertTrue(json.contains("\"faith_day\""));
assertTrue(json.contains("\"balance_avg\""));
assertTrue(json.contains("\"loan_count_his\""));
assertTrue(json.contains("\"last_loan_date\""));
}
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: FAIL because `CustomerMapRecordVO` does not exist.
- [ ] **Step 3: Implement the VO**
Create `CustomerMapRecordVO.java`:
```java
package com.ruoyi.loanpricing.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;
@Data
public class CustomerMapRecordVO implements Serializable
{
private static final long serialVersionUID = 1L;
@JsonProperty("cust_id")
private String custId;
@JsonProperty("cust_isn")
private String custIsn;
@JsonProperty("cust_name")
private String custName;
@JsonProperty("faith_day")
private String faithDay;
@JsonProperty("balance_avg")
private String balanceAvg;
@JsonProperty("loan_count_his")
private String loanCountHis;
@JsonProperty("last_loan_date")
private String lastLoanDate;
}
```
- [ ] **Step 4: Run the test to verify it passes**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: PASS.
## Task 2: Add Customer Map Service
**Files:**
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java`
- [ ] **Step 1: Write service tests first**
Create tests that use `MockRestServiceServer` and the package-private test constructor:
```java
package com.ruoyi.loanpricing.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import org.springframework.test.web.client.MockRestServiceServer;
class LoanPricingCustomerMapServiceTest
{
@Test
void shouldQueryPersonalCustomerMapWithCustIdParam()
{
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
LoanPricingCustomerMapService service = new LoanPricingCustomerMapService(
restTemplate,
"http://mock/personal",
"http://mock/corporate");
server.expect(requestTo("http://mock/personal?cust_id=P001"))
.andRespond(withSuccess("{\"code\":200,\"msg\":\"操作成功\",\"data\":[{\"cust_id\":\"P001\",\"cust_isn\":\"81033011438\",\"cust_name\":\"张三\"}]}",
MediaType.APPLICATION_JSON));
List<CustomerMapRecordVO> result = service.queryPersonal("P001");
assertEquals(1, result.size());
assertEquals("81033011438", result.get(0).getCustIsn());
assertEquals("张三", result.get(0).getCustName());
server.verify();
}
@Test
void shouldQueryCorporateCustomerMapWithCustIdParam()
{
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
LoanPricingCustomerMapService service = new LoanPricingCustomerMapService(
restTemplate,
"http://mock/personal",
"http://mock/corporate");
server.expect(requestTo("http://mock/corporate?cust_id=C001"))
.andRespond(withSuccess("{\"code\":200,\"data\":[{\"cust_id\":\"C001\",\"cust_isn\":\"82002469287\",\"cust_name\":\"测试企业\"}]}",
MediaType.APPLICATION_JSON));
List<CustomerMapRecordVO> result = service.queryCorporate("C001");
assertEquals("82002469287", result.get(0).getCustIsn());
assertEquals("测试企业", result.get(0).getCustName());
server.verify();
}
@Test
void shouldRejectBlankCustId()
{
LoanPricingCustomerMapService service = new LoanPricingCustomerMapService(
new RestTemplate(),
"http://mock/personal",
"http://mock/corporate");
ServiceException ex = assertThrows(ServiceException.class, () -> service.queryPersonal(" "));
assertEquals("客户号不能为空", ex.getMessage());
}
}
```
- [ ] **Step 2: Run the tests to verify they fail**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: FAIL because `LoanPricingCustomerMapService` does not exist.
- [ ] **Step 3: Implement the service**
Create the service with a default Spring constructor and a package-private test constructor:
```java
package com.ruoyi.loanpricing.service;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@Service
public class LoanPricingCustomerMapService
{
private final RestTemplate restTemplate;
@Value("${customer-map.personal-url}")
private String personalUrl;
@Value("${customer-map.corporate-url}")
private String corporateUrl;
public LoanPricingCustomerMapService()
{
this(new RestTemplate());
}
LoanPricingCustomerMapService(RestTemplate restTemplate)
{
this.restTemplate = restTemplate;
}
LoanPricingCustomerMapService(RestTemplate restTemplate, String personalUrl, String corporateUrl)
{
this.restTemplate = restTemplate;
this.personalUrl = personalUrl;
this.corporateUrl = corporateUrl;
}
public List<CustomerMapRecordVO> queryPersonal(String custId)
{
return query(personalUrl, custId);
}
public List<CustomerMapRecordVO> queryCorporate(String custId)
{
return query(corporateUrl, custId);
}
private List<CustomerMapRecordVO> query(String url, String custId)
{
if (!StringUtils.hasText(custId))
{
throw new ServiceException("客户号不能为空");
}
URI uri = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("cust_id", custId)
.build()
.toUri();
CustomerMapResponse response = restTemplate.getForObject(uri, CustomerMapResponse.class);
if (response == null)
{
throw new ServiceException("客户号映射接口无返回");
}
if (response.getCode() != null && response.getCode() != 200)
{
throw new ServiceException(StringUtils.hasText(response.getMsg()) ? response.getMsg() : "客户号映射查询失败");
}
return response.getData() == null ? Collections.emptyList() : response.getData();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
static class CustomerMapResponse
{
private Integer code;
private String msg;
private List<CustomerMapRecordVO> data;
}
}
```
- [ ] **Step 4: Run service tests**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: PASS.
## Task 3: Add Mock Endpoints
**Files:**
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java`
- [ ] **Step 1: Write controller tests for mock endpoints**
```java
package com.ruoyi.loanpricing.controller;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import java.util.List;
import org.junit.jupiter.api.Test;
class LoanRatePricingMockControllerCustomerMapTest
{
@Test
void shouldReturnPersonalCustomerMapRecords() throws Exception
{
LoanRatePricingMockController controller = new LoanRatePricingMockController();
AjaxResult result = controller.queryPersonalCustomerMap("P001");
List<?> rows = (List<?>) result.get("data");
assertFalse(rows.isEmpty());
String json = new ObjectMapper().writeValueAsString(rows.get(0));
assertTrue(json.contains("\"cust_id\""));
assertTrue(json.contains("\"cust_isn\""));
assertTrue(json.contains("\"cust_name\""));
}
@Test
void shouldReturnCorporateCustomerMapRecords() throws Exception
{
LoanRatePricingMockController controller = new LoanRatePricingMockController();
AjaxResult result = controller.queryCorporateCustomerMap("C001");
List<?> rows = (List<?>) result.get("data");
assertFalse(rows.isEmpty());
String json = new ObjectMapper().writeValueAsString(rows.get(0));
assertTrue(json.contains("\"cust_id\""));
assertTrue(json.contains("\"loan_count_his\""));
assertTrue(json.contains("\"last_loan_date\""));
}
}
```
- [ ] **Step 2: Run the tests to verify they fail**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRatePricingMockControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: FAIL because the mock methods do not exist.
- [ ] **Step 3: Implement mock methods**
Modify `LoanRatePricingMockController`:
```java
@Anonymous
@Operation(summary = "模拟个人客户号映射查询")
@GetMapping("/customer-map/personal")
public AjaxResult queryPersonalCustomerMap(@RequestParam("cust_id") String custId)
{
return success(randomCustomerMapRecords(custId, "个人客户"));
}
@Anonymous
@Operation(summary = "模拟企业客户号映射查询")
@GetMapping("/customer-map/corporate")
public AjaxResult queryCorporateCustomerMap(@RequestParam("cust_id") String custId)
{
return success(randomCustomerMapRecords(custId, "企业客户"));
}
```
Add a private helper in the same controller:
```java
private List<CustomerMapRecordVO> randomCustomerMapRecords(String custId, String namePrefix)
{
if (!StringUtils.hasText(custId))
{
throw new ServiceException("客户号不能为空");
}
int count = ThreadLocalRandom.current().nextInt(1, 4);
List<CustomerMapRecordVO> records = new ArrayList<>();
for (int i = 1; i <= count; i++)
{
CustomerMapRecordVO record = new CustomerMapRecordVO();
record.setCustId(custId);
record.setCustIsn(String.valueOf(81000000000L + ThreadLocalRandom.current().nextInt(1000000)));
record.setCustName(namePrefix + i);
record.setFaithDay(String.valueOf(ThreadLocalRandom.current().nextInt(1, 365)));
record.setBalanceAvg(String.valueOf(ThreadLocalRandom.current().nextInt(10000, 900000)));
record.setLoanCountHis(String.valueOf(ThreadLocalRandom.current().nextInt(0, 10)));
record.setLastLoanDate(LocalDate.now().minusDays(ThreadLocalRandom.current().nextInt(1, 800)).toString());
records.add(record);
}
return records;
}
```
Required imports:
```java
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
```
- [ ] **Step 4: Run mock controller tests**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRatePricingMockControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: PASS.
## Task 4: Add Business Endpoints
**Files:**
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerCustomerMapTest.java`
- [ ] **Step 1: Write controller delegation tests**
```java
package com.ruoyi.loanpricing.controller;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import com.ruoyi.loanpricing.service.LoanPricingCustomerMapService;
import java.lang.reflect.Field;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class LoanPricingWorkflowControllerCustomerMapTest
{
@Test
void shouldReturnPersonalCustomerMapFromService() throws Exception
{
LoanPricingCustomerMapService service = Mockito.mock(LoanPricingCustomerMapService.class);
CustomerMapRecordVO row = new CustomerMapRecordVO();
row.setCustIsn("81033011438");
when(service.queryPersonal("P001")).thenReturn(List.of(row));
LoanPricingWorkflowController controller = new LoanPricingWorkflowController();
setField(controller, "customerMapService", service);
AjaxResult result = controller.queryPersonalCustomerMap("P001");
List<?> rows = (List<?>) result.get("data");
assertEquals(1, rows.size());
}
@Test
void shouldReturnCorporateCustomerMapFromService() throws Exception
{
LoanPricingCustomerMapService service = Mockito.mock(LoanPricingCustomerMapService.class);
CustomerMapRecordVO row = new CustomerMapRecordVO();
row.setCustIsn("82002469287");
when(service.queryCorporate("C001")).thenReturn(List.of(row));
LoanPricingWorkflowController controller = new LoanPricingWorkflowController();
setField(controller, "customerMapService", service);
AjaxResult result = controller.queryCorporateCustomerMap("C001");
List<?> rows = (List<?>) result.get("data");
assertEquals(1, rows.size());
}
private static void setField(Object target, String fieldName, Object value) throws Exception
{
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
}
}
```
- [ ] **Step 2: Run the tests to verify they fail**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: FAIL because controller methods and field do not exist.
- [ ] **Step 3: Implement controller methods**
In `LoanPricingWorkflowController`, add:
```java
@Autowired
private LoanPricingCustomerMapService customerMapService;
@Operation(summary = "查询个人客户号映射")
@GetMapping("/customer-map/personal")
public AjaxResult queryPersonalCustomerMap(@RequestParam("custId") String custId)
{
return success(customerMapService.queryPersonal(custId));
}
@Operation(summary = "查询企业客户号映射")
@GetMapping("/customer-map/corporate")
public AjaxResult queryCorporateCustomerMap(@RequestParam("custId") String custId)
{
return success(customerMapService.queryCorporate(custId));
}
```
Required import:
```java
import com.ruoyi.loanpricing.service.LoanPricingCustomerMapService;
```
- [ ] **Step 4: Run controller tests**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: PASS.
## Task 5: Add Profile Configuration
**Files:**
- Modify: `ruoyi-admin/src/main/resources/application-dev.yml`
- Modify: `ruoyi-admin/src/main/resources/application-uat.yml`
- Modify: `ruoyi-admin/src/main/resources/application-pro.yml`
- [ ] **Step 1: Add `customer-map` to every active profile**
Place this block next to the existing `model:` block in each profile file:
```yaml
customer-map:
personal-url: http://localhost:63310/rate/pricing/mock/customer-map/personal
corporate-url: http://localhost:63310/rate/pricing/mock/customer-map/corporate
```
Keep the existing `model:` URLs unchanged.
- [ ] **Step 2: Verify config keys exist**
Run:
```bash
rg -n "customer-map:|personal-url: http://localhost:63310/rate/pricing/mock/customer-map/personal|corporate-url: http://localhost:63310/rate/pricing/mock/customer-map/corporate" ruoyi-admin/src/main/resources/application-dev.yml ruoyi-admin/src/main/resources/application-uat.yml ruoyi-admin/src/main/resources/application-pro.yml
```
Expected: each profile contains one `customer-map` block with both mock URLs.
## Task 6: Backend Verification
**Files:**
- Verify only, no new files.
- [ ] **Step 1: Run focused backend tests**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest,LoanPricingCustomerMapServiceTest,LoanRatePricingMockControllerCustomerMapTest,LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: PASS.
- [ ] **Step 2: Run affected existing model/workflow tests**
Run:
```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test
```
Expected: PASS.
- [ ] **Step 3: Manual API verification after backend restart**
Restart backend so config and routes are active, then call:
```bash
curl -sS 'http://localhost:63310/rate/pricing/mock/customer-map/personal?cust_id=P001'
curl -sS 'http://localhost:63310/rate/pricing/mock/customer-map/corporate?cust_id=C001'
TOKEN=$(curl -sS -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' 'http://localhost:63310/login/test' | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -sS -H "Authorization: Bearer ${TOKEN}" 'http://localhost:63310/loanPricing/workflow/customer-map/personal?custId=P001'
curl -sS -H "Authorization: Bearer ${TOKEN}" 'http://localhost:63310/loanPricing/workflow/customer-map/corporate?custId=C001'
```
Expected: each successful response uses the RuoYi response wrapper and `data` contains one or more records with underscore fields. If the local admin password differs, replace `admin/admin123` with a valid local test account before calling the authenticated workflow endpoints.
- [ ] **Step 4: Commit backend work**
Use a Chinese commit message and do not include unrelated dirty files:
```bash
git status --short
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVO.java \
ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java \
ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java \
ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java \
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVOTest.java \
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java \
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java \
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerCustomerMapTest.java \
ruoyi-admin/src/main/resources/application-dev.yml \
ruoyi-admin/src/main/resources/application-uat.yml \
ruoyi-admin/src/main/resources/application-pro.yml
git commit -m "新增客户号映射后端接口"
```
Do not commit temporary curl output, screenshots, or generated test data.