Commit e5850d0c by Lizh

feat: P3 Step 4 — CountryCode + Warehouse 管理 6 endpoints

新增 CountryCode 2端点: GET /api/v2/countryCode/{map,all}
新增 Warehouse 5端点: statusList/list/add/edit/delete

Part A (CountryCode): BaseCountryCodeModel + SaasAdminService 集成 +
BaseCountryCodeService/Controller, 本地缓存 TTL 1 小时
Part B (Warehouse): CustomWarehouseInfoAppService/Controller,
支持分页模糊查询/状态列表/CRUD

Co-Authored-By: Claude <noreply@anthropic.com>
parent 3dca12a3
package com.jomalls.custom.app.service.system;
import com.jomalls.custom.app.vo.BaseCountryCodeVO;
import java.util.List;
import java.util.Map;
/**
* 国家代码 App Service 接口
*
* @author Lizh
*/
public interface BaseCountryCodeService {
/**
* 获取国家代码映射(countryCode → nameCn)
*
* @return 国家代码映射
*/
Map<String, String> getMap();
/**
* 获取全部国家代码列表
*
* @return 国家代码列表
*/
List<BaseCountryCodeVO> getAll();
}
package com.jomalls.custom.app.service.system.impl;
import com.jomalls.custom.app.service.system.BaseCountryCodeService;
import com.jomalls.custom.app.vo.BaseCountryCodeVO;
import com.jomalls.custom.integrate.model.BaseCountryCodeModel;
import com.jomalls.custom.integrate.service.SaasAdminService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 国家代码 App Service 实现
*
* @author Lizh
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BaseCountryCodeServiceImpl implements BaseCountryCodeService {
private final SaasAdminService saasAdminService;
@Override
public Map<String, String> getMap() {
List<BaseCountryCodeModel> list = saasAdminService.getAllCountryCode();
if (list == null || list.isEmpty()) {
return Collections.emptyMap();
}
return list.stream()
.collect(Collectors.toMap(
BaseCountryCodeModel::getCountryCode,
BaseCountryCodeModel::getNameCn,
(a, b) -> a));
}
@Override
public List<BaseCountryCodeVO> getAll() {
List<BaseCountryCodeModel> list = saasAdminService.getAllCountryCode();
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
return list.stream()
.map(this::toVO)
.collect(Collectors.toList());
}
/**
* 将 Model 转换为 VO
*/
private BaseCountryCodeVO toVO(BaseCountryCodeModel model) {
BaseCountryCodeVO vo = new BaseCountryCodeVO();
vo.setCountryCode(model.getCountryCode());
vo.setNameCn(model.getNameCn());
vo.setNameEn(model.getNameEn());
vo.setContinentCode(model.getContinentCode());
vo.setTelephoneAreaCode(model.getTelephoneAreaCode());
return vo;
}
}
package com.jomalls.custom.app.service.warehouse;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.dal.entity.CustomWarehouseInfoEntity;
import java.util.List;
import java.util.Map;
/**
* 仓库管理 App Service 接口
*
* @author Lizh
*/
public interface CustomWarehouseInfoService {
/**
* 分页查询仓库列表
*
* @param entity 查询条件
* @param page 分页参数
* @return 分页结果
*/
IPage<CustomWarehouseInfoEntity> pageList(CustomWarehouseInfoEntity entity, Page<CustomWarehouseInfoEntity> page);
/**
* 获取仓库状态列表
*
* @return 状态列表,格式:[{code:0, label:"停用"}, {code:1, label:"启用"}]
*/
List<Map<String, Object>> statusList();
/**
* 新增仓库
*
* @param entity 仓库信息
*/
void add(CustomWarehouseInfoEntity entity);
/**
* 修改仓库
*
* @param entity 仓库信息
*/
void edit(CustomWarehouseInfoEntity entity);
/**
* 删除仓库(支持批量,逗号分隔 ID)
*
* @param ids 仓库 ID,逗号分隔
*/
void remove(String ids);
}
package com.jomalls.custom.app.service.warehouse.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.service.warehouse.CustomWarehouseInfoService;
import com.jomalls.custom.dal.entity.CustomWarehouseInfoEntity;
import com.jomalls.custom.domain.service.warehouse.CustomWarehouseInfoDomainService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 仓库管理 App Service 实现
*
* @author Lizh
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class CustomWarehouseInfoServiceImpl implements CustomWarehouseInfoService {
private final CustomWarehouseInfoDomainService customWarehouseInfoDomainService;
@Override
public IPage<CustomWarehouseInfoEntity> pageList(CustomWarehouseInfoEntity entity, Page<CustomWarehouseInfoEntity> page) {
LambdaQueryWrapper<CustomWarehouseInfoEntity> wrapper = new LambdaQueryWrapper<>();
// 按仓库名称模糊搜索
if (StringUtils.hasText(entity.getWarehouseName())) {
wrapper.like(CustomWarehouseInfoEntity::getWarehouseName, entity.getWarehouseName());
}
// 按仓库编码模糊搜索
if (StringUtils.hasText(entity.getWarehouseCode())) {
wrapper.like(CustomWarehouseInfoEntity::getWarehouseCode, entity.getWarehouseCode());
}
// 按状态查询
if (entity.getStatus() != null) {
wrapper.eq(CustomWarehouseInfoEntity::getStatus, entity.getStatus());
}
// 按创建时间降序
wrapper.orderByDesc(CustomWarehouseInfoEntity::getCreateTime);
return customWarehouseInfoDomainService.page(page, wrapper);
}
@Override
public List<Map<String, Object>> statusList() {
List<Map<String, Object>> list = new ArrayList<>(2);
Map<String, Object> enabled = new HashMap<>(2);
enabled.put("code", 1);
enabled.put("label", "启用");
list.add(enabled);
Map<String, Object> disabled = new HashMap<>(2);
disabled.put("code", 0);
disabled.put("label", "停用");
list.add(disabled);
return list;
}
@Override
public void add(CustomWarehouseInfoEntity entity) {
customWarehouseInfoDomainService.save(entity);
log.info("新增仓库成功: warehouseName={}", entity.getWarehouseName());
}
@Override
public void edit(CustomWarehouseInfoEntity entity) {
customWarehouseInfoDomainService.updateById(entity);
log.info("修改仓库成功: id={}", entity.getId());
}
@Override
public void remove(String ids) {
if (!StringUtils.hasText(ids)) {
log.warn("删除仓库失败: ids 为空");
return;
}
List<Long> idList = Arrays.stream(ids.split(","))
.map(String::trim)
.map(Long::valueOf)
.collect(Collectors.toList());
customWarehouseInfoDomainService.removeByIds(idList);
log.info("批量删除仓库成功: ids={}", ids);
}
}
package com.jomalls.custom.app.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseCountryCodeVO implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty("countryCode")
private String countryCode;
@JsonProperty("nameCn")
private String nameCn;
@JsonProperty("nameEn")
private String nameEn;
@JsonProperty("continentCode")
private String continentCode;
@JsonProperty("telephoneAreaCode")
private String telephoneAreaCode;
}
package com.jomalls.custom.integrate.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* 国家代码模型(对接 admin 管理端 API)
*
* @author Lizh
*/
@Data
public class BaseCountryCodeModel {
@JsonProperty("countryCode")
private String countryCode;
@JsonProperty("nameCn")
private String nameCn;
@JsonProperty("nameEn")
private String nameEn;
@JsonProperty("continentCode")
private String continentCode;
@JsonProperty("telephoneAreaCode")
private String telephoneAreaCode;
}
package com.jomalls.custom.integrate.service;
import com.jomalls.custom.integrate.model.BaseCountryCodeModel;
import com.jomalls.custom.integrate.model.CategoryInfoModel;
import com.jomalls.custom.integrate.model.PropertyModel;
......@@ -33,4 +34,11 @@ public interface SaasAdminService {
* 使用本地缓存避免每次分页查询都发起远程 HTTP 调用。
*/
List<CategoryInfoModel> getAllList();
/**
* 获取全部国家代码(带本地缓存,TTL 1 小时)
* <p>
* 调用 admin 管理端 API 查询所有国家代码。
*/
List<BaseCountryCodeModel> getAllCountryCode();
}
......@@ -2,6 +2,7 @@ package com.jomalls.custom.integrate.service.impl;
import com.jomalls.custom.enums.CodeEnum;
import com.jomalls.custom.integrate.client.RemoteApiClient;
import com.jomalls.custom.integrate.model.BaseCountryCodeModel;
import com.jomalls.custom.integrate.model.CategoryInfoModel;
import com.jomalls.custom.integrate.model.PropertyModel;
import com.jomalls.custom.integrate.model.SaasAdminApiResponseModel;
......@@ -38,17 +39,24 @@ public class SaasAdminServiceImpl implements SaasAdminService {
private static final String GET_ALL_LIST_URL = "/api/manage/rest/baseCategoryInfo/all_list";
private static final String GET_PROPERTY_BY_IDS_URL = "/api/manage/rest/baseProperty/getByIds";
private static final String GET_ALL_COUNTRY_CODE_URL = "/api/manage/rest/baseCountryCode/all";
private static final int DEFAULT_MAP_SIZE = 2;
/** 分类列表缓存 TTL(毫秒):5 分钟 */
private static final long CACHE_TTL_MS = 5 * 60 * 1000;
/** 国家代码缓存 TTL(毫秒):1 小时 */
private static final long COUNTRY_CODE_CACHE_TTL_MS = 60 * 60 * 1000;
private final RemoteApiClient remoteApiClient;
/** 分类列表本地缓存 */
private final AtomicReference<CacheEntry<List<CategoryInfoModel>>> categoryCache = new AtomicReference<>();
/** 国家代码本地缓存 */
private final AtomicReference<CacheEntry<List<BaseCountryCodeModel>>> countryCodeCache = new AtomicReference<>();
@Value("${server.admin.base-url:https://admin.jomalls.com}")
private String adminBaseUrl;
......@@ -134,6 +142,45 @@ public class SaasAdminServiceImpl implements SaasAdminService {
return Collections.emptyList();
}
/**
* 获取全部国家代码(带本地缓存,TTL 1 小时)
* <p>
* 调用 admin 管理端 API 查询所有国家代码。
*
* @return 国家代码列表,失败时返回空列表
*/
public List<BaseCountryCodeModel> getAllCountryCode() {
// 命中缓存且未过期 → 直接返回
CacheEntry<List<BaseCountryCodeModel>> entry = countryCodeCache.get();
if (entry != null && !entry.isExpired()) {
return entry.data;
}
// 未命中或已过期 → 远程调用刷新
String url = adminBaseUrl + GET_ALL_COUNTRY_CODE_URL;
try {
ResponseEntity<SaasAdminApiResponseModel<List<BaseCountryCodeModel>>> response = remoteApiClient.get(
url,
new ParameterizedTypeReference<>() {},
getHeader());
if (response != null && response.getBody() != null) {
SaasAdminApiResponseModel<List<BaseCountryCodeModel>> responseBody = response.getBody();
if (responseBody.getCode() == CodeEnum.SUCCESS.getCode()) {
List<BaseCountryCodeModel> data = responseBody.getData();
countryCodeCache.set(new CacheEntry<>(data, COUNTRY_CODE_CACHE_TTL_MS));
return data;
}
}
} catch (Exception e) {
log.error("[ SaasAdminService ] {} 调用失败", url, e);
}
// 远程调用失败但有旧缓存 → 降级返回旧缓存
if (entry != null) {
log.warn("[ SaasAdminService ] getAllCountryCode 远程失败,降级使用过期缓存");
return entry.data;
}
return Collections.emptyList();
}
/** 简单 TTL 缓存条目 */
private static class CacheEntry<T> {
final T data;
......@@ -144,6 +191,11 @@ public class SaasAdminServiceImpl implements SaasAdminService {
this.expireAt = System.currentTimeMillis() + CACHE_TTL_MS;
}
CacheEntry(T data, long ttlMs) {
this.data = data;
this.expireAt = System.currentTimeMillis() + ttlMs;
}
boolean isExpired() {
return System.currentTimeMillis() > expireAt;
}
......
package com.jomalls.custom.webapp.controller.system;
import com.jomalls.custom.app.service.system.BaseCountryCodeService;
import com.jomalls.custom.app.vo.BaseCountryCodeVO;
import com.jomalls.custom.utils.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 国家代码 Controller
*
* @author Lizh
*/
@Slf4j
@RestController
@RequestMapping("/api/v2/countryCode")
@RequiredArgsConstructor
@Tag(name = "国家代码管理")
public class BaseCountryCodeController {
private final BaseCountryCodeService baseCountryCodeService;
@Operation(summary = "获取国家代码映射(code → nameCn)")
@GetMapping("/map")
public R<Map<String, String>> getMap() {
Map<String, String> map = baseCountryCodeService.getMap();
return R.ok(map);
}
@Operation(summary = "获取全部国家代码列表")
@GetMapping("/all")
public R<List<BaseCountryCodeVO>> getAll() {
List<BaseCountryCodeVO> list = baseCountryCodeService.getAll();
return R.ok(list);
}
}
package com.jomalls.custom.webapp.controller.warehouse;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.annotation.RequiresPermissions;
import com.jomalls.custom.app.service.warehouse.CustomWarehouseInfoService;
import com.jomalls.custom.dal.entity.CustomWarehouseInfoEntity;
import com.jomalls.custom.utils.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 仓库管理 Controller
*
* @author Lizh
*/
@Slf4j
@RestController
@RequestMapping("/api/v2/customWarehouse")
@RequiredArgsConstructor
@Tag(name = "仓库管理")
public class CustomWarehouseInfoController {
private final CustomWarehouseInfoService customWarehouseInfoService;
@Operation(summary = "获取仓库状态列表")
@GetMapping("/statusList")
public R<List<Map<String, Object>>> statusList() {
return R.ok(customWarehouseInfoService.statusList());
}
@Operation(summary = "分页查询仓库列表")
@RequiresPermissions("business:customWarehouse:list")
@GetMapping("/list")
public R<IPage<CustomWarehouseInfoEntity>> list(CustomWarehouseInfoEntity entity, Page<CustomWarehouseInfoEntity> page) {
return R.ok(customWarehouseInfoService.pageList(entity, page));
}
@Operation(summary = "新增仓库")
@RequiresPermissions("business:customWarehouse:add")
@PostMapping
public R<Void> add(@RequestBody CustomWarehouseInfoEntity entity) {
customWarehouseInfoService.add(entity);
return R.ok();
}
@Operation(summary = "修改仓库")
@RequiresPermissions("business:customWarehouse:edit")
@PutMapping
public R<Void> edit(@RequestBody CustomWarehouseInfoEntity entity) {
customWarehouseInfoService.edit(entity);
return R.ok();
}
@Operation(summary = "删除仓库")
@RequiresPermissions("business:customWarehouse:remove")
@DeleteMapping("/{ids}")
public R<Void> remove(@PathVariable String ids) {
customWarehouseInfoService.remove(ids);
return R.ok();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment