Commit 97fc1928 by renyizhao

商品导入

parent ddb18310
......@@ -2,6 +2,7 @@ package com.luhu.computility.framework.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
......@@ -10,6 +11,7 @@ import java.io.Serializable;
@Schema(description="分页参数")
@Data
@Accessors(chain = true)
public class PageParam implements Serializable {
private static final Integer PAGE_NO = 1;
......
......@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fhs.core.trans.vo.TransPojo;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.ibatis.type.JdbcType;
import java.io.Serializable;
......@@ -20,6 +21,7 @@ import java.time.LocalDateTime;
* @author 芋道源码
*/
@Data
@Accessors(chain = true)
@JsonIgnoreProperties(value = "transMap") // 由于 Easy-Trans 会添加 transMap 属性,避免 Jackson 在 Spring Cache 反序列化报错
public abstract class BaseDO implements Serializable, TransPojo {
......
......@@ -31,4 +31,10 @@ public interface ErrorCodeConstants {
ErrorCode RESOURCE_SPU_SPEC_REQUIRED = new ErrorCode(1_030_011_000, "算力资源商品至少选择一个规格");
ErrorCode RESOURCE_SPU_SPEC_NOT_EXISTS = new ErrorCode(1_030_011_001, "算力资源商品规格不存在");
// ========== 阶段 8 - 批量导入相关错误码 ==========
ErrorCode RESOURCE_SPU_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_030_012_000, "导入文件内容为空");
ErrorCode RESOURCE_SPU_IMPORT_NAME_DUPLICATE = new ErrorCode(1_030_012_001, "商品名称已存在");
ErrorCode RESOURCE_SPU_IMPORT_CATEGORY_INVALID = new ErrorCode(1_030_012_002, "商品分类不存在或已禁用");
ErrorCode RESOURCE_SPU_IMPORT_HEADER_INVALID = new ErrorCode(1_030_012_003, "导入文件表头不合法");
}
\ No newline at end of file
package com.luhu.computility.module.compute.controller.admin.resourcespu;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcel;
import com.luhu.computility.framework.common.biz.system.dict.dto.DictDataRespDTO;
import com.luhu.computility.framework.common.pojo.CommonResult;
import com.luhu.computility.framework.common.util.http.HttpUtils;
import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.ResourceSpuImportRespVO;
import com.luhu.computility.module.compute.service.resourcespu.ResourceSpuImportService;
import com.luhu.computility.module.system.api.dict.DictDataApi;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.luhu.computility.framework.common.pojo.CommonResult.success;
/**
* 算力资源 SPU 批量导入 Controller
*/
@Tag(name = "管理后台 - 算力资源 SPU 批量导入")
@RestController
@RequestMapping("/compute/resource-spu")
@Validated
@Slf4j
public class ResourceSpuImportController {
/** 字典类型:算力资源配置类别 */
private static final String DICT_TYPE_CONFIG_CATEGORY = "compute_resource_config_category";
/** 固定列头 */
private static final List<String> FIXED_HEADERS = List.of(
"商品名称", "商品分类", "算力来源", "商品简介", "封面图", "商品销量", "状态");
@Resource
private ResourceSpuImportService resourceSpuImportService;
@Resource
private DictDataApi dictDataApi;
@GetMapping("/get-import-template")
@Operation(summary = "获得算力资源 SPU 导入模板")
public void importTemplate(HttpServletResponse response) throws IOException {
// 1. 拿字典列表(按 sort 排序?这里用默认顺序)
List<DictDataRespDTO> dictList = dictDataApi.getDictDataList(DICT_TYPE_CONFIG_CATEGORY);
// 2. 构造动态表头:固定列 + 字典 label
List<List<String>> head = new ArrayList<>();
for (String fixed : FIXED_HEADERS) {
head.add(List.of(fixed));
}
if (CollUtil.isNotEmpty(dictList)) {
for (DictDataRespDTO dict : dictList) {
head.add(List.of(dict.getLabel()));
}
}
// 3. 设置响应头(必须在 doWrite 之前,否则浏览器下载文件可能没后缀名)
response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8("算力资源SPU导入模板.xls"));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
// 4. 输出 Excel(仅表头,无数据)
EasyExcel.write(response.getOutputStream())
.head(head)
.autoCloseStream(false)
.sheet("算力资源SPU导入模板")
.doWrite(new ArrayList<>());
}
@PostMapping("/import")
@Operation(summary = "导入算力资源 SPU")
@Parameter(name = "file", description = "Excel 文件", required = true)
@PreAuthorize("@ss.hasPermission('compute:resource-spu:import')")
public CommonResult<ResourceSpuImportRespVO> importExcel(@RequestParam("file") MultipartFile file) throws IOException {
return success(resourceSpuImportService.importSpuList(file));
}
}
package com.luhu.computility.module.compute.controller.admin.resourcespu.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
/**
* 算力资源 SPU 批量导入 Request VO
*/
@Schema(description = "管理后台 - 算力资源 SPU 批量导入")
@Data
public class ResourceSpuImportReqVO {
@Schema(description = "Excel 文件", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "Excel 文件不能为空")
private MultipartFile file;
}
package com.luhu.computility.module.compute.controller.admin.resourcespu.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* 算力资源 SPU 批量导入 Response VO
*/
@Schema(description = "管理后台 - 算力资源 SPU 批量导入")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResourceSpuImportRespVO {
@Schema(description = "成功创建的商品名称列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Builder.Default
private List<String> createNames = new ArrayList<>();
@Schema(description = "导入失败行集合", requiredMode = Schema.RequiredMode.REQUIRED)
@Builder.Default
private List<ResourceSpuImportRowVO> failureRows = new ArrayList<>();
}
package com.luhu.computility.module.compute.controller.admin.resourcespu.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 算力资源 SPU 导入 - 失败行 VO
*/
@Schema(description = "管理后台 - 算力资源 SPU 导入 - 失败行")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResourceSpuImportRowVO {
@Schema(description = "Excel 行号(从 2 开始,1 是表头)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer rowIndex;
@Schema(description = "商品名称(填的那一行)", example = "A100 算力集群")
private String name;
@Schema(description = "失败原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品名称已存在")
private String message;
}
......@@ -29,8 +29,7 @@ public class ResourceSpuSaveReqVO {
@Schema(description = "算力来源", example = "own")
private String source;
@Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
@NotEmpty(message = "商品封面图不能为空")
@Schema(description = "商品封面图", example = "https://www.iocoder.cn")
private String picUrl;
@Schema(description = "商品轮播图地址,以逗号分隔,最多15张")
......
package com.luhu.computility.module.compute.service.resourcespu;
import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.ResourceSpuImportRespVO;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* 算力资源 SPU 批量导入 Service 接口
*/
public interface ResourceSpuImportService {
/**
* 解析上传的 Excel 并批量导入算力资源 SPU
*
* @param file Excel 文件
* @return 导入结果(成功列表 + 失败行集合)
*/
ResourceSpuImportRespVO importSpuList(MultipartFile file) throws IOException;
}
package com.luhu.computility.module.compute.service.resourcespu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.luhu.computility.framework.common.biz.system.dict.dto.DictDataRespDTO;
import com.luhu.computility.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.ResourceSpuImportRespVO;
import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.ResourceSpuImportRowVO;
import com.luhu.computility.module.compute.controller.admin.resourcespu.vo.ResourceSpuSaveReqVO;
import com.luhu.computility.module.compute.dal.dataobject.resourcecategory.ResourceCategoryDO;
import com.luhu.computility.module.compute.dal.dataobject.resourceconfig.ResourceConfigDO;
import com.luhu.computility.module.compute.dal.dataobject.resourcespu.ResourceSpuDO;
import com.luhu.computility.module.compute.dal.mysql.resourceconfig.ResourceConfigMapper;
import com.luhu.computility.module.compute.dal.mysql.resourcespu.ResourceSpuMapper;
import com.luhu.computility.module.compute.enums.ResourceEnableStatus;
import com.luhu.computility.module.compute.enums.ResourceSpuStatus;
import com.luhu.computility.module.compute.service.resourcecategory.ResourceCategoryService;
import com.luhu.computility.module.system.api.dict.DictDataApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import static com.luhu.computility.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.luhu.computility.module.compute.enums.ErrorCodeConstants.*;
/**
* 算力资源 SPU 批量导入 Service 实现类
*
* <p>Excel 模板列结构:
* <ul>
* <li>固定列:商品名称 / 商品分类 / 封面图 / 商品销量 / 状态</li>
* <li>动态列:根据字典 compute_resource_config_category 的 label 动态生成(如 CPU / GPU / RAM / Storage)</li>
* </ul>
*
* <p>表头处理:第一行表头里"非固定列"的列名如果匹配字典 label,则视为规格列,对应的字典 value 作为 configCategory 写入。</p>
*
* <p>同名商品:按 name 全局判重,本批次内和数据库都重复都会报错(不支持 update)。</p>
*
* <p>规格值不存在:按 (configCategory, configOption) 自动新建。</p>
*/
@Service
@Validated
@Slf4j
public class ResourceSpuImportServiceImpl implements ResourceSpuImportService {
@Resource
private ResourceSpuService resourceSpuService;
@Resource
private ResourceSpuMapper resourceSpuMapper;
@Resource
private ResourceCategoryService resourceCategoryService;
@Resource
private ResourceConfigMapper resourceConfigMapper;
@Resource
private DictDataApi dictDataApi;
/** 固定列:商品名称 */
private static final String COL_NAME = "商品名称";
/** 固定列:商品分类 */
private static final String COL_CATEGORY = "商品分类";
/** 固定列:算力来源 */
private static final String COL_SOURCE = "算力来源";
/** 固定列:商品简介 */
private static final String COL_INTRO = "商品简介";
/** 固定列:封面图 */
private static final String COL_PIC_URL = "封面图";
/** 固定列:商品销量 */
private static final String COL_SALES = "商品销量";
/** 固定列:状态 */
private static final String COL_STATUS = "状态";
/** 字典类型:算力资源配置类别 */
private static final String DICT_TYPE_CONFIG_CATEGORY = "compute_resource_config_category";
@Override
public ResourceSpuImportRespVO importSpuList(MultipartFile file) throws IOException {
// 1. 解析 Excel(自定义 listener 分别拿表头 + 数据行)
// EasyExcel 默认 headRowNumber=1,第一行通过 invokeHeadMap 接收;invoke 接收数据行
java.util.concurrent.atomic.AtomicReference<List<String>> headerRef = new java.util.concurrent.atomic.AtomicReference<>();
List<List<String>> dataRows = new ArrayList<>();
EasyExcel.read(file.getInputStream(), new AnalysisEventListener<Map<Integer, String>>() {
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
if (headerRef.get() != null) {
return;
}
int maxIdx = headMap.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1);
List<String> header = new ArrayList<>(Collections.nCopies(maxIdx + 1, null));
headMap.forEach((k, v) -> {
if (k < header.size()) {
header.set(k, v);
}
});
headerRef.set(header);
}
@Override
public void invoke(Map<Integer, String> row, AnalysisContext context) {
// 跳过多余空行
if (row.values().stream().allMatch(StrUtil::isBlank)) {
return;
}
// 转成 List<String>,按 key 顺序(key 即列索引)
List<String> list = new ArrayList<>(Collections.nCopies(collectMaxIndex(row) + 1, null));
row.forEach((k, v) -> {
if (k < list.size()) {
list.set(k, v);
}
});
dataRows.add(list);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// no-op
}
private int collectMaxIndex(Map<Integer, String> row) {
return row.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1);
}
}).sheet().doRead();
List<String> header = headerRef.get();
if (header == null || CollUtil.isEmpty(dataRows)) {
throw exception(RESOURCE_SPU_IMPORT_LIST_IS_EMPTY);
}
// 3. 拿字典(按 DICT_TYPE_CONFIG_CATEGORY 拿 value-label 映射)
List<DictDataRespDTO> dictList = dictDataApi.getDictDataList(DICT_TYPE_CONFIG_CATEGORY);
if (CollUtil.isEmpty(dictList)) {
throw exception(RESOURCE_SPU_IMPORT_HEADER_INVALID);
}
Map<String, String> labelToValue = dictList.stream()
.collect(Collectors.toMap(
DictDataRespDTO::getLabel,
DictDataRespDTO::getValue,
(a, b) -> a));
// 4. 解析列索引
Map<String, Integer> fixedColIdx = new HashMap<>();
Map<Integer, String> specColIdxToCategory = new HashMap<>();
for (int i = 0; i < header.size(); i++) {
String col = header.get(i);
if (StrUtil.isBlank(col)) {
continue;
}
col = col.trim();
switch (col) {
case COL_NAME:
case COL_CATEGORY:
case COL_SOURCE:
case COL_INTRO:
case COL_PIC_URL:
case COL_SALES:
case COL_STATUS:
fixedColIdx.put(col, i);
break;
default:
if (labelToValue.containsKey(col)) {
specColIdxToCategory.put(i, labelToValue.get(col));
}
break;
}
}
// 5. 校验固定列必须存在
if (!fixedColIdx.containsKey(COL_NAME) || !fixedColIdx.containsKey(COL_CATEGORY)
|| !fixedColIdx.containsKey(COL_PIC_URL)) {
throw exception(RESOURCE_SPU_IMPORT_HEADER_INVALID);
}
// 6. 遍历每行
ResourceSpuImportRespVO respVO = ResourceSpuImportRespVO.builder()
.createNames(new ArrayList<>())
.failureRows(new ArrayList<>())
.build();
// 用于检测本批次重复
Set<String> nameInBatch = new HashSet<>();
// 缓存已查询的 categoryId(避免重复查 DB)
Map<Long, Boolean> categoryValidCache = new HashMap<>();
// 缓存分类名 -> categoryId(用于文字分类输入)
Map<String, Long> categoryNameCache = new HashMap<>();
// 缓存已查询/已创建的 specConfig(key=configCategory:value, value=configId)
Map<String, Long> specCache = new HashMap<>();
for (int rowIdx = 0; rowIdx < dataRows.size(); rowIdx++) {
List<String> row = dataRows.get(rowIdx);
int excelRow = rowIdx + 2; // Excel 行号:表头占 1 行,数据行从第 2 行开始
String rowName = getCellValue(row, fixedColIdx.get(COL_NAME));
try {
processRow(row, fixedColIdx, specColIdxToCategory, nameInBatch, categoryValidCache, categoryNameCache, specCache, respVO);
} catch (Exception e) {
log.warn("[importSpuList][第 {} 行导入失败:{}]", excelRow, e.getMessage());
respVO.getFailureRows().add(ResourceSpuImportRowVO.builder()
.rowIndex(excelRow)
.name(rowName)
.message(e.getMessage())
.build());
}
}
return respVO;
}
/**
* 处理单行:校验 → 查重 → 查/建规格 → 创建 SPU + 中间表
*/
private void processRow(List<String> row,
Map<String, Integer> fixedColIdx,
Map<Integer, String> specColIdxToCategory,
Set<String> nameInBatch,
Map<Long, Boolean> categoryValidCache,
Map<String, Long> categoryNameCache,
Map<String, Long> specCache,
ResourceSpuImportRespVO respVO) {
// 1. 解析固定字段
String name = getCellValue(row, fixedColIdx.get(COL_NAME));
String categoryIdStr = getCellValue(row, fixedColIdx.get(COL_CATEGORY));
String source = getCellValue(row, fixedColIdx.get(COL_SOURCE));
String intro = getCellValue(row, fixedColIdx.get(COL_INTRO));
String picUrl = getCellValue(row, fixedColIdx.get(COL_PIC_URL));
String salesStr = getCellValue(row, fixedColIdx.get(COL_SALES));
String statusStr = getCellValue(row, fixedColIdx.get(COL_STATUS));
if (StrUtil.isBlank(name)) {
throw new IllegalArgumentException("商品名称不能为空");
}
if (StrUtil.isBlank(categoryIdStr)) {
throw new IllegalArgumentException("商品分类不能为空");
}
Long categoryId = resolveCategoryId(categoryIdStr.trim(), categoryValidCache, categoryNameCache);
if (categoryId == null) {
throw new IllegalArgumentException("商品分类不存在:" + categoryIdStr);
}
// 2. 校验分类
Boolean categoryValid = categoryValidCache.computeIfAbsent(categoryId, this::validateCategoryId);
if (Boolean.FALSE.equals(categoryValid)) {
throw exception(RESOURCE_SPU_IMPORT_CATEGORY_INVALID);
}
// 3. 校验 name:本批次去重
if (!nameInBatch.add(name)) {
throw new IllegalArgumentException(RESOURCE_SPU_IMPORT_NAME_DUPLICATE.getMsg() + "(本批次重复)");
}
// 4. 校验 name:DB 去重
Long nameCount = resourceSpuMapper.selectCount(new LambdaQueryWrapperX<ResourceSpuDO>()
.eq(ResourceSpuDO::getName, name));
if (nameCount != null && nameCount > 0) {
throw exception(RESOURCE_SPU_IMPORT_NAME_DUPLICATE);
}
// 5. 解析可选字段
Integer sales = parseIntOrDefault(salesStr, 0);
Integer status = parseStatusOrDefault(statusStr, ResourceSpuStatus.ONLINE.getValue());
// 6. 处理规格列:每列对应 (configCategory, configOption),查/建 spec
List<Long> specConfigIds = new ArrayList<>();
for (Map.Entry<Integer, String> entry : specColIdxToCategory.entrySet()) {
Integer colIdx = entry.getKey();
String configCategory = entry.getValue();
String configOption = getCellValue(row, colIdx);
if (StrUtil.isBlank(configOption)) {
continue;
}
// 单元格可能填多个值,用"、"分隔
String[] options = configOption.split("[、,,]");
for (String opt : options) {
if (StrUtil.isBlank(opt)) {
continue;
}
String optTrim = opt.trim();
Long configId = getOrCreateSpecId(configCategory, optTrim, specCache);
if (configId != null) {
specConfigIds.add(configId);
}
}
}
if (CollUtil.isEmpty(specConfigIds)) {
throw new IllegalArgumentException("至少需要一个规格");
}
// 7. 创建 SPU(复用 Service 内部事务)
ResourceSpuSaveReqVO saveReqVO = new ResourceSpuSaveReqVO();
saveReqVO.setName(name);
saveReqVO.setCategoryId(categoryId);
saveReqVO.setSource(source);
saveReqVO.setIntro(intro);
saveReqVO.setPicUrl(picUrl);
saveReqVO.setSales(sales);
saveReqVO.setStatus(status);
saveReqVO.setSpecConfigIds(specConfigIds);
resourceSpuService.createResourceSpu(saveReqVO);
respVO.getCreateNames().add(name);
}
/**
* 校验分类是否存在
*/
private Boolean validateCategoryId(Long categoryId) {
ResourceCategoryDO category = resourceCategoryService.getResourceCategory(categoryId);
return category != null;
}
/**
* 解析商品分类:支持数字 ID 或分类名称
* @return 分类 ID,解析不到返回 null
*/
private Long resolveCategoryId(String input,
Map<Long, Boolean> categoryValidCache,
Map<String, Long> categoryNameCache) {
// 1. 优先按数字 ID 解析
try {
return Long.parseLong(input);
} catch (NumberFormatException ignore) {
// 不是数字,走名称匹配
}
// 2. 按名称查询(用缓存避免每次都查全表)
Long cached = categoryNameCache.get(input);
if (cached != null) {
return cached;
}
List<ResourceCategoryDO> all = resourceCategoryService.getAllCategory();
for (ResourceCategoryDO c : all) {
if (input.equals(c.getName())) {
categoryNameCache.put(input, c.getId());
categoryValidCache.put(c.getId(), Boolean.TRUE);
return c.getId();
}
}
return null;
}
/**
* 查/建规格项:先按 (configCategory, configOption) 查 spec 表,不存在则新建
*/
private Long getOrCreateSpecId(String configCategory, String configOption,
Map<String, Long> specCache) {
String cacheKey = configCategory + "::" + configOption;
Long cached = specCache.get(cacheKey);
if (cached != null) {
return cached;
}
List<ResourceConfigDO> existing = resourceConfigMapper.selectList(
Wrappers.<ResourceConfigDO>lambdaQuery()
.eq(ResourceConfigDO::getConfigCategory, configCategory)
.eq(ResourceConfigDO::getConfigOption, configOption));
if (CollUtil.isNotEmpty(existing)) {
Long id = existing.get(0).getId();
specCache.put(cacheKey, id);
return id;
}
// 不存在则新建
ResourceConfigDO newCfg = new ResourceConfigDO();
newCfg.setConfigCategory(configCategory);
newCfg.setConfigOption(configOption);
newCfg.setStatus(ResourceEnableStatus.ENABLE.getValue());
newCfg.setSort(0);
resourceConfigMapper.insert(newCfg);
specCache.put(cacheKey, newCfg.getId());
return newCfg.getId();
}
private String getCellValue(List<String> row, Integer idx) {
if (idx == null || idx >= row.size()) {
return null;
}
return row.get(idx);
}
private Integer parseIntOrDefault(String s, Integer defaultVal) {
if (StrUtil.isBlank(s)) {
return defaultVal;
}
try {
return Integer.parseInt(s.trim());
} catch (NumberFormatException e) {
return defaultVal;
}
}
private Integer parseStatusOrDefault(String s, Integer defaultVal) {
if (StrUtil.isBlank(s)) {
return defaultVal;
}
String trimmed = s.trim();
// 优先按 label 匹配
for (ResourceSpuStatus st : ResourceSpuStatus.values()) {
if (st.getLabel().equals(trimmed)) {
return st.getValue();
}
}
// 退化为数字
return parseIntOrDefault(trimmed, defaultVal);
}
}
-- ============================================================
-- 算力资源商品改造 · 阶段 8 补丁
-- 目标:将服务器所在地 location 改为非必填字段
-- 适用库:new_computility
-- 前置:DTO/前端/导入校验已放开(location 允许为 null)
-- 业务上 location 是商家发货时填写,初始导入时无值
-- 配套:
-- - ResourceSpuSaveReqVO.location 去掉必填校验
-- - admin/src/views/compute/resourcespu/ResourceSpuForm.vue
-- 保留 location 字段由用户手动选择(单条保存仍可填)
-- - ResourceSpuImportServiceImpl.processRow 不强制设置 location
-- ============================================================
USE `new_computility`;
ALTER TABLE `compute_resource_spu`
MODIFY COLUMN `location` VARCHAR(255) DEFAULT NULL COMMENT '服务器所在地';
-- 验证:
-- SHOW COLUMNS FROM `compute_resource_spu` LIKE 'location';
-- ============================================================
-- 算力资源商品改造 · 阶段 8 补丁
-- 目标:将商品封面图 pic_url 改为非必填
-- 适用库:new_computility
-- 前置:DTO/前端/导入校验已放开(picUrl 允许为 null)
-- 配套:
-- - ResourceSpuSaveReqVO.picUrl 去掉 @NotEmpty
-- - admin/src/views/compute/resourcespu/ResourceSpuForm.vue
-- 去掉 picUrl 的 required 校验
-- - ResourceSpuImportServiceImpl.processRow 去掉封面图非空校验
-- ============================================================
USE `new_computility`;
ALTER TABLE `compute_resource_spu`
MODIFY COLUMN `pic_url` VARCHAR(255) DEFAULT NULL COMMENT '商品封面图';
-- 验证:
-- SHOW COLUMNS FROM `compute_resource_spu` LIKE 'pic_url';
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 sign in to comment