From 0019ae01ea533e53687a1a68153044336c97ed73 Mon Sep 17 00:00:00 2001 From: litianxiang Date: Mon, 27 Apr 2026 11:47:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E6=9C=8D=E5=8A=A1=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../seller/common/result/PageResponse.java | 15 +- .../aida/seller/config/MyBatisPlusConfig.java | 20 ++ .../controller/DesignerController.java | 30 ++- .../module/designer/dto/DesignerApplyDTO.java | 4 +- .../module/designer/dto/DesignerDTO.java | 40 +++ .../designer/entity/DesignerEntity.java | 7 +- .../designer/service/DesignerService.java | 16 ++ .../designer/service/DesignerServiceImpl.java | 86 +++++- .../module/file/FileUploadController.java | 87 ++++++ .../listing/controller/ListingController.java | 95 +++++++ .../module/listing/dto/ListingImageDTO.java | 35 +++ .../module/listing/dto/ListingQueryDTO.java | 25 ++ .../module/listing/dto/ListingSaveDTO.java | 57 ++++ .../listing/dto/ListingStatusUpdateDTO.java | 23 ++ .../module/listing/dto/ListingStockDTO.java | 23 ++ .../module/listing/entity/ListingEntity.java | 68 +++++ .../listing/entity/ListingImageEntity.java | 40 +++ .../module/listing/enums/DesignForEnum.java | 30 +++ .../listing/enums/ImageCategoryEnum.java | 34 +++ .../listing/enums/ListingStatusEnum.java | 19 ++ .../listing/enums/ProductCategoryEnum.java | 34 +++ .../listing/mapper/ListingImageMapper.java | 12 + .../module/listing/mapper/ListingMapper.java | 12 + .../listing/service/ListingService.java | 73 +++++ .../listing/service/ListingServiceImpl.java | 255 ++++++++++++++++++ .../module/listing/vo/ListingPageVO.java | 40 +++ .../module/order/entity/OrderInfoEntity.java | 6 +- .../module/order/entity/OrderItemEntity.java | 4 +- .../order/service/OrderServiceImpl.java | 5 +- src/main/resources/db/schema.sql | 168 ++++++------ 30 files changed, 1258 insertions(+), 105 deletions(-) create mode 100644 src/main/java/com/aida/seller/module/designer/dto/DesignerDTO.java create mode 100644 src/main/java/com/aida/seller/module/file/FileUploadController.java create mode 100644 src/main/java/com/aida/seller/module/listing/controller/ListingController.java create mode 100644 src/main/java/com/aida/seller/module/listing/dto/ListingImageDTO.java create mode 100644 src/main/java/com/aida/seller/module/listing/dto/ListingQueryDTO.java create mode 100644 src/main/java/com/aida/seller/module/listing/dto/ListingSaveDTO.java create mode 100644 src/main/java/com/aida/seller/module/listing/dto/ListingStatusUpdateDTO.java create mode 100644 src/main/java/com/aida/seller/module/listing/dto/ListingStockDTO.java create mode 100644 src/main/java/com/aida/seller/module/listing/entity/ListingEntity.java create mode 100644 src/main/java/com/aida/seller/module/listing/entity/ListingImageEntity.java create mode 100644 src/main/java/com/aida/seller/module/listing/enums/DesignForEnum.java create mode 100644 src/main/java/com/aida/seller/module/listing/enums/ImageCategoryEnum.java create mode 100644 src/main/java/com/aida/seller/module/listing/enums/ListingStatusEnum.java create mode 100644 src/main/java/com/aida/seller/module/listing/enums/ProductCategoryEnum.java create mode 100644 src/main/java/com/aida/seller/module/listing/mapper/ListingImageMapper.java create mode 100644 src/main/java/com/aida/seller/module/listing/mapper/ListingMapper.java create mode 100644 src/main/java/com/aida/seller/module/listing/service/ListingService.java create mode 100644 src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java create mode 100644 src/main/java/com/aida/seller/module/listing/vo/ListingPageVO.java diff --git a/src/main/java/com/aida/seller/common/result/PageResponse.java b/src/main/java/com/aida/seller/common/result/PageResponse.java index a78a36f..ede75c3 100644 --- a/src/main/java/com/aida/seller/common/result/PageResponse.java +++ b/src/main/java/com/aida/seller/common/result/PageResponse.java @@ -14,7 +14,7 @@ import java.util.List; @Data @NoArgsConstructor @Schema(description = "分页响应结果") -public class PageResponse extends Response> { +public class PageResponse { @Schema(description = "页码") private long page; @Schema(description = "每页数量") @@ -26,21 +26,16 @@ public class PageResponse extends Response> { @Schema(description = "结果集") private List content; - public PageResponse(Response> response, long page, long size, long total, long pages) { - if (response != null) { - this.setData(response.getData()); - this.setErrCode(response.getErrCode()); - this.setErrMsg(response.getErrMsg()); - } + + public PageResponse(List list, long page, long size, long total, long pages) { this.page = page; this.size = size; this.total = total; this.pages = pages; - this.content = response.getData(); + this.content = list; } public static PageResponse success(IPage page) { - Response> response = success(page.getRecords()); - return new PageResponse<>(response, page.getCurrent(), page.getSize(), page.getTotal(), page.getPages()); + return new PageResponse<>(page.getRecords(), page.getCurrent(), page.getSize(), page.getTotal(), page.getPages()); } } diff --git a/src/main/java/com/aida/seller/config/MyBatisPlusConfig.java b/src/main/java/com/aida/seller/config/MyBatisPlusConfig.java index 2900f1b..4bbfded 100644 --- a/src/main/java/com/aida/seller/config/MyBatisPlusConfig.java +++ b/src/main/java/com/aida/seller/config/MyBatisPlusConfig.java @@ -1,11 +1,15 @@ package com.aida.seller.config; import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.apache.ibatis.reflection.MetaObject; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.time.LocalDateTime; + @Configuration public class MyBatisPlusConfig { @@ -15,4 +19,20 @@ public class MyBatisPlusConfig { interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } + + @Bean + public MetaObjectHandler metaObjectHandler() { + return new MetaObjectHandler() { + @Override + public void insertFill(MetaObject metaObject) { + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); + this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } + }; + } } diff --git a/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java b/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java index ad8780e..2d71c08 100644 --- a/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java +++ b/src/main/java/com/aida/seller/module/designer/controller/DesignerController.java @@ -1,9 +1,11 @@ package com.aida.seller.module.designer.controller; +import com.aida.seller.common.context.UserContext; import com.aida.seller.common.result.PageResponse; import com.aida.seller.common.result.Response; import com.aida.seller.module.designer.dto.DesignerApplyDTO; import com.aida.seller.module.designer.dto.DesignerAuditDTO; +import com.aida.seller.module.designer.dto.DesignerDTO; import com.aida.seller.module.designer.entity.DesignerEntity; import com.aida.seller.module.designer.service.DesignerService; import io.swagger.v3.oas.annotations.Operation; @@ -22,8 +24,8 @@ public class DesignerController { @Operation(summary = "查询设计师是否有售卖资格") @GetMapping("/check") - public Response check( - @Parameter(description = "用户ID") @RequestParam Long userId) { + public Response check() { + Long userId = UserContext.getUserId(); boolean hasQualification = designerService.checkQualification(userId); return Response.success(hasQualification); } @@ -61,4 +63,28 @@ public class DesignerController { designerService.audit(dto); return Response.success(); } + + @Operation(summary = "获取设计师申请状态", description = "根据当前登录用户ID获取设计师申请状态0-待审核, 1-审核通过, 2-审核拒绝,null-未申请过") + @GetMapping("/apply/status") + public Response getApplyStatus() { + Long userId = UserContext.getUserId(); + Integer applyStatus = designerService.getApplyStatus(userId); + return Response.success(applyStatus); + } + + @Operation(summary = "更新设计师信息", description = "当前登录设计师更新自身信息") + @PutMapping("/update") + public Response update( + @Parameter(description = "更新表单") @RequestBody DesignerDTO dto) { + designerService.update(dto); + return Response.success(); + } + + @Operation(summary = "获取设计师详细信息", description = "根据当前登录用户ID获取设计师详细信息") + @GetMapping("/info") + public Response getInfo() { + Long userId = UserContext.getUserId(); + DesignerDTO designerInfo = designerService.getDesignerInfo(userId); + return Response.success(designerInfo); + } } diff --git a/src/main/java/com/aida/seller/module/designer/dto/DesignerApplyDTO.java b/src/main/java/com/aida/seller/module/designer/dto/DesignerApplyDTO.java index 7a40b88..36e3259 100644 --- a/src/main/java/com/aida/seller/module/designer/dto/DesignerApplyDTO.java +++ b/src/main/java/com/aida/seller/module/designer/dto/DesignerApplyDTO.java @@ -40,9 +40,11 @@ public class DesignerApplyDTO implements Serializable { @Schema(description = "手机号") @NotBlank(message = "手机号不能为空") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") private String mobile; @Schema(description = "作品集/社交媒体链接(JSON数组)") private String socialLinks; + + @Schema(description = "设计师简介") + private String description; } diff --git a/src/main/java/com/aida/seller/module/designer/dto/DesignerDTO.java b/src/main/java/com/aida/seller/module/designer/dto/DesignerDTO.java new file mode 100644 index 0000000..e615f93 --- /dev/null +++ b/src/main/java/com/aida/seller/module/designer/dto/DesignerDTO.java @@ -0,0 +1,40 @@ +package com.aida.seller.module.designer.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import java.io.Serializable; + +/** + * 设计师信息DTO + */ +@Data +@Schema(description = "设计师信息表单") +public class DesignerDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + @Schema(description = "店铺名称") + private String shopName; + + @Schema(description = "店铺头像URL") + private String avatar; + + @Schema(description = "品牌Banner URL") + private String brandBanner; + + @Schema(description = "所有者全名") + private String ownerName; + + @Schema(description = "邮箱") + private String email; + + @Schema(description = "手机号") + private String mobile; + + @Schema(description = "作品集/社交媒体链接(JSON数组)") + private String socialLinks; + + @Schema(description = "设计师简介") + private String description; +} diff --git a/src/main/java/com/aida/seller/module/designer/entity/DesignerEntity.java b/src/main/java/com/aida/seller/module/designer/entity/DesignerEntity.java index 26acdda..9521f58 100644 --- a/src/main/java/com/aida/seller/module/designer/entity/DesignerEntity.java +++ b/src/main/java/com/aida/seller/module/designer/entity/DesignerEntity.java @@ -9,13 +9,13 @@ import java.time.LocalDateTime; * 设计师表实体类 */ @Data -@TableName("designer") +@TableName("seller_designer") public class DesignerEntity implements Serializable { private static final long serialVersionUID = 1L; /** 设计师ID */ - @TableId(type = IdType.AUTO) + @TableId(type = IdType.ASSIGN_ID) private Long id; /** 用户ID(关联用户表) */ @@ -41,6 +41,9 @@ public class DesignerEntity implements Serializable { /** 作品集/社交媒体链接(JSON数组) */ private String socialLinks; + + /** 设计师简介 */ + private String description; /** 申请状态: 0-待审核, 1-审核通过, 2-审核拒绝 */ private Integer applyStatus; diff --git a/src/main/java/com/aida/seller/module/designer/service/DesignerService.java b/src/main/java/com/aida/seller/module/designer/service/DesignerService.java index 76fe20b..077e801 100644 --- a/src/main/java/com/aida/seller/module/designer/service/DesignerService.java +++ b/src/main/java/com/aida/seller/module/designer/service/DesignerService.java @@ -2,6 +2,7 @@ package com.aida.seller.module.designer.service; import com.aida.seller.module.designer.dto.DesignerApplyDTO; import com.aida.seller.module.designer.dto.DesignerAuditDTO; +import com.aida.seller.module.designer.dto.DesignerDTO; import com.aida.seller.module.designer.entity.DesignerEntity; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.IService; @@ -33,4 +34,19 @@ public interface DesignerService extends IService { * 审核设计师入驻申请 */ void audit(DesignerAuditDTO dto); + + /** + * 获取设计师申请状态 + */ + Integer getApplyStatus(Long userId); + + /** + * 更新设计师信息 + */ + void update(DesignerDTO dto); + + /** + * 获取设计师详细信息 + */ + DesignerDTO getDesignerInfo(Long userId); } diff --git a/src/main/java/com/aida/seller/module/designer/service/DesignerServiceImpl.java b/src/main/java/com/aida/seller/module/designer/service/DesignerServiceImpl.java index 5f13be2..dbbbb3d 100644 --- a/src/main/java/com/aida/seller/module/designer/service/DesignerServiceImpl.java +++ b/src/main/java/com/aida/seller/module/designer/service/DesignerServiceImpl.java @@ -1,11 +1,15 @@ package com.aida.seller.module.designer.service; +import com.aida.seller.common.constants.CommonConstants; +import com.aida.seller.common.context.UserContext; import com.aida.seller.common.exception.BusinessException; import com.aida.seller.module.designer.dto.DesignerApplyDTO; import com.aida.seller.module.designer.dto.DesignerAuditDTO; +import com.aida.seller.module.designer.dto.DesignerDTO; import com.aida.seller.module.designer.entity.DesignerEntity; import com.aida.seller.module.designer.enums.DesignerApplyStatusEnum; import com.aida.seller.module.designer.mapper.DesignerMapper; +import com.aida.seller.util.MinioUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -20,6 +24,7 @@ import java.time.LocalDateTime; @RequiredArgsConstructor public class DesignerServiceImpl extends ServiceImpl implements DesignerService { + private final MinioUtil minioUtil; @Override public Boolean checkQualification(Long userId) { DesignerEntity entity = this.getOne( @@ -50,14 +55,15 @@ public class DesignerServiceImpl extends ServiceImpl() + .eq(DesignerEntity::getUserId, userId) + .last("LIMIT 1") + ); + return entity != null ? entity.getApplyStatus() : null; + } + + @Override + public void update(DesignerDTO dto) { + Long userId = UserContext.getUserId(); + DesignerEntity entity = this.getOne( + new LambdaQueryWrapper() + .eq(DesignerEntity::getUserId, userId) + .last("LIMIT 1") + ); + if (entity == null) { + throw new BusinessException("设计师记录不存在"); + } + + if (dto.getShopName() != null) { + entity.setShopName(dto.getShopName()); + } + if (dto.getAvatar() != null) { + entity.setAvatar(dto.getAvatar()); + } + if (dto.getBrandBanner() != null) { + entity.setBrandBanner(dto.getBrandBanner()); + } + if (dto.getOwnerName() != null) { + entity.setOwnerName(dto.getOwnerName()); + } + if (dto.getEmail() != null) { + entity.setEmail(dto.getEmail()); + } + if (dto.getMobile() != null) { + entity.setMobile(dto.getMobile()); + } + if (dto.getSocialLinks() != null) { + entity.setSocialLinks(dto.getSocialLinks()); + } + if (dto.getDescription() != null) { + entity.setDescription(dto.getDescription()); + } + + this.updateById(entity); + } + + @Override + public DesignerDTO getDesignerInfo(Long userId) { + DesignerEntity entity = this.getOne( + new LambdaQueryWrapper() + .eq(DesignerEntity::getUserId, userId) + .last("LIMIT 1") + ); + if (entity == null) { + throw new BusinessException("设计师记录不存在"); + } + + DesignerDTO dto = new DesignerDTO(); + dto.setShopName(entity.getShopName()); + dto.setAvatar(minioUtil.processMinioResource(entity.getAvatar(), CommonConstants.MINIO_PATH_TIMEOUT)); + dto.setBrandBanner(minioUtil.processMinioResource(entity.getBrandBanner(), CommonConstants.MINIO_PATH_TIMEOUT)); + dto.setOwnerName(entity.getOwnerName()); + dto.setEmail(entity.getEmail()); + dto.setMobile(entity.getMobile()); + dto.setSocialLinks(entity.getSocialLinks()); + dto.setDescription(entity.getDescription()); + + return dto; + } } diff --git a/src/main/java/com/aida/seller/module/file/FileUploadController.java b/src/main/java/com/aida/seller/module/file/FileUploadController.java new file mode 100644 index 0000000..5f3c648 --- /dev/null +++ b/src/main/java/com/aida/seller/module/file/FileUploadController.java @@ -0,0 +1,87 @@ +package com.aida.seller.module.file; + +import cn.hutool.crypto.digest.DigestUtil; +import com.aida.seller.common.constants.CommonConstants; +import com.aida.seller.common.context.UserContext; +import com.aida.seller.common.exception.BusinessException; +import com.aida.seller.common.result.Response; +import com.aida.seller.util.MinioUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; + +/** + * 文件上传控制器 + * + * @author Fida Team + * @date 2026-02-03 + */ +@Slf4j +@RestController +@RequestMapping("/file") +@Tag(name = "文件管理", description = "文件上传、下载等功能接口") +@RequiredArgsConstructor +public class FileUploadController { + + private final MinioUtil minioUtil; + + @Value("${multipart.max-file-size}") + private Long maxFileSize; + + @PostMapping("/upload") + @Operation(summary = "文件上传", description = "上传文件到Minio服务器") + public Response uploadFile( + @Parameter(description = "文件", required = true) @RequestParam("file") MultipartFile file, + @Parameter(description = "允许的文件类型") @RequestParam(value = "allowedTypes", required = false) String[] allowedTypes + ) { + Long userId = UserContext.getUserId(); + + if (file.isEmpty()) { + throw new BusinessException("文件不能为空"); + } + // 验证文件类型 + String contentType = file.getContentType(); + if (allowedTypes != null && allowedTypes.length > 0) { + boolean validType = false; + for (String allowedType : allowedTypes) { + if (contentType != null && contentType.startsWith(allowedType)) { + validType = true; + break; + } + } + if (!validType) { + throw new BusinessException("不支持的文件类型: " + contentType); + } + } + // 验证文件大小 + if (file.getSize() > maxFileSize * 1024 * 1024) { + throw new BusinessException("文件大小超出限制: " + maxFileSize + " MB"); + } + try { + // 计算文件MD5(可选,用于文件完整性校验) + String md5 = DigestUtil.md5Hex(file.getInputStream()); + log.info("文件MD5: {}", md5); + + // 调用MinioUtil上传文件(使用默认桶名,按userId划分文件夹) + String filePath = minioUtil.uploadImage(file, String.valueOf(userId)); + + log.info("文件上传成功: {}, 文件路径: {}", file.getOriginalFilename(), filePath); + + return Response.success(minioUtil.processMinioResource(filePath, CommonConstants.MINIO_PATH_TIMEOUT)); + } catch (IOException e) { + log.error("文件上传失败: {}", e.getMessage(), e); + throw new BusinessException("文件上传失败"); + } + + } + + +} diff --git a/src/main/java/com/aida/seller/module/listing/controller/ListingController.java b/src/main/java/com/aida/seller/module/listing/controller/ListingController.java new file mode 100644 index 0000000..5f190a4 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/controller/ListingController.java @@ -0,0 +1,95 @@ +package com.aida.seller.module.listing.controller; + +import com.aida.seller.common.context.UserContext; +import com.aida.seller.common.result.PageResponse; +import com.aida.seller.common.result.Response; +import com.aida.seller.module.listing.dto.*; +import com.aida.seller.module.listing.service.ListingService; +import com.aida.seller.module.listing.vo.ListingPageVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 商品管理 Controller + */ +@Tag(name = "Listing - 商品管理") +@RestController +@RequestMapping("/listing") +@RequiredArgsConstructor +public class ListingController { + + private final ListingService listingService; + + @Operation(summary = "批量保存/更新商品", description = "根据 id 是否存在判断新增或更新,同时保存图片") + @PostMapping("/batch") + public Response saveOrUpdate( + @Parameter(description = "商品保存/更新表单") @RequestBody ListingSaveDTO dto) { + Long sellerId = UserContext.getUserId(); + listingService.saveOrUpdate(dto, sellerId); + return Response.success(); + } + + @Operation(summary = "获取商品详情", description = "返回商品信息及所有图片") + @GetMapping("/detail") + public Response getById( + @Parameter(description = "商品ID") @RequestParam Long id) { + Long sellerId = UserContext.getUserId(); + ListingSaveDTO result = listingService.getById(id, sellerId); + return Response.success(result); + } + + @Operation(summary = "分页查询商品列表", description = "按 status 过滤,返回 ListingPageVO 不含图片详情") + @GetMapping("/page") + public Response> getPage( + @Parameter(description = "商品状态,可选0-草稿, 1-已发布, 2-已删除") @RequestParam(required = false) Integer status, + @Parameter(description = "页码,默认1") @RequestParam(defaultValue = "1") Integer pageNum, + @Parameter(description = "每页数量,默认10") @RequestParam(defaultValue = "10") Integer pageSize) { + Long sellerId = UserContext.getUserId(); + ListingQueryDTO dto = new ListingQueryDTO(); + dto.setStatus(status); + dto.setPageNum(pageNum); + dto.setPageSize(pageSize); + IPage page = listingService.getPage(dto, sellerId); + return Response.success(PageResponse.success(page)); + } + + @Operation(summary = "更新商品状态", description = "支持设为已删除或恢复为草稿") + @PutMapping("/status") + public Response updateStatus( + @Parameter(description = "状态更新表单") @RequestBody ListingStatusUpdateDTO dto) { + Long sellerId = UserContext.getUserId(); + listingService.updateStatus(dto.getId(), dto.getStatus(), sellerId); + return Response.success(); + } + + @Operation(summary = "批量设置库存", description = "入参为商品ID和库存值列表") + @PutMapping("/stock/batch") + public Response batchUpdateStock( + @Parameter(description = "库存更新列表") @RequestBody List list) { + Long sellerId = UserContext.getUserId(); + listingService.batchUpdateStock(list, sellerId); + return Response.success(); + } + + @Operation(summary = "设置弹窗提醒标志", description = "在Redis中设置7天过期的弹窗提醒标志") + @PostMapping("/popup/set") + public Response setPopupReminder() { + Long sellerId = UserContext.getUserId(); + listingService.setPopupReminder(sellerId); + return Response.success(); + } + + @Operation(summary = "检查是否需要弹窗提醒", description = "检查Redis中是否存在未过期的弹窗提醒标志") + @GetMapping("/popup/check") + public Response checkPopupReminder() { + Long sellerId = UserContext.getUserId(); + boolean needPopup = listingService.checkPopupReminder(sellerId); + return Response.success(needPopup ? 1 : 0); + } +} diff --git a/src/main/java/com/aida/seller/module/listing/dto/ListingImageDTO.java b/src/main/java/com/aida/seller/module/listing/dto/ListingImageDTO.java new file mode 100644 index 0000000..1f4a433 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/dto/ListingImageDTO.java @@ -0,0 +1,35 @@ +package com.aida.seller.module.listing.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 商品图片 DTO(入参/出参复用) + */ +@Data +public class ListingImageDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 图片ID,有值则更新,无则新增 */ + @Schema(description = "图片ID,有值则更新,无则新增") + private Long id; + + /** 图片类别: cover/main_product/product/sketch/apparel */ + @Schema(description = "图片类别: cover/main_product/product/sketch/apparel") + private String category; + + /** 图片URL */ + @Schema(description = "图片URL") + private String imageUrl; + + /** 排序 */ + @Schema(description = "排序") + private Integer sortOrder; + + /** 是否选中: 0-未选中, 1-选中(仅 product 有效) */ + @Schema(description = "是否选中: 0-未选中, 1-选中(仅 product 有效)") + private Boolean isSelected; +} diff --git a/src/main/java/com/aida/seller/module/listing/dto/ListingQueryDTO.java b/src/main/java/com/aida/seller/module/listing/dto/ListingQueryDTO.java new file mode 100644 index 0000000..0be320d --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/dto/ListingQueryDTO.java @@ -0,0 +1,25 @@ +package com.aida.seller.module.listing.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 商品分页查询 DTO + */ +@Data +public class ListingQueryDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 按状态过滤(选填,不传则查所有非删除) */ + @Schema(description = "按状态过滤(选填,不传则查所有非删除)") + private Integer status; + + @Schema(description = "页码,默认1") + private Integer pageNum = 1; + + @Schema(description = "每页数量,默认10") + private Integer pageSize = 10; +} diff --git a/src/main/java/com/aida/seller/module/listing/dto/ListingSaveDTO.java b/src/main/java/com/aida/seller/module/listing/dto/ListingSaveDTO.java new file mode 100644 index 0000000..2ecb898 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/dto/ListingSaveDTO.java @@ -0,0 +1,57 @@ +package com.aida.seller.module.listing.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 商品保存/更新 DTO(入参/出参复用) + */ +@Data +public class ListingSaveDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 商品ID,无则新建,有则更新 */ + @Schema(description = "商品ID,无则新建,有则更新") + private Long id; + + /** 商品标题 */ + @Schema(description = "商品标题") + private String title; + + /** 商品描述 */ + @Schema(description = "商品描述") + private String description; + + /** 价格 */ + @Schema(description = "价格") + private BigDecimal price; + + /** 库存 */ + @Schema(description = "库存") + private Integer stock; + + /** 浏览量(更新时传入) */ + @Schema(description = "浏览量(更新时传入)") + private Integer viewCount; + + /** 状态: 0-草稿, 1-已发布, 2-已删除 */ + @Schema(description = "状态: 0-草稿, 1-已发布, 2-已删除") + private Integer status; + + /** 图片列表(更新时全量覆盖) */ + @Schema(description = "图片列表(更新时全量覆盖)") + private List images; + + /** 适用性别: male/female */ + @Schema(description = "适用性别: male/female") + private String designFor; + + /** 商品分类列表: outwear/trousers/blouse/dress/skirt/accessories */ + @Schema(description = "商品分类列表: outwear/trousers/blouse/dress/skirt/accessories") + private List productCategory; +} diff --git a/src/main/java/com/aida/seller/module/listing/dto/ListingStatusUpdateDTO.java b/src/main/java/com/aida/seller/module/listing/dto/ListingStatusUpdateDTO.java new file mode 100644 index 0000000..cf876a3 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/dto/ListingStatusUpdateDTO.java @@ -0,0 +1,23 @@ +package com.aida.seller.module.listing.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 商品状态更新 DTO + */ +@Data +public class ListingStatusUpdateDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 商品ID */ + @Schema(description = "商品ID") + private Long id; + + /** 目标状态: 0-草稿, 1-已发布, 2-已删除 */ + @Schema(description = "目标状态: 0-草稿, 1-已发布, 2-已删除") + private Integer status; +} diff --git a/src/main/java/com/aida/seller/module/listing/dto/ListingStockDTO.java b/src/main/java/com/aida/seller/module/listing/dto/ListingStockDTO.java new file mode 100644 index 0000000..97af457 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/dto/ListingStockDTO.java @@ -0,0 +1,23 @@ +package com.aida.seller.module.listing.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 批量库存更新 DTO + */ +@Data +public class ListingStockDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 商品ID */ + @Schema(description = "商品ID") + private Long id; + + /** 库存值 */ + @Schema(description = "库存值") + private Integer stock; +} diff --git a/src/main/java/com/aida/seller/module/listing/entity/ListingEntity.java b/src/main/java/com/aida/seller/module/listing/entity/ListingEntity.java new file mode 100644 index 0000000..5a3ca81 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/entity/ListingEntity.java @@ -0,0 +1,68 @@ +package com.aida.seller.module.listing.entity; + +import com.aida.seller.module.listing.enums.ProductCategoryEnum; +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 商品实体 + */ +@Data +@TableName("seller_listing") +public class ListingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 商品ID */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 卖家ID */ + private Long sellerId; + + /** 商品标题 */ + private String title; + + /** 商品描述 */ + private String description; + + /** 价格 */ + private BigDecimal price; + + /** 库存数量 */ + private Integer stock; + + /** 封面图URL(列表页展示用) */ + private String cover; + + /** 浏览量 */ + private Integer viewCount; + + /** 状态: 0-草稿, 1-已发布, 2-已删除 */ + private Integer status; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** 是否删除:0-否,1-是 */ + @TableLogic + private Integer deleted; + + /** 适用性别: male/female */ + private String designFor; + + /** 商品分类列表 */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List productCategory; +} diff --git a/src/main/java/com/aida/seller/module/listing/entity/ListingImageEntity.java b/src/main/java/com/aida/seller/module/listing/entity/ListingImageEntity.java new file mode 100644 index 0000000..a3b6f6d --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/entity/ListingImageEntity.java @@ -0,0 +1,40 @@ +package com.aida.seller.module.listing.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 商品图片实体 + */ +@Data +@TableName("seller_listing_image") +public class ListingImageEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 图片ID */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** 商品ID */ + private Long listingId; + + /** 图片类别: cover/main_product/product/sketch/apparel */ + private String category; + + /** 图片URL */ + private String imageUrl; + + /** 排序 */ + private Integer sortOrder; + + /** 是否选中: 0-未选中, 1-选中(仅 product 有效) */ + private Integer isSelected; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; +} diff --git a/src/main/java/com/aida/seller/module/listing/enums/DesignForEnum.java b/src/main/java/com/aida/seller/module/listing/enums/DesignForEnum.java new file mode 100644 index 0000000..681ddad --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/enums/DesignForEnum.java @@ -0,0 +1,30 @@ +package com.aida.seller.module.listing.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 适用性别枚举 + */ +@Getter +@AllArgsConstructor +public enum DesignForEnum { + + MALE("male", "男性"), + FEMALE("female", "女性"); + + private final String code; + private final String desc; + + public static DesignForEnum of(String code) { + if (code == null) { + return null; + } + for (DesignForEnum value : values()) { + if (value.code.equals(code)) { + return value; + } + } + return null; + } +} diff --git a/src/main/java/com/aida/seller/module/listing/enums/ImageCategoryEnum.java b/src/main/java/com/aida/seller/module/listing/enums/ImageCategoryEnum.java new file mode 100644 index 0000000..d7db920 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/enums/ImageCategoryEnum.java @@ -0,0 +1,34 @@ +package com.aida.seller.module.listing.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 商品图片类别枚举 + */ +@Getter +@AllArgsConstructor +public enum ImageCategoryEnum { + + COVER("cover", "封面图", false), + MAIN_PRODUCT("main_product", "主产品图", false), + PRODUCT("product", "产品图", true), + SKETCH("sketch", "草图", false), + APPAREL("apparel", "成衣图", false); + + private final String code; + private final String desc; + private final boolean hasSelection; + + public static ImageCategoryEnum of(String code) { + if (code == null) { + return null; + } + for (ImageCategoryEnum value : values()) { + if (value.code.equals(code)) { + return value; + } + } + return null; + } +} diff --git a/src/main/java/com/aida/seller/module/listing/enums/ListingStatusEnum.java b/src/main/java/com/aida/seller/module/listing/enums/ListingStatusEnum.java new file mode 100644 index 0000000..6012d36 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/enums/ListingStatusEnum.java @@ -0,0 +1,19 @@ +package com.aida.seller.module.listing.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 商品状态枚举 + */ +@Getter +@AllArgsConstructor +public enum ListingStatusEnum { + + DRAFT(0, "草稿"), + PUBLISHED(1, "已发布"), + DELETED(2, "已删除"); + + private final Integer code; + private final String desc; +} diff --git a/src/main/java/com/aida/seller/module/listing/enums/ProductCategoryEnum.java b/src/main/java/com/aida/seller/module/listing/enums/ProductCategoryEnum.java new file mode 100644 index 0000000..36750ee --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/enums/ProductCategoryEnum.java @@ -0,0 +1,34 @@ +package com.aida.seller.module.listing.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 商品分类枚举 + */ +@Getter +@AllArgsConstructor +public enum ProductCategoryEnum { + + OUTWEAR("outwear", "外套"), + TROUSERS("trousers", "裤装"), + BLOUSE("blouse", "衬衫"), + DRESS("dress", "连衣裙"), + SKIRT("skirt", "半身裙"), + ACCESSORIES("accessories", "配饰"); + + private final String code; + private final String desc; + + public static ProductCategoryEnum of(String code) { + if (code == null) { + return null; + } + for (ProductCategoryEnum value : values()) { + if (value.code.equals(code)) { + return value; + } + } + return null; + } +} diff --git a/src/main/java/com/aida/seller/module/listing/mapper/ListingImageMapper.java b/src/main/java/com/aida/seller/module/listing/mapper/ListingImageMapper.java new file mode 100644 index 0000000..3878118 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/mapper/ListingImageMapper.java @@ -0,0 +1,12 @@ +package com.aida.seller.module.listing.mapper; + +import com.aida.seller.module.listing.entity.ListingImageEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商品图片 Mapper + */ +@Mapper +public interface ListingImageMapper extends BaseMapper { +} diff --git a/src/main/java/com/aida/seller/module/listing/mapper/ListingMapper.java b/src/main/java/com/aida/seller/module/listing/mapper/ListingMapper.java new file mode 100644 index 0000000..5d80803 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/mapper/ListingMapper.java @@ -0,0 +1,12 @@ +package com.aida.seller.module.listing.mapper; + +import com.aida.seller.module.listing.entity.ListingEntity; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 商品 Mapper + */ +@Mapper +public interface ListingMapper extends BaseMapper { +} diff --git a/src/main/java/com/aida/seller/module/listing/service/ListingService.java b/src/main/java/com/aida/seller/module/listing/service/ListingService.java new file mode 100644 index 0000000..acba932 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/service/ListingService.java @@ -0,0 +1,73 @@ +package com.aida.seller.module.listing.service; + +import com.aida.seller.module.listing.dto.ListingQueryDTO; +import com.aida.seller.module.listing.dto.ListingSaveDTO; +import com.aida.seller.module.listing.dto.ListingStockDTO; +import com.aida.seller.module.listing.entity.ListingEntity; +import com.aida.seller.module.listing.vo.ListingPageVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * 商品 Service 接口 + */ +public interface ListingService extends IService { + + /** + * 保存或更新商品(含图片) + * + * @param dto 商品信息 + * @param sellerId 卖家ID + */ + void saveOrUpdate(ListingSaveDTO dto, Long sellerId); + + /** + * 获取商品详情(含所有图片) + * + * @param id 商品ID + * @param sellerId 卖家ID + * @return 商品详情 + */ + ListingSaveDTO getById(Long id, Long sellerId); + + /** + * 分页查询商品列表 + * + * @param dto 查询条件 + * @param sellerId 卖家ID + * @return 分页结果 + */ + IPage getPage(ListingQueryDTO dto, Long sellerId); + + /** + * 更新商品状态 + * + * @param id 商品ID + * @param status 目标状态 + * @param sellerId 卖家ID + */ + void updateStatus(Long id, Integer status, Long sellerId); + + /** + * 批量更新库存 + * + * @param list 库存列表 + * @param sellerId 卖家ID + */ + void batchUpdateStock(java.util.List list, Long sellerId); + + /** + * 设置弹窗提醒标志(7天过期) + * + * @param sellerId 卖家ID + */ + void setPopupReminder(Long sellerId); + + /** + * 检查是否需要弹窗提醒 + * + * @param sellerId 卖家ID + * @return true需要弹窗,false不需要 + */ + boolean checkPopupReminder(Long sellerId); +} diff --git a/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java b/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java new file mode 100644 index 0000000..623f33a --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/service/ListingServiceImpl.java @@ -0,0 +1,255 @@ +package com.aida.seller.module.listing.service; + +import com.aida.seller.common.constants.CommonConstants; +import com.aida.seller.common.exception.BusinessException; +import com.aida.seller.module.listing.dto.*; +import com.aida.seller.module.listing.entity.ListingEntity; +import com.aida.seller.module.listing.entity.ListingImageEntity; +import com.aida.seller.module.listing.enums.ImageCategoryEnum; +import com.aida.seller.module.listing.enums.ListingStatusEnum; +import com.aida.seller.module.listing.enums.DesignForEnum; +import com.aida.seller.module.listing.enums.ProductCategoryEnum; +import com.aida.seller.module.listing.mapper.ListingImageMapper; +import com.aida.seller.module.listing.mapper.ListingMapper; +import com.aida.seller.module.listing.vo.ListingPageVO; +import com.aida.seller.util.MinioUtil; +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.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 商品 Service 实现 + */ +@Service +@RequiredArgsConstructor +public class ListingServiceImpl extends ServiceImpl implements ListingService { + + private final ListingImageMapper listingImageMapper; + private final RedisTemplate redisTemplate; + private final MinioUtil minioUtil; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveOrUpdate(ListingSaveDTO dto, Long sellerId) { + ListingEntity entity = new ListingEntity(); + BeanUtils.copyProperties(dto, entity); + entity.setSellerId(sellerId); + + if (!CollectionUtils.isEmpty(dto.getProductCategory())) { + List categories = dto.getProductCategory().stream() + .map(ProductCategoryEnum::of) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + entity.setProductCategory(categories.isEmpty() ? null : categories); + } + + if (dto.getDesignFor() != null && DesignForEnum.of(dto.getDesignFor()) == null) { + throw new BusinessException("designFor 只能为 male/female"); + } + + if (entity.getViewCount() == null) { + entity.setViewCount(0); + } + + Long listingId; + if (dto.getId() == null) { + entity.setStatus(ListingStatusEnum.DRAFT.getCode()); + this.save(entity); + listingId = entity.getId(); + } else { + ListingEntity existing = this.getOne( + new LambdaQueryWrapper() + .eq(ListingEntity::getId, dto.getId()) + .eq(ListingEntity::getSellerId, sellerId)); + if (existing == null) { + throw new BusinessException("商品不存在"); + } + entity.setCreateTime(existing.getCreateTime()); + this.updateById(entity); + listingImageMapper.delete(new LambdaQueryWrapper() + .eq(ListingImageEntity::getListingId, dto.getId())); + listingId = dto.getId(); + } + + if (!CollectionUtils.isEmpty(dto.getImages())) { + handleImages(listingId, dto.getImages()); + String cover = extractCover(dto.getImages()); + if (StringUtils.hasText(cover)) { + ListingEntity update = new ListingEntity(); + update.setId(listingId); + update.setCover(minioUtil.convertToLogicalPath(cover)); + this.updateById(update); + } + } + } + + @Override + public ListingSaveDTO getById(Long id, Long sellerId) { + ListingEntity entity = this.getOne( + new LambdaQueryWrapper() + .eq(ListingEntity::getId, id) + .eq(ListingEntity::getSellerId, sellerId)); + if (entity == null) { + throw new BusinessException("商品不存在"); + } + + ListingSaveDTO dto = new ListingSaveDTO(); + BeanUtils.copyProperties(entity, dto); + + if (!CollectionUtils.isEmpty(entity.getProductCategory())) { + dto.setProductCategory( + entity.getProductCategory().stream() + .map(ProductCategoryEnum::getCode) + .collect(Collectors.toList())); + } + + List images = listingImageMapper.selectList( + new LambdaQueryWrapper() + .eq(ListingImageEntity::getListingId, id) + .orderByAsc(ListingImageEntity::getSortOrder)); + if (!CollectionUtils.isEmpty(images)) { + List imageDTOs = images.stream().map(img -> { + ListingImageDTO imgDto = new ListingImageDTO(); + BeanUtils.copyProperties(img, imgDto); + imgDto.setImageUrl(minioUtil.processMinioResource(imgDto.getImageUrl(),CommonConstants.MINIO_PATH_TIMEOUT)); + imgDto.setIsSelected(img.getIsSelected() != null && img.getIsSelected() == 1); + return imgDto; + }).collect(Collectors.toList()); + dto.setImages(imageDTOs); + } + return dto; + } + + @Override + public IPage getPage(ListingQueryDTO dto, Long sellerId) { + Page pageParam = new Page<>(dto.getPageNum(), dto.getPageSize()); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ListingEntity::getSellerId, sellerId); + if (dto.getStatus() != null) { + queryWrapper.eq(ListingEntity::getStatus, dto.getStatus()); + } else { + queryWrapper.ne(ListingEntity::getStatus, ListingStatusEnum.DELETED.getCode()); + } + queryWrapper.orderByDesc(ListingEntity::getCreateTime); + IPage page = this.page(pageParam, queryWrapper); + + Page result = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); + result.setRecords(page.getRecords().stream().map(entity -> { + ListingPageVO vo = new ListingPageVO(); + BeanUtils.copyProperties(entity, vo); + vo.setCover(minioUtil.processMinioResource(vo.getCover(), CommonConstants.MINIO_PATH_TIMEOUT)); + return vo; + }).collect(Collectors.toList())); + return result; + } + + @Override + public void updateStatus(Long id, Integer status, Long sellerId) { + ListingEntity existing = this.getOne( + new LambdaQueryWrapper() + .eq(ListingEntity::getId, id) + .eq(ListingEntity::getSellerId, sellerId)); + if (existing == null) { + throw new BusinessException("商品不存在"); + } + ListingEntity update = new ListingEntity(); + update.setId(id); + update.setStatus(status); + this.updateById(update); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void batchUpdateStock(List list, Long sellerId) { + if (CollectionUtils.isEmpty(list)) { + return; + } + List ids = list.stream().map(ListingStockDTO::getId).collect(Collectors.toList()); + List existing = this.list( + new LambdaQueryWrapper() + .eq(ListingEntity::getSellerId, sellerId) + .in(ListingEntity::getId, ids)); + if (existing.size() != ids.size()) { + throw new BusinessException("部分商品不存在或无权操作"); + } + Map stockMap = list.stream() + .collect(Collectors.toMap(ListingStockDTO::getId, ListingStockDTO::getStock)); + existing.forEach(e -> e.setStock(stockMap.get(e.getId()))); + this.updateBatchById(existing); + } + + private void handleImages(Long listingId, List images) { + Map> byCategory = images.stream() + .collect(Collectors.groupingBy(img -> img.getCategory() == null ? "" : img.getCategory())); + + for (Map.Entry> entry : byCategory.entrySet()) { + String category = entry.getKey(); + List categoryImages = entry.getValue(); + + if (!StringUtils.hasText(category)) { + continue; + } + + ImageCategoryEnum categoryEnum = ImageCategoryEnum.of(category); + boolean supportsSelection = categoryEnum != null && categoryEnum.isHasSelection(); + + for (int i = 0; i < categoryImages.size(); i++) { + ListingImageDTO imgDto = categoryImages.get(i); + ListingImageEntity imgEntity = new ListingImageEntity(); + imgEntity.setListingId(listingId); + imgEntity.setCategory(category); + imgEntity.setImageUrl(minioUtil.convertToLogicalPath(imgDto.getImageUrl())); + imgEntity.setSortOrder(imgDto.getSortOrder() != null ? imgDto.getSortOrder() : i); + + if (supportsSelection) { + imgEntity.setIsSelected(Boolean.TRUE.equals(imgDto.getIsSelected()) ? 1 : 0); + } else { + imgEntity.setIsSelected(0); + } + listingImageMapper.insert(imgEntity); + } + } + } + + private String extractCover(List images) { + if (CollectionUtils.isEmpty(images)) { + return null; + } + for (ListingImageDTO img : images) { + if (ImageCategoryEnum.COVER.getCode().equals(img.getCategory())) { + return img.getImageUrl(); + } + } + if (!CollectionUtils.isEmpty(images)) { + return images.get(0).getImageUrl(); + } + return null; + } + + @Override + public void setPopupReminder(Long sellerId) { + String key = "popup:reminder:" + sellerId; + redisTemplate.opsForValue().set(key, true, 7, java.util.concurrent.TimeUnit.DAYS); + } + + @Override + public boolean checkPopupReminder(Long sellerId) { + String key = "popup:reminder:" + sellerId; + Object value = redisTemplate.opsForValue().get(key); + return value == null; + } +} diff --git a/src/main/java/com/aida/seller/module/listing/vo/ListingPageVO.java b/src/main/java/com/aida/seller/module/listing/vo/ListingPageVO.java new file mode 100644 index 0000000..3fd0017 --- /dev/null +++ b/src/main/java/com/aida/seller/module/listing/vo/ListingPageVO.java @@ -0,0 +1,40 @@ +package com.aida.seller.module.listing.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 商品分页列表 VO(不含图片详情) + */ +@Data +public class ListingPageVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 商品ID */ + private Long id; + + /** 封面图URL(列表页直返,无须关联图片表) */ + private String cover; + + /** 商品标题 */ + private String title; + + /** 价格 */ + private BigDecimal price; + + /** 库存 */ + private Integer stock; + + /** 浏览量 */ + private Integer viewCount; + + /** 状态 */ + private Integer status; + + /** 创建时间(用于排序) */ + private LocalDateTime createTime; +} diff --git a/src/main/java/com/aida/seller/module/order/entity/OrderInfoEntity.java b/src/main/java/com/aida/seller/module/order/entity/OrderInfoEntity.java index 5a391eb..2c2d594 100644 --- a/src/main/java/com/aida/seller/module/order/entity/OrderInfoEntity.java +++ b/src/main/java/com/aida/seller/module/order/entity/OrderInfoEntity.java @@ -11,13 +11,15 @@ import java.time.LocalDateTime; * 订单主表 */ @Data -@TableName("order_info") +@TableName("seller_orders") public class OrderInfoEntity implements Serializable { private static final long serialVersionUID = 1L; + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** 订单唯一标识(如 SP897772698) */ - @TableId(type = IdType.INPUT) private String orderId; /** 卖家ID */ diff --git a/src/main/java/com/aida/seller/module/order/entity/OrderItemEntity.java b/src/main/java/com/aida/seller/module/order/entity/OrderItemEntity.java index 8b582c7..bd8a969 100644 --- a/src/main/java/com/aida/seller/module/order/entity/OrderItemEntity.java +++ b/src/main/java/com/aida/seller/module/order/entity/OrderItemEntity.java @@ -11,13 +11,13 @@ import java.time.LocalDateTime; * 订单商品明细表 */ @Data -@TableName("order_item") +@TableName("seller_order_item") public class OrderItemEntity implements Serializable { private static final long serialVersionUID = 1L; /** 主键ID */ - @TableId(type = IdType.AUTO) + @TableId(type = IdType.ASSIGN_ID) private Long id; /** 订单ID(关联 order_info) */ diff --git a/src/main/java/com/aida/seller/module/order/service/OrderServiceImpl.java b/src/main/java/com/aida/seller/module/order/service/OrderServiceImpl.java index 98f9c63..a75de5a 100644 --- a/src/main/java/com/aida/seller/module/order/service/OrderServiceImpl.java +++ b/src/main/java/com/aida/seller/module/order/service/OrderServiceImpl.java @@ -1,5 +1,6 @@ package com.aida.seller.module.order.service; +import com.aida.seller.common.constants.CommonConstants; import com.aida.seller.module.order.dto.OrderListDTO; import com.aida.seller.module.order.entity.OrderInfoEntity; import com.aida.seller.module.order.entity.OrderItemEntity; @@ -7,6 +8,7 @@ import com.aida.seller.module.order.mapper.OrderInfoMapper; import com.aida.seller.module.order.mapper.OrderItemMapper; import com.aida.seller.module.order.vo.OrderSummaryVO; import com.aida.seller.module.order.vo.OrderVO; +import com.aida.seller.util.MinioUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -30,6 +32,7 @@ import java.util.stream.Collectors; public class OrderServiceImpl extends ServiceImpl implements OrderService { private final OrderItemMapper orderItemMapper; + private final MinioUtil minioUtil; /** * 查询指定卖家的订单汇总数据 @@ -107,7 +110,7 @@ public class OrderServiceImpl extends ServiceImpl