微服务改造

This commit is contained in:
litianxiang
2026-04-27 11:47:17 +08:00
parent 1504e86b35
commit 0019ae01ea
30 changed files with 1258 additions and 105 deletions

View File

@@ -14,7 +14,7 @@ import java.util.List;
@Data
@NoArgsConstructor
@Schema(description = "分页响应结果")
public class PageResponse<T> extends Response<List<T>> {
public class PageResponse<T> {
@Schema(description = "页码")
private long page;
@Schema(description = "每页数量")
@@ -26,21 +26,16 @@ public class PageResponse<T> extends Response<List<T>> {
@Schema(description = "结果集")
private List<T> content;
public PageResponse(Response<List<T>> 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<T> 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 <T> PageResponse<T> success(IPage<T> page) {
Response<List<T>> 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());
}
}

View File

@@ -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());
}
};
}
}

View File

@@ -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<Boolean> check(
@Parameter(description = "用户ID") @RequestParam Long userId) {
public Response<Boolean> 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<Integer> getApplyStatus() {
Long userId = UserContext.getUserId();
Integer applyStatus = designerService.getApplyStatus(userId);
return Response.success(applyStatus);
}
@Operation(summary = "更新设计师信息", description = "当前登录设计师更新自身信息")
@PutMapping("/update")
public Response<Void> update(
@Parameter(description = "更新表单") @RequestBody DesignerDTO dto) {
designerService.update(dto);
return Response.success();
}
@Operation(summary = "获取设计师详细信息", description = "根据当前登录用户ID获取设计师详细信息")
@GetMapping("/info")
public Response<DesignerDTO> getInfo() {
Long userId = UserContext.getUserId();
DesignerDTO designerInfo = designerService.getDesignerInfo(userId);
return Response.success(designerInfo);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<DesignerEntity> {
* 审核设计师入驻申请
*/
void audit(DesignerAuditDTO dto);
/**
* 获取设计师申请状态
*/
Integer getApplyStatus(Long userId);
/**
* 更新设计师信息
*/
void update(DesignerDTO dto);
/**
* 获取设计师详细信息
*/
DesignerDTO getDesignerInfo(Long userId);
}

View File

@@ -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<DesignerMapper, DesignerEntity> 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<DesignerMapper, DesignerEnt
}
DesignerEntity entity = new DesignerEntity();
entity.setUserId(dto.getUserId());
entity.setUserId(UserContext.getUserId());
entity.setShopName(dto.getShopName());
entity.setAvatar(dto.getAvatar());
entity.setBrandBanner(dto.getBrandBanner());
entity.setAvatar(minioUtil.convertToLogicalPath(dto.getAvatar()));
entity.setBrandBanner(minioUtil.convertToLogicalPath(dto.getBrandBanner()));
entity.setOwnerName(dto.getOwnerName());
entity.setEmail(dto.getEmail());
entity.setMobile(dto.getMobile());
entity.setSocialLinks(dto.getSocialLinks());
entity.setDescription(dto.getDescription());
entity.setApplyStatus(DesignerApplyStatusEnum.PENDING.getCode());
entity.setStatus(0);
@@ -130,4 +136,78 @@ public class DesignerServiceImpl extends ServiceImpl<DesignerMapper, DesignerEnt
this.updateById(entity);
}
@Override
public Integer getApplyStatus(Long userId) {
DesignerEntity entity = this.getOne(
new LambdaQueryWrapper<DesignerEntity>()
.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<DesignerEntity>()
.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<DesignerEntity>()
.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;
}
}

View File

@@ -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<String> 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("文件上传失败");
}
}
}

View File

