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;
}
-- ============================================================
-- 算力资源商品改造 · 阶段 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