679 lines
24 KiB
Markdown
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.
|