需求 地區數據往往是存在強上下級關係的一種數據結構,在電商系統中是比較常應用到的,比如北京的下級地區只有海澱區、通州區……,而不會是太原市,而且在開發人員傳遞地區值的時候往往要傳遞很多的值,比如省、市、區、鎮、省Id,市id、區id、鎮id,這樣影響了代碼的美觀性及校驗強上下級關係代碼的複雜性。基於 ...
需求
地區數據往往是存在強上下級關係的一種數據結構,在電商系統中是比較常應用到的,比如北京的下級地區只有海澱區、通州區……,而不會是太原市,而且在開發人員傳遞地區值的時候往往要傳遞很多的值,比如省、市、區、鎮、省Id,市id、區id、鎮id,這樣影響了代碼的美觀性及校驗強上下級關係代碼的複雜性。基於這樣的問題要求我們必須要實現一種可以簡潔傳遞參數並且可以實現地區格式化的一種格式化器。這樣衍生出了我們基於Spring Formatter地區自動化格式的一種格式化器。
預期效果
常規做法的地區參數傳遞:
?province_id=1&county_id=2&city_id=3&town_id=4
在controller接收參數時:
@PostMapping
public Result someMethod(Integer provinceId,Integer countyId,Integer cityId,Integer townId){ //街道id不為空找到級聯的父 if(townId!=null){ //找到區 countyId=findCounty(townId) //找到市 cityId=findCity(countyId); //找到省 provinceId=findProvince(cityId); } //如果是只保存到區的需求,也要依次尋找級聯父 if(countyId!=null){ //找到市 cityId=findCity(countyId); //找到省 provinceId=findCity(cityId); } //如果是保存到市也是同樣,不在贅述 }
使用註解後效果:
參數傳遞:
xxx?region=3
在controller接收參數時
public result someMethod(@RegionFormat Region region) { //這裡的region對象中的省市區街道已經按相應的級別對應好,直接使用即可 if(region.getProvinceProId!=null){ // 保存省id} if(region.getCityId!=null){ // 保存市id} if(region.getCountyId=!=null{//保存區id} if(region.getTwonId=!=null{//保存街道id} }
根據上述需求,如下是基於javashop電商系統中,地區參數接收註解的架構和實現:
類圖
1、 RegionFormatter地區格式化器實現RegionFomat註解介面,實現地區轉換器方法pase方法,傳入地區id和地區語言,最後將轉換後的數據輸出。如下代碼
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface RegionFormat {
}
public class RegionFormatter implements Formatter<Region> {
private RegionsClient regionsClient;
public RegionFormatter(RegionsClient regionsClient) {
this.regionsClient = regionsClient;
}
public RegionFormatter() {
}
/**
* 地區轉換器
*
* @param regionId 地區id(只允許第三級或者第四級地區id)
* @param locale 國際化US
* @return 地區對象
* @throws ParseException
*/
@Override
public Region parse(String regionId, Locale locale) throws ParseException {
Regions regions = regionsClient.getModel(Integer.valueOf(regionId));
if (regions == null || regions.getRegionGrade() < 3) {
throw new IllegalArgumentException("地區不合法,請聯繫管理員");
}
//根據底層地區id反推出上級id
String regionPath = regions.getRegionPath();
regionPath = regionPath.substring(1, regionPath.length());
String[] regionPathArray = regionPath.split(",");
//給地區賦值
List rList = new ArrayList();
for (String path : regionPathArray) {
Regions region = regionsClient.getModel(Integer.valueOf(path));
if (regions == null) {
throw new IllegalArgumentException("地區不合法,請聯繫管理員");
}
rList.add(region);
}
return this.createRegion(rList);
}
/**
* 組織地區數據
*
* @param list 地區集合
* @return 地區
*/
private Region createRegion(List<Regions> list) {
//將地區數據組織好存入Region對象
Region region = new Region();
region.setProvinceId(list.get(0).getId());
region.setProvince(list.get(0).getLocalName());
region.setCityId(list.get(1).getId());
region.setCity(list.get(1).getLocalName());
region.setCountyId(list.get(2).getId());
region.setCounty(list.get(2).getLocalName());
//如果地區數據為四級,為第四級地區賦值
if (list.size() == 4) {
region.setTown(list.get(3).getLocalName());
region.setTownId(list.get(3).getId());
} else {
region.setTown("");
region.setTownId(0);
}
return region;
}
/**
* 將格式化的地區toString輸出
*
* @param object 地區對象
* @param locale 國際化US
* @return
*/
@Override
public String print(Region object, Locale locale) {
return object.toString();
}
}
2、 RegionFormatAnnotationFormatterFactory地區格式化工廠實現AnnotationFormatterFactory自定義格式工廠介面,將要實現的自動註解RegionFomat傳入。如下代碼:
public class RegionFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<RegionFormat> {
/**
* 獲取被註解對象的類型
*
* @return
*/
@Override
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(Region.class));
}
/**
* 獲取輸出對象
*
* @param annotation 註解實例
* @param fieldType 被註解欄位的類型
* @return 地區格式化後輸出的對象
*/
@Override
public Printer<?> getPrinter(RegionFormat annotation, Class<?> fieldType) {
return new RegionFormatter();
}
/**
* 獲取解析器
*
* @param annotation 註解實例
* @param fieldType 被註解欄位的類型
* @return 地區格式化後對象
*/
@Override
public Parser<?> getParser(RegionFormat annotation, Class<?> fieldType) {
return new RegionFormatter();
}
}
3、 WebConfig實現WebMvcConfigurer,註冊自定義的地區格式化器。如下代碼:
public class RegionFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<RegionFormat> {
/**
* 獲取被註解對象的類型
*
* @return
*/
@Override
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(Region.class));
}
/**
* 獲取輸出對象
*
* @param annotation 註解實例
* @param fieldType 被註解欄位的類型
* @return 地區格式化後輸出的對象
*/
@Override
public Printer<?> getPrinter(RegionFormat annotation, Class<?> fieldType) {
return new RegionFormatter();
}
/**
* 獲取解析器
*
* @param annotation 註解實例
* @param fieldType 被註解欄位的類型
* @return 地區格式化後對象
*/
@Override
public Parser<?> getParser(RegionFormat annotation, Class<?> fieldType) {
return new RegionFormatter();
}
}
使用
1、支持對象屬性註解
@RegionFormat
@ApiModelProperty(name = "region", value = "地區")
private Region region;
2、值Controller入參註解
@ApiOperation(value = "平臺添加店鋪", response = ShopVO.class)
@PostMapping()
public ShopVO save(@Valid ShopVO shop,
@RegionFormat @RequestParam("license_region") Region licenseRegion,
@RegionFormat @RequestParam("bank_region") Region bankRegion,
@RegionFormat @RequestParam("shop_region") Region shopRegion) {
}
效果
使用上述地區格式化器方法,假設如下請求
xxx?region=3
則會將id為1的地區及父的信息讀取並且形成Region對象。
例如region=3的地區為長安街,父級為東城區->北京,形成的Region對象如下。
{
"cityId": 2,
"townId": 0,
"countyId": 3,
"provinceId": 1,
"province": "長安街",
"county": "東城區",
"city": "北京市",
"town": ""
}
如果傳遞的是townId,則會自動填充county、city及province。
如果傳遞的是countyId,則會自動填充city、province,此時townId為0。
同時支持對象屬性級別註解:
對象:
public Person{ //所屬區域 @RegionFormat private Region region; //geter and seter }
controller:
@PostMapping() public Result save(Person person) { //這裡的地區已經按上述規則賦值 person.getRegion() }