@@ -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<Void> saveOrUpdate(
@Parameter(description = "商品保存/更新表单") @RequestBody ListingSaveDTO dto) {
Long sellerId = UserContext.getUserId();
listingService.saveOrUpdate(dto, sellerId);
return Response.success();
}
@Operation(summary = "获取商品详情", description = "返回商品信息及所有图片")
@GetMapping("/detail")
public Response<ListingSaveDTO> 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<PageResponse<ListingPageVO>> 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<ListingPageVO> page = listingService.getPage(dto, sellerId);
return Response.success(PageResponse.success(page));
}
@Operation(summary = "更新商品状态", description = "支持设为已删除或恢复为草稿")
@PutMapping("/status")
public Response<Void> 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<Void> batchUpdateStock(
@Parameter(description = "库存更新列表") @RequestBody List<ListingStockDTO> list) {
Long sellerId = UserContext.getUserId();
listingService.batchUpdateStock(list, sellerId);
return Response.success();
}
@Operation(summary = "设置弹窗提醒标志", description = "在Redis中设置7天过期的弹窗提醒标志")
@PostMapping("/popup/set")
public Response<Void> setPopupReminder() {
Long sellerId = UserContext.getUserId();
listingService.setPopupReminder(sellerId);
return Response.success();
}
@Operation(summary = "检查是否需要弹窗提醒", description = "检查Redis中是否存在未过期的弹窗提醒标志")
@GetMapping("/popup/check")
public Response<Integer> checkPopupReminder() {
Long sellerId = UserContext.getUserId();
boolean needPopup = listingService.checkPopupReminder(sellerId);
return Response.success(needPopup ? 1 : 0);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<ListingImageDTO> 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<String> productCategory;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<ProductCategoryEnum> productCategory;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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<ListingImageEntity> {
}

View File

@@ -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<ListingEntity> {
}

View File

@@ -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<ListingEntity> {
/**
* 保存或更新商品(含图片)
*
* @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<ListingPageVO> 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<ListingStockDTO> list, Long sellerId);
/**
* 设置弹窗提醒标志7天过期
*
* @param sellerId 卖家ID
*/
void setPopupReminder(Long sellerId);
/**
* 检查是否需要弹窗提醒
*
* @param sellerId 卖家ID
* @return true需要弹窗false不需要
*/
boolean checkPopupReminder(Long sellerId);
}

View File

@@ -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<ListingMapper, ListingEntity> implements ListingService {
private final ListingImageMapper listingImageMapper;
private final RedisTemplate<String, Object> 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<ProductCategoryEnum> 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<ListingEntity>()
.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<ListingImageEntity>()
.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<ListingEntity>()
.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<ListingImageEntity> images = listingImageMapper.selectList(
new LambdaQueryWrapper<ListingImageEntity>()
.eq(ListingImageEntity::getListingId, id)
.orderByAsc(ListingImageEntity::getSortOrder));
if (!CollectionUtils.isEmpty(images)) {
List<ListingImageDTO> 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<ListingPageVO> getPage(ListingQueryDTO dto, Long sellerId) {
Page<ListingEntity> pageParam = new Page<>(dto.getPageNum(), dto.getPageSize());
LambdaQueryWrapper<ListingEntity> 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<ListingEntity> page = this.page(pageParam, queryWrapper);
Page<ListingPageVO> 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<ListingEntity>()
.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<ListingStockDTO> list, Long sellerId) {
if (CollectionUtils.isEmpty(list)) {
return;
}
List<Long> ids = list.stream().map(ListingStockDTO::getId).collect(Collectors.toList());
List<ListingEntity> existing = this.list(
new LambdaQueryWrapper<ListingEntity>()
.eq(ListingEntity::getSellerId, sellerId)
.in(ListingEntity::getId, ids));
if (existing.size() != ids.size()) {
throw new BusinessException("部分商品不存在或无权操作");
}
Map<Long, Integer> 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<ListingImageDTO> images) {
Map<String, List<ListingImageDTO>> byCategory = images.stream()
.collect(Collectors.groupingBy(img -> img.getCategory() == null ? "" : img.getCategory()));
for (Map.Entry<String, List<ListingImageDTO>> entry : byCategory.entrySet()) {
String category = entry.getKey();
List<ListingImageDTO> 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<ListingImageDTO> 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;
}
}

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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<OrderInfoMapper, OrderInfoEntity> implements OrderService {
private final OrderItemMapper orderItemMapper;
private final MinioUtil minioUtil;
/**
* 查询指定卖家的订单汇总数据
@@ -107,7 +110,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
OrderVO.ItemVO itemVO = new OrderVO.ItemVO();
itemVO.setProductId(item.getProductId());
itemVO.setProductName(item.getProductName());
itemVO.setThumbnailUrl(item.getThumbnailUrl());
itemVO.setThumbnailUrl(minioUtil.processMinioResource(item.getThumbnailUrl(), CommonConstants.MINIO_PATH_TIMEOUT));
return itemVO;
}).collect(Collectors.toList());
vo.setItems(itemVOs);

View File

@@ -1,87 +1,91 @@
-- 商品表
CREATE TABLE seller_listing (
id BIGINT PRIMARY KEY COMMENT '商品ID',
seller_id BIGINT NOT NULL COMMENT '卖家ID',
title VARCHAR(255) NOT NULL COMMENT '商品标题',
description TEXT COMMENT '商品描述',
price DECIMAL(10,2) COMMENT '价格',
stock INT COMMENT '库存数量',
cover VARCHAR(200) COMMENT '封面图URL',
view_count INT DEFAULT 0 COMMENT '浏览量',
status INT(1) DEFAULT 0 COMMENT '状态: 0-草稿, 1-已发布, 2-已删除',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted INT(1) DEFAULT 0 COMMENT '是否删除0-否1-是',
design_for VARCHAR(50) COMMENT '适用性别: male/female',
product_category JSON COMMENT '商品分类列表',
INDEX idx_seller_id (seller_id),
INDEX idx_status (status),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
-- 商品图片表
CREATE TABLE seller_listing_image (
id BIGINT PRIMARY KEY COMMENT '图片ID',
listing_id BIGINT NOT NULL COMMENT '商品ID',
category VARCHAR(50) NOT NULL COMMENT '图片类别: cover/main_product/product/sketch/apparel',
image_url VARCHAR(500) NOT NULL COMMENT '图片URL',
sort_order INT DEFAULT 0 COMMENT '排序',
is_selected INT(1) DEFAULT 0 COMMENT '是否选中: 0-未选中, 1-选中(仅product有效)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_listing_id (listing_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品图片表';
CREATE DATABASE IF NOT EXISTS aida_seller DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE aida_seller;
-- ==================== 1. 设计师表 ====================
DROP TABLE IF EXISTS `designer`;
CREATE TABLE `designer` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '设计师ID',
`user_id` BIGINT DEFAULT NULL COMMENT '用户ID(关联用户表)',
`shop_name` VARCHAR(100) NOT NULL COMMENT '店铺名称',
`owner_name` VARCHAR(50) NOT NULL COMMENT '所有者全名',
`email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
`mobile` VARCHAR(20) NOT NULL COMMENT '手机号',
`social_links` TEXT DEFAULT NULL COMMENT '作品集/社交媒体链接(JSON数组)',
`apply_status` TINYINT NOT NULL DEFAULT 0 COMMENT '申请状态: 0-待审核, 1-审核通过, 2-审核拒绝',
`audit_remark` VARCHAR(500) DEFAULT NULL COMMENT '审核备注',
`audit_time` DATETIME DEFAULT NULL COMMENT '审核时间',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态: 0-禁用, 1-启用',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
PRIMARY KEY (`id`),
KEY `idx_mobile` (`mobile`),
KEY `idx_email` (`email`),
KEY `idx_apply_status` (`apply_status`),
KEY `idx_status` (`status`)
-- 设计师表
CREATE TABLE seller_designer (
id BIGINT PRIMARY KEY COMMENT '设计师ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
shop_name VARCHAR(100) NOT NULL COMMENT '店铺名称',
avatar VARCHAR(200) COMMENT '店铺头像URL',
brand_banner VARCHAR(200) COMMENT '品牌Banner URL',
owner_name VARCHAR(100) COMMENT '所有者全名',
email VARCHAR(100) COMMENT '邮箱',
mobile VARCHAR(30) COMMENT '手机号',
social_links JSON COMMENT '作品集/社交媒体链接',
description TEXT COMMENT '设计师简介',
apply_status INT(1) DEFAULT 0 COMMENT '申请状态: 0-待审核, 1-审核通过, 2-审核拒绝',
audit_remark VARCHAR(500) COMMENT '审核备注',
audit_time DATETIME COMMENT '审核时间',
status INT(1) DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted INT(1) DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
INDEX idx_user_id (user_id),
INDEX idx_apply_status (apply_status),
INDEX idx_status (status),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设计师表';
-- ==================== 2. 订单表 ====================
-- 注意: 代码中 OrderInfoEntity 使用 @TableName("order_info")
-- 若生产库表名为 "orders" 请改为 "order_info",列名 "order_no" 建议改为 "order_id"
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` VARCHAR(32) NOT NULL COMMENT '订单',
`seller_id` BIGINT NOT NULL COMMENT '家ID',
`buyer_id` BIGINT NOT NULL COMMENT '买家ID',
`buyer_name` VARCHAR(100) DEFAULT NULL COMMENT '买家名称',
`total_price` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '商品总金额',
`order_status` TINYINT NOT NULL DEFAULT 0 COMMENT '订单状态: 0-待支付, 1-已支付, 2-已发货, 3-已完成, 4-已取消, 5-退款中',
`total_views` BIGINT NOT NULL DEFAULT 0 COMMENT '商品浏览量(订单关联商品的总浏览数)',
`shipping_address` TEXT DEFAULT NULL COMMENT '收货地址',
`receiver_name` VARCHAR(50) DEFAULT NULL COMMENT '收货人',
`receiver_phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
`receiver_address` VARCHAR(500) DEFAULT NULL COMMENT '详细地址',
`tracking_number` VARCHAR(100) DEFAULT NULL COMMENT '快递单号',
`tracking_company` VARCHAR(100) DEFAULT NULL COMMENT '快递公司',
`pay_time` DATETIME DEFAULT NULL COMMENT '支付时间',
`ship_time` DATETIME DEFAULT NULL COMMENT '发货时间',
`receive_time` DATETIME DEFAULT NULL COMMENT '收货时间',
`cancel_time` DATETIME DEFAULT NULL COMMENT '取消时间',
`cancel_reason` VARCHAR(500) DEFAULT NULL COMMENT '取消原因',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_id` (`order_id`),
KEY `idx_seller_id` (`seller_id`),
KEY `idx_buyer_id` (`buyer_id`),
KEY `idx_order_status` (`order_status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
-- 订单主表
CREATE TABLE seller_orders (
id BIGINT PRIMARY KEY COMMENT '主键ID',
order_id VARCHAR(50) NOT NULL COMMENT '订单唯一标识(如 SP897772698',
seller_id BIGINT NOT NULL COMMENT '卖家ID',
total_price DECIMAL(10,2) COMMENT '订单总金额(HK$)',
buyer_username VARCHAR(100) COMMENT '买家账',
total_items INT COMMENT '品总数量',
total_views BIGINT DEFAULT 0 COMMENT '总浏览量',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted INT(1) DEFAULT 0 COMMENT '是否删除0-否1-是',
INDEX idx_order_id (order_id),
INDEX idx_seller_id (seller_id),
INDEX idx_deleted (deleted),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';
-- ==================== 3. 订单项表 ====================
DROP TABLE IF EXISTS `order_item`;
CREATE TABLE `order_item` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_id` BIGINT NOT NULL COMMENT '订单ID(关联orders.id)',
`order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
`product_id` BIGINT NOT NULL COMMENT '商品ID',
`sku_id` BIGINT DEFAULT NULL COMMENT 'SKU ID',
`product_name` VARCHAR(200) NOT NULL COMMENT '商品名称',
`sku_name` VARCHAR(200) DEFAULT NULL COMMENT 'SKU名称',
`product_image` VARCHAR(500) DEFAULT NULL COMMENT '商品图片',
`price` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '商品单价',
`quantity` INT NOT NULL DEFAULT 1 COMMENT '购买数量',
`total_amount` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '小计金额',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_order_no` (`order_no`),
KEY `idx_product_id` (`product_id`),
KEY `idx_sku_id` (`sku_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单项表';
-- 订单商品明细表
CREATE TABLE seller_order_item (
id BIGINT PRIMARY KEY COMMENT '主键ID',
order_id VARCHAR(50) NOT NULL COMMENT '订单ID',
product_id BIGINT NOT NULL COMMENT '商品ID',
product_name VARCHAR(255) COMMENT '商品名称',
thumbnail_url VARCHAR(200) COMMENT '商品缩略图URL',
price DECIMAL(10,2) COMMENT '成交单价(HK$)',
quantity INT NOT NULL COMMENT '购买数量',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
deleted INT(1) DEFAULT 0 COMMENT '是否删除0-否1-是',
INDEX idx_order_id (order_id),
INDEX idx_product_id (product_id),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品明细表';