first commit

This commit is contained in:
dev
2026-02-22 12:12:02 +08:00
commit b236048a24
1467 changed files with 160403 additions and 0 deletions
@@ -0,0 +1,201 @@
# 交易中心模块 (ruoyi-mall-trade)
## 模块简介
`ruoyi-mall-trade` 是商城系统的交易中心模块,负责交易流程的核心功能,包括购物车、订单、售后等。
## 功能特性
### 1. 购物车管理
- ✅ 添加/删除购物车
- ✅ 购物车数量修改
- ✅ 购物车选中状态管理
- ✅ 购物车详情查询
### 2. 订单管理
- ✅ 订单创建
- ✅ 订单查询(列表、详情)
- ✅ 订单取消
- ✅ 订单状态管理
- ✅ 订单支付集成
### 3. 售后管理 ✅
- ✅ 订单退款申请
- ✅ 退货申请
- ✅ 换货申请
- ✅ 售后审核(同意/拒绝)
- ✅ 售后状态跟踪
- ✅ 退货物流信息填写
- ✅ 商家确认收货
- ✅ 完成售后处理
- ✅ 取消售后申请
### 4. 订单评价 ✅
- ✅ 订单评价提交
- ✅ 评价列表查询(支持按订单、商品查询)
- ✅ 评价审核(通过/驳回)
- ✅ 评价回复(商家回复)
- ✅ 评价导出功能
### 5. 地址管理 ✅
- ✅ 收货地址CRUD操作
- ✅ 用户地址列表查询
- ✅ 默认地址查询
- ✅ 设置默认地址
- ✅ 地址导出功能(管理端)
## 数据库表
- `mall_cart` - 购物车表
- `mall_order` - 订单表
- `mall_order_item` - 订单明细表
- `mall_after_sale` - 售后申请表
- `mall_order_evaluate` - 订单评价表
- `mall_order_coupon` - 订单优惠券使用记录表
- `mall_address` - 收货地址表
详细表结构请参考:`script/sql/ry_mall.sql`
## 模块结构
```
org.dromara.mall.trade
├── controller
│ ├── admin // 管理端API
│ └── app // 用户端API
├── domain
│ ├── bo // 业务对象
│ └── vo // 视图对象
├── mapper // MyBatis Mapper
├── service // 业务逻辑层
│ └── impl
├── enums // 枚举类
└── config // 配置类
```
## 依赖模块
- `ruoyi-mall-product` - 商品中心模块
- `ruoyi-pay` - 支付中心模块
- `ruoyi-common-*` - 通用工具模块
## 业务流程
### 订单提交流程
1. 用户选择商品(从购物车或直接购买)
2. 调用订单提交接口
3. 系统创建订单和订单明细
4. 调用支付中心创建支付单
5. 返回支付参数给前端
6. 前端调起支付
7. 支付成功后,支付中心回调
8. 系统更新订单状态并扣减库存
## 模块结构
```
org.dromara.mall.trade
├── controller
│ ├── admin // 管理端API
│ │ ├── AdminMallAfterSaleController.java
│ │ ├── AdminMallOrderEvaluateController.java
│ │ └── AdminMallAddressController.java
│ └── app // 用户端API
│ ├── AppMallAfterSaleController.java
│ ├── AppMallOrderEvaluateController.java
│ └── AppMallAddressController.java
├── domain
│ ├── MallAfterSale.java
│ ├── MallOrderEvaluate.java
│ ├── MallOrderCoupon.java
│ ├── MallAddress.java
│ ├── bo // 业务对象
│ │ ├── MallAfterSaleBo.java
│ │ ├── MallOrderEvaluateBo.java
│ │ ├── MallOrderCouponBo.java
│ │ └── MallAddressBo.java
│ └── vo // 视图对象
│ ├── MallAfterSaleVo.java
│ ├── MallOrderEvaluateVo.java
│ ├── MallOrderCouponVo.java
│ └── MallAddressVo.java
├── mapper
│ ├── MallAfterSaleMapper.java
│ ├── MallOrderEvaluateMapper.java
│ ├── MallOrderCouponMapper.java
│ └── MallAddressMapper.java
├── service
│ ├── MallAfterSaleService.java
│ ├── MallOrderEvaluateService.java
│ ├── MallOrderCouponService.java
│ ├── MallAddressService.java
│ ├── SeckillOrderService.java
│ └── impl
│ ├── MallAfterSaleServiceImpl.java
│ ├── MallOrderEvaluateServiceImpl.java
│ ├── MallOrderCouponServiceImpl.java
│ ├── MallAddressServiceImpl.java
│ └── SeckillOrderServiceImpl.java
├── utils
│ └── CouponCalculator.java
└── config
└── MallTradeAutoConfiguration.java
```
## API接口
### 管理端接口
#### 售后申请管理 (`/mall/order/after-sale`)
- `GET /mall/order/after-sale/list` - 查询售后申请列表(分页)
- `GET /mall/order/after-sale/{id}` - 获取售后申请详情
- `DELETE /mall/order/after-sale/{ids}` - 删除售后申请
- `POST /mall/order/after-sale/export` - 导出售后申请列表
- `PUT /mall/order/after-sale/audit/{id}` - 审核售后申请(同意/拒绝)
- `PUT /mall/order/after-sale/receive/{id}` - 确认收货(商家确认收到退货)
- `PUT /mall/order/after-sale/complete/{id}` - 完成售后
#### 订单评价管理 (`/mall/order/evaluate`)
- `GET /mall/order/evaluate/list` - 查询订单评价列表(分页)
- `GET /mall/order/evaluate/{id}` - 获取订单评价详情
- `DELETE /mall/order/evaluate/{ids}` - 删除订单评价
- `POST /mall/order/evaluate/export` - 导出订单评价列表
- `PUT /mall/order/evaluate/audit/{id}` - 审核评价(通过/驳回)
- `PUT /mall/order/evaluate/reply/{id}` - 回复评价
### 移动端接口
#### 售后申请 (`/app/mall/order/after-sale`)
- `GET /app/mall/order/after-sale/list` - 查询售后申请列表
- `GET /app/mall/order/after-sale/order/{orderId}` - 根据订单ID查询售后申请列表
- `GET /app/mall/order/after-sale/{id}` - 获取售后申请详情
- `POST /app/mall/order/after-sale` - 新增售后申请(退款/退货/换货)
- `PUT /app/mall/order/after-sale/logistics/{id}` - 填写退货物流信息
- `PUT /app/mall/order/after-sale/cancel/{id}` - 取消售后申请
#### 订单评价 (`/app/mall/order/evaluate`)
- `GET /app/mall/order/evaluate/list` - 查询订单评价列表
- `GET /app/mall/order/evaluate/order/{orderId}` - 根据订单ID查询评价列表
- `GET /app/mall/order/evaluate/product/{productId}` - 根据商品ID查询评价列表(仅已审核)
- `GET /app/mall/order/evaluate/{id}` - 获取订单评价详情
- `POST /app/mall/order/evaluate` - 提交订单评价
#### 收货地址 (`/app/mall/address`)
- `GET /app/mall/address/list` - 查询收货地址列表
- `GET /app/mall/address/default` - 获取默认收货地址
- `GET /app/mall/address/{id}` - 获取收货地址详情
- `POST /app/mall/address` - 新增收货地址
- `PUT /app/mall/address` - 修改收货地址
- `DELETE /app/mall/address/{ids}` - 删除收货地址
- `PUT /app/mall/address/default/{id}` - 设置默认地址
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-mall</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-mall-trade</artifactId>
<description>
ruoyi-mall-trade 交易中心模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-excel</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<!-- 商品中心模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-mall-product</artifactId>
<version>${revision}</version>
</dependency>
<!-- 支付模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-pay</artifactId>
<version>${revision}</version>
</dependency>
<!-- 营销中心模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-mall-promotion</artifactId>
<version>${revision}</version>
</dependency>
<!-- 优惠券中心模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-mall-coupon</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,23 @@
package org.dromara.mall.trade.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 交易中心自动配置类
* 主要用于扫描交易中心模块的组件
*
* @author pengles
*/
@Slf4j
@Configuration
@ComponentScan(basePackages = "org.dromara.mall.trade")
@ConditionalOnProperty(prefix = "ruoyi.mall.trade", name = "enabled", havingValue = "true", matchIfMissing = true)
public class MallTradeAutoConfiguration {
public MallTradeAutoConfiguration() {
log.info("交易中心模块已启用");
}
}
@@ -0,0 +1,111 @@
package org.dromara.mall.trade.controller.admin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.mall.trade.domain.bo.MallAddressBo;
import org.dromara.mall.trade.domain.vo.MallAddressVo;
import org.dromara.mall.trade.service.MallAddressService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
* 收货地址管理
*
* @author pengles
*/
@Tag(name = "管理端-收货地址管理", description = "收货地址管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/mall/address")
public class AdminMallAddressController extends BaseController {
private final MallAddressService mallAddressService;
/**
* 查询收货地址列表
*/
@Operation(summary = "查询收货地址列表", description = "分页查询收货地址列表")
@SaCheckPermission("mall:address:list")
@GetMapping("/list")
public TableDataInfo<MallAddressVo> list(@Parameter(description = "收货地址查询条件") MallAddressBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return mallAddressService.queryPageList(bo, pageQuery);
}
/**
* 导出收货地址列表
*/
@Operation(summary = "导出收货地址列表", description = "导出收货地址到Excel")
@SaCheckPermission("mall:address:export")
@Log(title = "收货地址", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "收货地址查询条件") MallAddressBo bo,
@Parameter(description = "响应对象") HttpServletResponse response) {
List<MallAddressVo> list = mallAddressService.queryList(bo);
ExcelUtil.exportExcel(list, "收货地址", MallAddressVo.class, response);
}
/**
* 获取收货地址详细信息
*/
@Operation(summary = "获取收货地址详情", description = "根据地址ID获取详细信息")
@SaCheckPermission("mall:address:query")
@GetMapping("/{id}")
public R<MallAddressVo> getInfo(@Parameter(description = "地址ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallAddressService.queryById(id));
}
/**
* 新增收货地址
*/
@Operation(summary = "新增收货地址", description = "创建新的收货地址")
@SaCheckPermission("mall:address:add")
@Log(title = "收货地址", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Parameter(description = "收货地址信息") @Validated @RequestBody MallAddressBo bo) {
return toAjax(mallAddressService.insertByBo(bo));
}
/**
* 修改收货地址
*/
@Operation(summary = "修改收货地址", description = "更新收货地址信息")
@SaCheckPermission("mall:address:edit")
@Log(title = "收货地址", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Parameter(description = "收货地址信息") @Validated @RequestBody MallAddressBo bo) {
return toAjax(mallAddressService.updateByBo(bo));
}
/**
* 删除收货地址
*/
@Operation(summary = "删除收货地址", description = "删除收货地址")
@SaCheckPermission("mall:address:remove")
@Log(title = "收货地址", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@Parameter(description = "地址ID数组", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(mallAddressService.deleteWithValidByIds(Arrays.asList(ids), true));
}
}
@@ -0,0 +1,132 @@
package org.dromara.mall.trade.controller.admin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.mall.trade.domain.bo.MallAfterSaleBo;
import org.dromara.mall.trade.domain.vo.MallAfterSaleVo;
import org.dromara.mall.trade.service.MallAfterSaleService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
* 售后申请管理
*
* @author pengles
*/
@Tag(name = "管理端-售后申请管理", description = "售后申请管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/mall/order/after-sale")
public class AdminMallAfterSaleController extends BaseController {
private final MallAfterSaleService mallAfterSaleService;
/**
* 查询售后申请列表
*/
@Operation(summary = "查询售后申请列表", description = "分页查询售后申请列表")
@SaCheckPermission("mall:order:after-sale:list")
@GetMapping("/list")
public TableDataInfo<MallAfterSaleVo> list(@Parameter(description = "售后申请查询条件") MallAfterSaleBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return mallAfterSaleService.queryPageList(bo, pageQuery);
}
/**
* 导出售后申请列表
*/
@Operation(summary = "导出售后申请列表", description = "导出售后申请到Excel")
@SaCheckPermission("mall:order:after-sale:export")
@Log(title = "售后申请", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "售后申请查询条件") MallAfterSaleBo bo,
@Parameter(description = "响应对象") HttpServletResponse response) {
List<MallAfterSaleVo> list = mallAfterSaleService.queryList(bo);
ExcelUtil.exportExcel(list, "售后申请", MallAfterSaleVo.class, response);
}
/**
* 获取售后申请详细信息
*/
@Operation(summary = "获取售后申请详情", description = "根据售后申请ID获取详细信息")
@SaCheckPermission("mall:order:after-sale:query")
@GetMapping("/{id}")
public R<MallAfterSaleVo> getInfo(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallAfterSaleService.queryById(id));
}
/**
* 删除售后申请
*/
@Operation(summary = "删除售后申请", description = "删除售后申请")
@SaCheckPermission("mall:order:after-sale:remove")
@Log(title = "售后申请", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@Parameter(description = "售后申请ID数组", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(mallAfterSaleService.deleteWithValidByIds(Arrays.asList(ids), true));
}
/**
* 审核售后申请(同意/拒绝)
*/
@Operation(summary = "审核售后申请", description = "审核售后申请,1-同意,2-拒绝")
@SaCheckPermission("mall:order:after-sale:audit")
@Log(title = "售后申请", businessType = BusinessType.UPDATE)
@PutMapping("/audit/{id}")
public R<Void> audit(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id,
@Parameter(description = "审核状态", required = true)
@NotNull(message = "审核状态不能为空")
@RequestParam Integer status,
@Parameter(description = "拒绝原因")
@RequestParam(required = false) String refuseReason) {
return toAjax(mallAfterSaleService.audit(id, status, refuseReason));
}
/**
* 确认收货(商家确认收到退货)
*/
@Operation(summary = "确认收货", description = "商家确认收到退货商品")
@SaCheckPermission("mall:order:after-sale:receive")
@Log(title = "售后申请", businessType = BusinessType.UPDATE)
@PutMapping("/receive/{id}")
public R<Void> confirmReceive(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return toAjax(mallAfterSaleService.confirmReceive(id));
}
/**
* 完成售后(退款完成或换货完成)
*/
@Operation(summary = "完成售后", description = "完成售后申请(退款完成或换货完成)")
@SaCheckPermission("mall:order:after-sale:complete")
@Log(title = "售后申请", businessType = BusinessType.UPDATE)
@PutMapping("/complete/{id}")
public R<Void> complete(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return toAjax(mallAfterSaleService.complete(id));
}
}
@@ -0,0 +1,120 @@
package org.dromara.mall.trade.controller.admin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.mall.trade.domain.bo.MallOrderEvaluateBo;
import org.dromara.mall.trade.domain.vo.MallOrderEvaluateVo;
import org.dromara.mall.trade.service.MallOrderEvaluateService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
* 订单评价管理
*
* @author pengles
*/
@Tag(name = "管理端-订单评价管理", description = "订单评价管理接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/mall/order/evaluate")
public class AdminMallOrderEvaluateController extends BaseController {
private final MallOrderEvaluateService mallOrderEvaluateService;
/**
* 查询订单评价列表
*/
@Operation(summary = "查询订单评价列表", description = "分页查询订单评价列表")
@SaCheckPermission("mall:order:evaluate:list")
@GetMapping("/list")
public TableDataInfo<MallOrderEvaluateVo> list(@Parameter(description = "评价查询条件") MallOrderEvaluateBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
return mallOrderEvaluateService.queryPageList(bo, pageQuery);
}
/**
* 导出订单评价列表
*/
@Operation(summary = "导出订单评价列表", description = "导出订单评价到Excel")
@SaCheckPermission("mall:order:evaluate:export")
@Log(title = "订单评价", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(@Parameter(description = "评价查询条件") MallOrderEvaluateBo bo,
@Parameter(description = "响应对象") HttpServletResponse response) {
List<MallOrderEvaluateVo> list = mallOrderEvaluateService.queryList(bo);
ExcelUtil.exportExcel(list, "订单评价", MallOrderEvaluateVo.class, response);
}
/**
* 获取订单评价详细信息
*/
@Operation(summary = "获取订单评价详情", description = "根据评价ID获取详细信息")
@SaCheckPermission("mall:order:evaluate:query")
@GetMapping("/{id}")
public R<MallOrderEvaluateVo> getInfo(@Parameter(description = "评价ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallOrderEvaluateService.queryById(id));
}
/**
* 删除订单评价
*/
@Operation(summary = "删除订单评价", description = "删除订单评价")
@SaCheckPermission("mall:order:evaluate:remove")
@Log(title = "订单评价", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@Parameter(description = "评价ID数组", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(mallOrderEvaluateService.deleteWithValidByIds(Arrays.asList(ids), true));
}
/**
* 审核评价(通过/驳回)
*/
@Operation(summary = "审核评价", description = "审核评价,1-通过,2-驳回")
@SaCheckPermission("mall:order:evaluate:audit")
@Log(title = "订单评价", businessType = BusinessType.UPDATE)
@PutMapping("/audit/{id}")
public R<Void> audit(@Parameter(description = "评价ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id,
@Parameter(description = "审核状态", required = true)
@NotNull(message = "审核状态不能为空")
@RequestParam Integer status) {
return toAjax(mallOrderEvaluateService.audit(id, status));
}
/**
* 回复评价
*/
@Operation(summary = "回复评价", description = "商家回复评价")
@SaCheckPermission("mall:order:evaluate:reply")
@Log(title = "订单评价", businessType = BusinessType.UPDATE)
@PutMapping("/reply/{id}")
public R<Void> reply(@Parameter(description = "评价ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id,
@Parameter(description = "回复内容", required = true)
@NotNull(message = "回复内容不能为空")
@RequestParam String replyContent) {
return toAjax(mallOrderEvaluateService.reply(id, replyContent));
}
}
@@ -0,0 +1,106 @@
package org.dromara.mall.trade.controller.app;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.web.core.BaseController;
import org.dromara.mall.trade.domain.bo.MallAddressBo;
import org.dromara.mall.trade.domain.vo.MallAddressVo;
import org.dromara.mall.trade.service.MallAddressService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
* 收货地址(移动端)
*
* @author pengles
*/
@Tag(name = "移动端-收货地址", description = "收货地址接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/mall/address")
public class AppMallAddressController extends BaseController {
private final MallAddressService mallAddressService;
/**
* 查询收货地址列表
*/
@Operation(summary = "查询收货地址列表", description = "查询用户的收货地址列表")
@GetMapping("/list")
public R<List<MallAddressVo>> list() {
Long userId = org.dromara.common.satoken.utils.LoginHelper.getUserId();
List<MallAddressVo> list = mallAddressService.queryListByUserId(userId);
return R.ok(list);
}
/**
* 获取默认收货地址
*/
@Operation(summary = "获取默认收货地址", description = "查询用户的默认收货地址")
@GetMapping("/default")
public R<MallAddressVo> getDefault() {
Long userId = org.dromara.common.satoken.utils.LoginHelper.getUserId();
MallAddressVo address = mallAddressService.queryDefaultByUserId(userId);
return R.ok(address);
}
/**
* 获取收货地址详细信息
*/
@Operation(summary = "获取收货地址详情", description = "根据地址ID获取详细信息")
@GetMapping("/{id}")
public R<MallAddressVo> getInfo(@Parameter(description = "地址ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallAddressService.queryById(id));
}
/**
* 新增收货地址
*/
@Operation(summary = "新增收货地址", description = "创建新的收货地址")
@PostMapping
public R<Void> add(@Parameter(description = "收货地址信息") @Validated @RequestBody MallAddressBo bo) {
return toAjax(mallAddressService.insertByBo(bo));
}
/**
* 修改收货地址
*/
@Operation(summary = "修改收货地址", description = "更新收货地址信息")
@PutMapping
public R<Void> edit(@Parameter(description = "收货地址信息") @Validated @RequestBody MallAddressBo bo) {
return toAjax(mallAddressService.updateByBo(bo));
}
/**
* 删除收货地址
*/
@Operation(summary = "删除收货地址", description = "删除收货地址")
@DeleteMapping("/{ids}")
public R<Void> remove(@Parameter(description = "地址ID数组", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(mallAddressService.deleteWithValidByIds(Arrays.asList(ids), true));
}
/**
* 设置默认地址
*/
@Operation(summary = "设置默认地址", description = "将指定地址设置为默认收货地址")
@PutMapping("/default/{id}")
public R<Void> setDefault(@Parameter(description = "地址ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return toAjax(mallAddressService.setDefault(id));
}
}
@@ -0,0 +1,106 @@
package org.dromara.mall.trade.controller.app;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.mall.trade.domain.bo.MallAfterSaleBo;
import org.dromara.mall.trade.domain.vo.MallAfterSaleVo;
import org.dromara.mall.trade.service.MallAfterSaleService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 售后申请(移动端)
*
* @author pengles
*/
@Tag(name = "移动端-售后申请", description = "售后申请接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/mall/order/after-sale")
public class AppMallAfterSaleController {
private final MallAfterSaleService mallAfterSaleService;
/**
* 查询售后申请列表(移动端)
*/
@Operation(summary = "查询售后申请列表", description = "查询用户的售后申请列表")
@GetMapping("/list")
public R<TableDataInfo<MallAfterSaleVo>> list(@Parameter(description = "售后申请查询条件") MallAfterSaleBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
TableDataInfo<MallAfterSaleVo> result = mallAfterSaleService.queryPageList(bo, pageQuery);
return R.ok(result);
}
/**
* 根据订单ID查询售后申请列表
*/
@Operation(summary = "查询订单售后申请列表", description = "根据订单ID查询售后申请列表")
@GetMapping("/order/{orderId}")
public R<List<MallAfterSaleVo>> listByOrderId(@Parameter(description = "订单ID", required = true)
@NotNull(message = "订单ID不能为空")
@PathVariable Long orderId) {
MallAfterSaleBo bo = new MallAfterSaleBo();
bo.setOrderId(orderId);
List<MallAfterSaleVo> list = mallAfterSaleService.queryList(bo);
return R.ok(list);
}
/**
* 获取售后申请详细信息
*/
@Operation(summary = "获取售后申请详情", description = "根据售后申请ID获取详细信息")
@GetMapping("/{id}")
public R<MallAfterSaleVo> getInfo(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallAfterSaleService.queryById(id));
}
/**
* 新增售后申请
*/
@Operation(summary = "新增售后申请", description = "提交售后申请(退款/退货/换货)")
@PostMapping
public R<Void> add(@Parameter(description = "售后申请信息") @Validated @RequestBody MallAfterSaleBo bo) {
return R.ok(mallAfterSaleService.insertByBo(bo));
}
/**
* 用户填写退货物流信息
*/
@Operation(summary = "填写退货物流信息", description = "用户填写退货物流信息(仅退货和换货类型)")
@PutMapping("/logistics/{id}")
public R<Void> fillLogistics(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id,
@Parameter(description = "物流公司", required = true)
@NotBlank(message = "物流公司不能为空")
@RequestParam String logisticsCompany,
@Parameter(description = "物流单号", required = true)
@NotBlank(message = "物流单号不能为空")
@RequestParam String logisticsNo) {
return R.ok(mallAfterSaleService.fillLogistics(id, logisticsCompany, logisticsNo));
}
/**
* 取消售后申请
*/
@Operation(summary = "取消售后申请", description = "用户取消售后申请")
@PutMapping("/cancel/{id}")
public R<Void> cancel(@Parameter(description = "售后申请ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallAfterSaleService.cancel(id));
}
}
@@ -0,0 +1,93 @@
package org.dromara.mall.trade.controller.app;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.mall.trade.domain.bo.MallOrderEvaluateBo;
import org.dromara.mall.trade.domain.vo.MallOrderEvaluateVo;
import org.dromara.mall.trade.service.MallOrderEvaluateService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 订单评价(移动端)
*
* @author pengles
*/
@Tag(name = "移动端-订单评价", description = "订单评价接口")
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/app/mall/order/evaluate")
public class AppMallOrderEvaluateController {
private final MallOrderEvaluateService mallOrderEvaluateService;
/**
* 查询订单评价列表(移动端)
*/
@Operation(summary = "查询订单评价列表", description = "查询订单评价列表")
@GetMapping("/list")
public R<TableDataInfo<MallOrderEvaluateVo>> list(@Parameter(description = "评价查询条件") MallOrderEvaluateBo bo,
@Parameter(description = "分页参数") PageQuery pageQuery) {
TableDataInfo<MallOrderEvaluateVo> result = mallOrderEvaluateService.queryPageList(bo, pageQuery);
return R.ok(result);
}
/**
* 根据订单ID查询评价列表
*/
@Operation(summary = "查询订单评价列表", description = "根据订单ID查询评价列表")
@GetMapping("/order/{orderId}")
public R<List<MallOrderEvaluateVo>> listByOrderId(@Parameter(description = "订单ID", required = true)
@NotNull(message = "订单ID不能为空")
@PathVariable Long orderId) {
MallOrderEvaluateBo bo = new MallOrderEvaluateBo();
bo.setOrderId(orderId);
List<MallOrderEvaluateVo> list = mallOrderEvaluateService.queryList(bo);
return R.ok(list);
}
/**
* 根据商品ID查询评价列表
*/
@Operation(summary = "查询商品评价列表", description = "根据商品ID查询评价列表(仅已通过审核的)")
@GetMapping("/product/{productId}")
public R<TableDataInfo<MallOrderEvaluateVo>> listByProductId(@Parameter(description = "商品ID", required = true)
@NotNull(message = "商品ID不能为空")
@PathVariable Long productId,
@Parameter(description = "分页参数") PageQuery pageQuery) {
MallOrderEvaluateBo bo = new MallOrderEvaluateBo();
bo.setProductId(productId);
bo.setStatus(1); // 只查询已通过审核的评价
TableDataInfo<MallOrderEvaluateVo> result = mallOrderEvaluateService.queryPageList(bo, pageQuery);
return R.ok(result);
}
/**
* 获取订单评价详细信息
*/
@Operation(summary = "获取订单评价详情", description = "根据评价ID获取详细信息")
@GetMapping("/{id}")
public R<MallOrderEvaluateVo> getInfo(@Parameter(description = "评价ID", required = true)
@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(mallOrderEvaluateService.queryById(id));
}
/**
* 新增订单评价
*/
@Operation(summary = "新增订单评价", description = "提交订单评价")
@PostMapping
public R<Void> add(@Parameter(description = "评价信息") @Validated @RequestBody MallOrderEvaluateBo bo) {
return R.ok(mallOrderEvaluateService.insertByBo(bo));
}
}
@@ -0,0 +1,81 @@
package org.dromara.mall.trade.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 收货地址对象 mall_address
*
* @author pengles
*/
@Data
@EqualsAndHashCode
@TableName("mall_address")
public class MallAddress implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 地址ID
*/
@TableId
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 收货人姓名
*/
private String receiverName;
/**
* 收货人电话
*/
private String receiverPhone;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 区/县
*/
private String district;
/**
* 详细地址
*/
private String address;
/**
* 是否默认地址(0否 1是)
*/
private Integer isDefault;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,150 @@
package org.dromara.mall.trade.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 售后申请对象 mall_after_sale
*
* @author pengles
*/
@Data
@EqualsAndHashCode
@TableName("mall_after_sale")
public class MallAfterSale implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 售后ID
*/
@TableId
private Long id;
/**
* 售后单号
*/
private String afterSaleNo;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 订单明细ID
*/
private Long orderItemId;
/**
* 用户ID
*/
private Long userId;
/**
* 售后类型(0退款 1退货 2换货)
*/
private Integer type;
/**
* 申请原因
*/
private String reason;
/**
* 问题描述
*/
private String description;
/**
* 凭证图片(JSON数组)
*/
private String images;
/**
* 退款金额(分)
*/
private Integer refundAmount;
/**
* 数量
*/
private Integer quantity;
/**
* 状态(0待审核 1已同意 2已拒绝 3待退货 4待收货 5已完成 6已取消)
*/
private Integer status;
/**
* 拒绝原因
*/
private String refuseReason;
/**
* 退款单号
*/
private String refundOrderNo;
/**
* 物流公司
*/
private String logisticsCompany;
/**
* 物流单号
*/
private String logisticsNo;
/**
* 申请时间
*/
private Date applyTime;
/**
* 审核时间
*/
private Date auditTime;
/**
* 退货时间
*/
private Date returnTime;
/**
* 收货时间
*/
private Date receiveTime;
/**
* 退款时间
*/
private Date refundTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
@@ -0,0 +1,61 @@
package org.dromara.mall.trade.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 订单优惠券使用记录对象 mall_order_coupon
*
* @author pengles
*/
@Data
@EqualsAndHashCode
@TableName("mall_order_coupon")
public class MallOrderCoupon implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@TableId
private Long id;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 优惠券ID
*/
private Long couponId;
/**
* 用户优惠券ID
*/
private Long couponUserId;
/**
* 优惠金额(分)
*/
private Integer discountAmount;
/**
* 使用时间
*/
private Date createTime;
}
@@ -0,0 +1,105 @@
package org.dromara.mall.trade.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 订单评价对象 mall_order_evaluate
*
* @author pengles
*/
@Data
@EqualsAndHashCode
@TableName("mall_order_evaluate")
public class MallOrderEvaluate implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 评价ID
*/
@TableId
private Long id;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 订单明细ID
*/
private Long orderItemId;
/**
* 商品ID
*/
private Long productId;
/**
* SKU ID
*/
private Long skuId;
/**
* 用户ID
*/
private Long userId;
/**
* 评分(1-5)
*/
private Integer score;
/**
* 评价内容
*/
private String content;
/**
* 评价图片(JSON数组)
*/
private String images;
/**
* 回复内容
*/
private String replyContent;
/**
* 回复时间
*/
private Date replyTime;
/**
* 状态(0待审核 1已通过 2已驳回)
*/
private Integer status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
@@ -0,0 +1,76 @@
package org.dromara.mall.trade.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.mall.trade.domain.MallAddress;
/**
* 收货地址业务对象 mall_address
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = MallAddress.class, reverseConvertGenerate = false)
public class MallAddressBo extends BaseEntity {
/**
* 地址ID
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 收货人姓名
*/
@NotBlank(message = "收货人姓名不能为空")
@Size(max = 64, message = "收货人姓名长度不能超过64个字符")
private String receiverName;
/**
* 收货人电话
*/
@NotBlank(message = "收货人电话不能为空")
@Size(max = 32, message = "收货人电话长度不能超过32个字符")
private String receiverPhone;
/**
* 省份
*/
@Size(max = 64, message = "省份长度不能超过64个字符")
private String province;
/**
* 城市
*/
@Size(max = 64, message = "城市长度不能超过64个字符")
private String city;
/**
* 区/县
*/
@Size(max = 64, message = "区/县长度不能超过64个字符")
private String district;
/**
* 详细地址
*/
@NotBlank(message = "详细地址不能为空")
@Size(max = 512, message = "详细地址长度不能超过512个字符")
private String address;
/**
* 是否默认地址(0否 1是)
*/
private Integer isDefault;
}
@@ -0,0 +1,110 @@
package org.dromara.mall.trade.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.mall.trade.domain.MallAfterSale;
/**
* 售后申请业务对象 mall_after_sale
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = MallAfterSale.class, reverseConvertGenerate = false)
public class MallAfterSaleBo extends BaseEntity {
/**
* 售后ID
*/
private Long id;
/**
* 售后单号
*/
private String afterSaleNo;
/**
* 订单ID
*/
@NotNull(message = "订单ID不能为空")
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 订单明细ID
*/
@NotNull(message = "订单明细ID不能为空")
private Long orderItemId;
/**
* 用户ID
*/
private Long userId;
/**
* 售后类型(0退款 1退货 2换货)
*/
@NotNull(message = "售后类型不能为空")
private Integer type;
/**
* 申请原因
*/
@NotBlank(message = "申请原因不能为空")
private String reason;
/**
* 问题描述
*/
private String description;
/**
* 凭证图片(JSON数组)
*/
private String images;
/**
* 退款金额(分)
*/
private Integer refundAmount;
/**
* 数量
*/
private Integer quantity;
/**
* 状态(0待审核 1已同意 2已拒绝 3待退货 4待收货 5已完成 6已取消)
*/
private Integer status;
/**
* 拒绝原因
*/
private String refuseReason;
/**
* 退款单号
*/
private String refundOrderNo;
/**
* 物流公司
*/
private String logisticsCompany;
/**
* 物流单号
*/
private String logisticsNo;
}
@@ -0,0 +1,56 @@
package org.dromara.mall.trade.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.mall.trade.domain.MallOrderCoupon;
/**
* 订单优惠券使用记录业务对象 mall_order_coupon
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = MallOrderCoupon.class, reverseConvertGenerate = false)
public class MallOrderCouponBo extends BaseEntity {
/**
* ID
*/
private Long id;
/**
* 订单ID
*/
@NotNull(message = "订单ID不能为空")
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 优惠券ID
*/
@NotNull(message = "优惠券ID不能为空")
private Long couponId;
/**
* 用户优惠券ID
*/
@NotNull(message = "用户优惠券ID不能为空")
private Long couponUserId;
/**
* 优惠金额(分)
*/
@NotNull(message = "优惠金额不能为空")
@Min(value = 0, message = "优惠金额不能为负数")
private Integer discountAmount;
}
@@ -0,0 +1,87 @@
package org.dromara.mall.trade.domain.bo;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.mall.trade.domain.MallOrderEvaluate;
/**
* 订单评价业务对象 mall_order_evaluate
*
* @author pengles
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = MallOrderEvaluate.class, reverseConvertGenerate = false)
public class MallOrderEvaluateBo extends BaseEntity {
/**
* 评价ID
*/
private Long id;
/**
* 订单ID
*/
@NotNull(message = "订单ID不能为空")
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 订单明细ID
*/
@NotNull(message = "订单明细ID不能为空")
private Long orderItemId;
/**
* 商品ID
*/
private Long productId;
/**
* SKU ID
*/
private Long skuId;
/**
* 用户ID
*/
private Long userId;
/**
* 评分(1-5)
*/
@NotNull(message = "评分不能为空")
@Min(value = 1, message = "评分不能小于1")
@Max(value = 5, message = "评分不能大于5")
private Integer score;
/**
* 评价内容
*/
private String content;
/**
* 评价图片(JSON数组)
*/
private String images;
/**
* 回复内容
*/
private String replyContent;
/**
* 状态(0待审核 1已通过 2已驳回)
*/
private Integer status;
}
@@ -0,0 +1,98 @@
package org.dromara.mall.trade.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.mall.trade.domain.MallAddress;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 收货地址视图对象 mall_address
*
* @author pengles
*/
@Data
@AutoMapper(target = MallAddress.class)
public class MallAddressVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 地址ID
*/
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 收货人姓名
*/
private String receiverName;
/**
* 收货人电话
*/
private String receiverPhone;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 区/县
*/
private String district;
/**
* 详细地址
*/
private String address;
/**
* 是否默认地址(0否 1是)
*/
private Integer isDefault;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 完整地址(省份+城市+区/县+详细地址)
*/
public String getFullAddress() {
StringBuilder fullAddress = new StringBuilder();
if (province != null && !province.isEmpty()) {
fullAddress.append(province);
}
if (city != null && !city.isEmpty()) {
fullAddress.append(city);
}
if (district != null && !district.isEmpty()) {
fullAddress.append(district);
}
if (address != null && !address.isEmpty()) {
fullAddress.append(address);
}
return fullAddress.toString();
}
}
@@ -0,0 +1,143 @@
package org.dromara.mall.trade.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.mall.trade.domain.MallAfterSale;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 售后申请视图对象 mall_after_sale
*
* @author pengles
*/
@Data
@AutoMapper(target = MallAfterSale.class)
public class MallAfterSaleVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 售后ID
*/
private Long id;
/**
* 售后单号
*/
private String afterSaleNo;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 订单明细ID
*/
private Long orderItemId;
/**
* 用户ID
*/
private Long userId;
/**
* 售后类型(0退款 1退货 2换货)
*/
private Integer type;
/**
* 申请原因
*/
private String reason;
/**
* 问题描述
*/
private String description;
/**
* 凭证图片(JSON数组)
*/
private String images;
/**
* 退款金额(分)
*/
private Integer refundAmount;
/**
* 数量
*/
private Integer quantity;
/**
* 状态(0待审核 1已同意 2已拒绝 3待退货 4待收货 5已完成 6已取消)
*/
private Integer status;
/**
* 拒绝原因
*/
private String refuseReason;
/**
* 退款单号
*/
private String refundOrderNo;
/**
* 物流公司
*/
private String logisticsCompany;
/**
* 物流单号
*/
private String logisticsNo;
/**
* 申请时间
*/
private Date applyTime;
/**
* 审核时间
*/
private Date auditTime;
/**
* 退货时间
*/
private Date returnTime;
/**
* 收货时间
*/
private Date receiveTime;
/**
* 退款时间
*/
private Date refundTime;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,58 @@
package org.dromara.mall.trade.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.mall.trade.domain.MallOrderCoupon;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 订单优惠券使用记录视图对象 mall_order_coupon
*
* @author pengles
*/
@Data
@AutoMapper(target = MallOrderCoupon.class)
public class MallOrderCouponVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long id;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 优惠券ID
*/
private Long couponId;
/**
* 用户优惠券ID
*/
private Long couponUserId;
/**
* 优惠金额(分)
*/
private Integer discountAmount;
/**
* 使用时间
*/
private Date createTime;
}
@@ -0,0 +1,98 @@
package org.dromara.mall.trade.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.mall.trade.domain.MallOrderEvaluate;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 订单评价视图对象 mall_order_evaluate
*
* @author pengles
*/
@Data
@AutoMapper(target = MallOrderEvaluate.class)
public class MallOrderEvaluateVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 评价ID
*/
private Long id;
/**
* 订单ID
*/
private Long orderId;
/**
* 订单号
*/
private String orderNo;
/**
* 订单明细ID
*/
private Long orderItemId;
/**
* 商品ID
*/
private Long productId;
/**
* SKU ID
*/
private Long skuId;
/**
* 用户ID
*/
private Long userId;
/**
* 评分(1-5)
*/
private Integer score;
/**
* 评价内容
*/
private String content;
/**
* 评价图片(JSON数组)
*/
private String images;
/**
* 回复内容
*/
private String replyContent;
/**
* 回复时间
*/
private Date replyTime;
/**
* 状态(0待审核 1已通过 2已驳回)
*/
private Integer status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,16 @@
package org.dromara.mall.trade.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.mall.trade.domain.MallAddress;
import org.dromara.mall.trade.domain.vo.MallAddressVo;
/**
* 收货地址Mapper接口
*
* @author pengles
*/
@Mapper
public interface MallAddressMapper extends BaseMapperPlus<MallAddress, MallAddressVo> {
}
@@ -0,0 +1,16 @@
package org.dromara.mall.trade.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.mall.trade.domain.MallAfterSale;
import org.dromara.mall.trade.domain.vo.MallAfterSaleVo;
/**
* 售后申请Mapper接口
*
* @author pengles
*/
@Mapper
public interface MallAfterSaleMapper extends BaseMapperPlus<MallAfterSale, MallAfterSaleVo> {
}
@@ -0,0 +1,16 @@
package org.dromara.mall.trade.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.mall.trade.domain.MallOrderCoupon;
import org.dromara.mall.trade.domain.vo.MallOrderCouponVo;
/**
* 订单优惠券使用记录Mapper接口
*
* @author pengles
*/
@Mapper
public interface MallOrderCouponMapper extends BaseMapperPlus<MallOrderCoupon, MallOrderCouponVo> {
}
@@ -0,0 +1,16 @@
package org.dromara.mall.trade.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.mall.trade.domain.MallOrderEvaluate;
import org.dromara.mall.trade.domain.vo.MallOrderEvaluateVo;
/**
* 订单评价Mapper接口
*
* @author pengles
*/
@Mapper
public interface MallOrderEvaluateMapper extends BaseMapperPlus<MallOrderEvaluate, MallOrderEvaluateVo> {
}
@@ -0,0 +1,63 @@
package org.dromara.mall.trade.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.mall.trade.domain.bo.MallAddressBo;
import org.dromara.mall.trade.domain.vo.MallAddressVo;
import java.util.Collection;
import java.util.List;
/**
* 收货地址Service接口
*
* @author pengles
*/
public interface MallAddressService {
/**
* 查询收货地址
*/
MallAddressVo queryById(Long id);
/**
* 查询收货地址列表
*/
TableDataInfo<MallAddressVo> queryPageList(MallAddressBo bo, PageQuery pageQuery);
/**
* 查询收货地址列表
*/
List<MallAddressVo> queryList(MallAddressBo bo);
/**
* 查询用户的收货地址列表
*/
List<MallAddressVo> queryListByUserId(Long userId);
/**
* 查询用户的默认地址
*/
MallAddressVo queryDefaultByUserId(Long userId);
/**
* 新增收货地址
*/
Boolean insertByBo(MallAddressBo bo);
/**
* 修改收货地址
*/
Boolean updateByBo(MallAddressBo bo);
/**
* 校验并批量删除收货地址信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 设置默认地址
*/
Boolean setDefault(Long id);
}
@@ -0,0 +1,78 @@
package org.dromara.mall.trade.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.mall.trade.domain.bo.MallAfterSaleBo;
import org.dromara.mall.trade.domain.vo.MallAfterSaleVo;
import java.util.Collection;
import java.util.List;
/**
* 售后申请Service接口
*
* @author pengles
*/
public interface MallAfterSaleService {
/**
* 查询售后申请
*/
MallAfterSaleVo queryById(Long id);
/**
* 根据售后单号查询
*/
MallAfterSaleVo queryByAfterSaleNo(String afterSaleNo);
/**
* 查询售后申请列表
*/
TableDataInfo<MallAfterSaleVo> queryPageList(MallAfterSaleBo bo, PageQuery pageQuery);
/**
* 查询售后申请列表
*/
List<MallAfterSaleVo> queryList(MallAfterSaleBo bo);
/**
* 新增售后申请
*/
Boolean insertByBo(MallAfterSaleBo bo);
/**
* 修改售后申请
*/
Boolean updateByBo(MallAfterSaleBo bo);
/**
* 校验并批量删除售后申请信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 审核售后申请(同意/拒绝)
*/
Boolean audit(Long id, Integer status, String refuseReason);
/**
* 用户填写退货物流信息
*/
Boolean fillLogistics(Long id, String logisticsCompany, String logisticsNo);
/**
* 确认收货(商家确认收到退货)
*/
Boolean confirmReceive(Long id);
/**
* 完成售后(退款完成或换货完成)
*/
Boolean complete(Long id);
/**
* 取消售后申请
*/
Boolean cancel(Long id);
}
@@ -0,0 +1,77 @@
package org.dromara.mall.trade.service;
import org.dromara.mall.trade.domain.bo.MallOrderCouponBo;
import org.dromara.mall.trade.domain.vo.MallOrderCouponVo;
import java.util.Collection;
import java.util.List;
/**
* 订单优惠券使用记录Service接口
*
* @author pengles
*/
public interface MallOrderCouponService {
/**
* 查询订单优惠券使用记录
*/
MallOrderCouponVo queryById(Long id);
/**
* 根据订单ID查询订单优惠券使用记录列表
*/
List<MallOrderCouponVo> queryListByOrderId(Long orderId);
/**
* 根据订单号查询订单优惠券使用记录列表
*/
List<MallOrderCouponVo> queryListByOrderNo(String orderNo);
/**
* 新增订单优惠券使用记录
*/
Boolean insertByBo(MallOrderCouponBo bo);
/**
* 校验并批量删除订单优惠券使用记录信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 计算并使用优惠券(订单创建时调用)
* 计算优惠券折扣金额,创建订单优惠券使用记录,并标记用户优惠券为已使用
*
* @param couponUserId 用户优惠券ID
* @param orderId 订单ID
* @param orderNo 订单号
* @param orderAmount 订单金额(单位:分)
* @return 优惠金额(单位:分)
*/
Long calculateAndUseCoupon(Long couponUserId, Long orderId, String orderNo, Long orderAmount);
/**
* 根据订单ID统计优惠金额
*
* @param orderId 订单ID
* @return 优惠金额总和(单位:分)
*/
Long sumDiscountAmountByOrderId(Long orderId);
/**
* 根据订单号统计优惠金额
*
* @param orderNo 订单号
* @return 优惠金额总和(单位:分)
*/
Long sumDiscountAmountByOrderNo(String orderNo);
/**
* 根据优惠券ID统计核销金额
*
* @param couponId 优惠券ID
* @return 核销金额总和(单位:分)
*/
Long sumDiscountAmountByCouponId(Long couponId);
}
@@ -0,0 +1,58 @@
package org.dromara.mall.trade.service;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.mall.trade.domain.bo.MallOrderEvaluateBo;
import org.dromara.mall.trade.domain.vo.MallOrderEvaluateVo;
import java.util.Collection;
import java.util.List;
/**
* 订单评价Service接口
*
* @author pengles
*/
public interface MallOrderEvaluateService {
/**
* 查询订单评价
*/
MallOrderEvaluateVo queryById(Long id);
/**
* 查询订单评价列表
*/
TableDataInfo<MallOrderEvaluateVo> queryPageList(MallOrderEvaluateBo bo, PageQuery pageQuery);
/**
* 查询订单评价列表
*/
List<MallOrderEvaluateVo> queryList(MallOrderEvaluateBo bo);
/**
* 新增订单评价
*/
Boolean insertByBo(MallOrderEvaluateBo bo);
/**
* 修改订单评价
*/
Boolean updateByBo(MallOrderEvaluateBo bo);
/**
* 校验并批量删除订单评价信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 审核评价(通过/驳回)
*/
Boolean audit(Long id, Integer status);
/**
* 回复评价(商家回复)
*/
Boolean reply(Long id, String replyContent);
}
@@ -0,0 +1,102 @@
package org.dromara.mall.trade.service;
import java.util.Map;
/**
* 秒杀订单处理Service接口
* 提供秒杀订单创建、验证、库存管理等核心业务逻辑
*
* @author pengles
*/
public interface SeckillOrderService {
/**
* 验证秒杀活动是否可用
*
* @param seckillId 秒杀活动ID
* @param quantity 购买数量
* @return 验证结果信息(包含是否可用、错误信息等)
*/
SeckillValidationResult validateSeckillActivity(Long seckillId, Integer quantity);
/**
* 验证秒杀限购
*
* @param seckillId 秒杀活动ID
* @param userId 用户ID
* @param skuId SKU ID
* @return 是否允许购买
*/
Boolean validatePurchaseLimit(Long seckillId, Long userId, Long skuId);
/**
* 预扣秒杀库存
*
* @param seckillId 秒杀活动ID
* @param quantity 购买数量
* @return 是否成功
*/
Boolean preDeductSeckillStock(Long seckillId, Integer quantity);
/**
* 恢复秒杀库存(订单取消时调用)
*
* @param seckillId 秒杀活动ID
* @param quantity 恢复数量
* @return 是否成功
*/
Boolean restoreSeckillStock(Long seckillId, Integer quantity);
/**
* 获取秒杀价格
*
* @param seckillId 秒杀活动ID
* @return 秒杀价(分)
*/
Integer getSeckillPrice(Long seckillId);
/**
* 秒杀验证结果
*/
class SeckillValidationResult {
private Boolean valid;
private String errorMessage;
private Map<String, Object> data;
public SeckillValidationResult(Boolean valid, String errorMessage) {
this.valid = valid;
this.errorMessage = errorMessage;
}
public SeckillValidationResult(Boolean valid, String errorMessage, Map<String, Object> data) {
this.valid = valid;
this.errorMessage = errorMessage;
this.data = data;
}
public Boolean getValid() {
return valid;
}
public void setValid(Boolean valid) {
this.valid = valid;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public Map<String, Object> getData() {
return data;
}
public void setData(Map<String, Object> data) {
this.data = data;
}
}
}
@@ -0,0 +1,207 @@
package org.dromara.mall.trade.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.mall.trade.domain.MallAddress;
import org.dromara.mall.trade.domain.bo.MallAddressBo;
import org.dromara.mall.trade.domain.vo.MallAddressVo;
import org.dromara.mall.trade.mapper.MallAddressMapper;
import org.dromara.mall.trade.service.MallAddressService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
/**
* 收货地址Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class MallAddressServiceImpl implements MallAddressService {
private final MallAddressMapper baseMapper;
/**
* 查询收货地址
*/
@Override
public MallAddressVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 查询收货地址列表
*/
@Override
public TableDataInfo<MallAddressVo> queryPageList(MallAddressBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<MallAddress> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoPage(pageQuery.build(), lqw);
}
/**
* 查询收货地址列表
*/
@Override
public List<MallAddressVo> queryList(MallAddressBo bo) {
LambdaQueryWrapper<MallAddress> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
/**
* 查询用户的收货地址列表
*/
@Override
public List<MallAddressVo> queryListByUserId(Long userId) {
LambdaQueryWrapper<MallAddress> lqw = new LambdaQueryWrapper<>();
lqw.eq(MallAddress::getUserId, userId);
lqw.orderByDesc(MallAddress::getIsDefault);
lqw.orderByDesc(MallAddress::getCreateTime);
return baseMapper.selectVoList(lqw);
}
/**
* 查询用户的默认地址
*/
@Override
public MallAddressVo queryDefaultByUserId(Long userId) {
LambdaQueryWrapper<MallAddress> lqw = new LambdaQueryWrapper<>();
lqw.eq(MallAddress::getUserId, userId);
lqw.eq(MallAddress::getIsDefault, 1);
lqw.last("LIMIT 1");
List<MallAddressVo> list = baseMapper.selectVoList(lqw);
return list.isEmpty() ? null : list.get(0);
}
private LambdaQueryWrapper<MallAddress> buildQueryWrapper(MallAddressBo bo) {
LambdaQueryWrapper<MallAddress> lqw = new LambdaQueryWrapper<>();
lqw.eq(bo.getUserId() != null, MallAddress::getUserId, bo.getUserId());
lqw.like(StringUtils.isNotBlank(bo.getReceiverName()), MallAddress::getReceiverName, bo.getReceiverName());
lqw.eq(StringUtils.isNotBlank(bo.getReceiverPhone()), MallAddress::getReceiverPhone, bo.getReceiverPhone());
lqw.eq(bo.getIsDefault() != null, MallAddress::getIsDefault, bo.getIsDefault());
lqw.orderByDesc(MallAddress::getIsDefault);
lqw.orderByDesc(MallAddress::getCreateTime);
return lqw;
}
/**
* 新增收货地址
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(MallAddressBo bo) {
MallAddress add = MapstructUtils.convert(bo, MallAddress.class);
validEntityBeforeSave(add);
// 设置用户ID(移动端用户)
if (add.getUserId() == null) {
add.setUserId(LoginHelper.getUserId());
}
// 如果设置为默认地址,则取消其他默认地址
if (add.getIsDefault() != null && add.getIsDefault() == 1) {
cancelOtherDefaultAddress(add.getUserId());
} else {
add.setIsDefault(0);
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改收货地址
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(MallAddressBo bo) {
MallAddress update = MapstructUtils.convert(bo, MallAddress.class);
validEntityBeforeSave(update);
// 如果设置为默认地址,则取消其他默认地址
if (update.getIsDefault() != null && update.getIsDefault() == 1) {
cancelOtherDefaultAddress(update.getUserId(), update.getId());
}
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(MallAddress entity) {
// 可以添加业务校验逻辑,如手机号格式校验等
}
/**
* 取消其他默认地址
*/
private void cancelOtherDefaultAddress(Long userId) {
cancelOtherDefaultAddress(userId, null);
}
/**
* 取消其他默认地址(排除指定地址ID)
*/
private void cancelOtherDefaultAddress(Long userId, Long excludeId) {
LambdaUpdateWrapper<MallAddress> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(MallAddress::getUserId, userId);
updateWrapper.eq(MallAddress::getIsDefault, 1);
if (excludeId != null) {
updateWrapper.ne(MallAddress::getId, excludeId);
}
updateWrapper.set(MallAddress::getIsDefault, 0);
baseMapper.update(null, updateWrapper);
}
/**
* 批量删除收货地址信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 可以添加业务校验逻辑
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 设置默认地址
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean setDefault(Long id) {
MallAddress address = baseMapper.selectById(id);
if (address == null) {
throw new ServiceException("收货地址不存在");
}
Long userId = address.getUserId();
if (userId == null) {
userId = LoginHelper.getUserId();
}
// 取消其他默认地址
cancelOtherDefaultAddress(userId, id);
// 设置当前地址为默认地址
address.setIsDefault(1);
return baseMapper.updateById(address) > 0;
}
}
@@ -0,0 +1,293 @@
package org.dromara.mall.trade.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.mall.trade.domain.MallAfterSale;
import org.dromara.mall.trade.domain.bo.MallAfterSaleBo;
import org.dromara.mall.trade.domain.vo.MallAfterSaleVo;
import org.dromara.mall.trade.mapper.MallAfterSaleMapper;
import org.dromara.mall.trade.service.MallAfterSaleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 售后申请Service业务层处理
*
* @author pengles
*/
@RequiredArgsConstructor
@Service
public class MallAfterSaleServiceImpl implements MallAfterSaleService {
private final MallAfterSaleMapper baseMapper;
/**
* 查询售后申请
*/
@Override
public MallAfterSaleVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 根据售后单号查询
*/
@Override
public MallAfterSaleVo queryByAfterSaleNo(String afterSaleNo) {
LambdaQueryWrapper<MallAfterSale> lqw = new LambdaQueryWrapper<>();
lqw.eq(MallAfterSale::getAfterSaleNo, afterSaleNo);
return baseMapper.selectVoOne(lqw);
}
/**
* 查询售后申请列表
*/
@Override
public TableDataInfo<MallAfterSaleVo> queryPageList(MallAfterSaleBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<MallAfterSale> lqw = buildQueryWrapper(bo);
Page<MallAfterSaleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询售后申请列表
*/
@Override
public List<MallAfterSaleVo> queryList(MallAfterSaleBo bo) {
LambdaQueryWrapper<MallAfterSale> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<MallAfterSale> buildQueryWrapper(MallAfterSaleBo bo) {
LambdaQueryWrapper<MallAfterSale> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(bo.getAfterSaleNo()), MallAfterSale::getAfterSaleNo, bo.getAfterSaleNo());
lqw.eq(bo.getOrderId() != null, MallAfterSale::getOrderId, bo.getOrderId());
lqw.eq(StringUtils.isNotBlank(bo.getOrderNo()), MallAfterSale::getOrderNo, bo.getOrderNo());
lqw.eq(bo.getOrderItemId() != null, MallAfterSale::getOrderItemId, bo.getOrderItemId());
lqw.eq(bo.getUserId() != null, MallAfterSale::getUserId, bo.getUserId());
lqw.eq(bo.getType() != null, MallAfterSale::getType, bo.getType());
lqw.eq(bo.getStatus() != null, MallAfterSale::getStatus, bo.getStatus());
lqw.orderByDesc(MallAfterSale::getCreateTime);
return lqw;
}
/**
* 生成售后单号
*/
private String generateAfterSaleNo() {
return "AS" + System.currentTimeMillis() + IdUtil.fastSimpleUUID().substring(0, 6).toUpperCase();
}
/**
* 新增售后申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(MallAfterSaleBo bo) {
MallAfterSale add = MapstructUtils.convert(bo, MallAfterSale.class);
validEntityBeforeSave(add);
// 设置用户ID(移动端用户)
if (add.getUserId() == null) {
add.setUserId(LoginHelper.getUserId());
}
// 生成售后单号
if (StringUtils.isBlank(add.getAfterSaleNo())) {
add.setAfterSaleNo(generateAfterSaleNo());
}
// 设置默认状态为待审核
if (add.getStatus() == null) {
add.setStatus(0);
}
// 设置申请时间
if (add.getApplyTime() == null) {
add.setApplyTime(new Date());
}
// 设置默认数量
if (add.getQuantity() == null) {
add.setQuantity(1);
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改售后申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(MallAfterSaleBo bo) {
MallAfterSale update = MapstructUtils.convert(bo, MallAfterSale.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(MallAfterSale entity) {
// 可以添加业务校验逻辑
}
/**
* 批量删除售后申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 可以添加业务校验逻辑
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 审核售后申请(同意/拒绝)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean audit(Long id, Integer status, String refuseReason) {
MallAfterSale afterSale = baseMapper.selectById(id);
if (afterSale == null) {
throw new ServiceException("售后申请不存在");
}
if (afterSale.getStatus() != 0) {
throw new ServiceException("售后申请已审核,不能重复审核");
}
if (status == null || (status != 1 && status != 2)) {
throw new ServiceException("审核状态不正确");
}
afterSale.setStatus(status);
afterSale.setAuditTime(new Date());
if (status == 2) {
// 拒绝
if (StringUtils.isBlank(refuseReason)) {
throw new ServiceException("拒绝原因不能为空");
}
afterSale.setRefuseReason(refuseReason);
} else if (status == 1) {
// 同意
if (afterSale.getType() == 1 || afterSale.getType() == 2) {
// 退货或换货,状态变为待退货
afterSale.setStatus(3);
}
// 退款类型,状态保持为已同意,等待退款处理
}
return baseMapper.updateById(afterSale) > 0;
}
/**
* 用户填写退货物流信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean fillLogistics(Long id, String logisticsCompany, String logisticsNo) {
MallAfterSale afterSale = baseMapper.selectById(id);
if (afterSale == null) {
throw new ServiceException("售后申请不存在");
}
if (afterSale.getStatus() != 3) {
throw new ServiceException("当前状态不能填写物流信息");
}
if (afterSale.getType() == 0) {
throw new ServiceException("退款类型不需要填写物流信息");
}
afterSale.setLogisticsCompany(logisticsCompany);
afterSale.setLogisticsNo(logisticsNo);
afterSale.setReturnTime(new Date());
afterSale.setStatus(4); // 待收货
return baseMapper.updateById(afterSale) > 0;
}
/**
* 确认收货(商家确认收到退货)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean confirmReceive(Long id) {
MallAfterSale afterSale = baseMapper.selectById(id);
if (afterSale == null) {
throw new ServiceException("售后申请不存在");
}
if (afterSale.getStatus() != 4) {
throw new ServiceException("当前状态不能确认收货");
}
afterSale.setReceiveTime(new Date());
// 状态变为已完成(退款或换货完成需要调用支付中心退款接口或换货接口)
// 这里暂时设置为已完成,实际业务中需要调用支付中心退款接口
afterSale.setStatus(5);
return baseMapper.updateById(afterSale) > 0;
}
/**
* 完成售后(退款完成或换货完成)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean complete(Long id) {
MallAfterSale afterSale = baseMapper.selectById(id);
if (afterSale == null) {
throw new ServiceException("售后申请不存在");
}
if (afterSale.getStatus() == 5 || afterSale.getStatus() == 6) {
throw new ServiceException("售后申请已完成或已取消");
}
afterSale.setStatus(5);
if (afterSale.getType() == 0 || afterSale.getType() == 1) {
// 退款或退货,设置退款时间
afterSale.setRefundTime(new Date());
}
return baseMapper.updateById(afterSale) > 0;
}
/**
* 取消售后申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cancel(Long id) {
MallAfterSale afterSale = baseMapper.selectById(id);
if (afterSale == null) {
throw new ServiceException("售后申请不存在");
}
if (afterSale.getStatus() == 5 || afterSale.getStatus() == 6) {
throw new ServiceException("售后申请已完成或已取消");
}
if (afterSale.getStatus() == 4) {
throw new ServiceException("商家已收货,不能取消");
}
afterSale.setStatus(6);
return baseMapper.updateById(afterSale) > 0;
}
}
@@ -0,0 +1,227 @@
package org.dromara.mall.trade.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.mall.coupon.domain.vo.MallCouponVo;
import org.dromara.mall.coupon.service.MallCouponService;
import org.dromara.mall.coupon.service.MallCouponUserService;
import org.dromara.mall.trade.domain.MallOrderCoupon;
import org.dromara.mall.trade.domain.bo.MallOrderCouponBo;
import org.dromara.mall.trade.domain.vo.MallOrderCouponVo;
import org.dromara.mall.trade.mapper.MallOrderCouponMapper;
import org.dromara.mall.trade.service.MallOrderCouponService;
import org.dromara.mall.trade.utils.CouponCalculator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 订单优惠券使用记录Service业务层处理
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class MallOrderCouponServiceImpl implements MallOrderCouponService {
private final MallOrderCouponMapper baseMapper;
private final MallCouponService couponService;
private final MallCouponUserService couponUserService;
/**
* 查询订单优惠券使用记录
*/
@Override
public MallOrderCouponVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 根据订单ID查询订单优惠券使用记录列表
*/
@Override
public List<MallOrderCouponVo> queryListByOrderId(Long orderId) {
LambdaQueryWrapper<MallOrderCoupon> lqw = new LambdaQueryWrapper<>();
lqw.eq(MallOrderCoupon::getOrderId, orderId);
return baseMapper.selectVoList(lqw);
}
/**
* 根据订单号查询订单优惠券使用记录列表
*/
@Override
public List<MallOrderCouponVo> queryListByOrderNo(String orderNo) {
LambdaQueryWrapper<MallOrderCoupon> lqw = new LambdaQueryWrapper<>();
lqw.eq(MallOrderCoupon::getOrderNo, orderNo);
return baseMapper.selectVoList(lqw);
}
/**
* 新增订单优惠券使用记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(MallOrderCouponBo bo) {
MallOrderCoupon add = MapstructUtils.convert(bo, MallOrderCoupon.class);
validEntityBeforeSave(add);
// 设置使用时间
if (add.getCreateTime() == null) {
add.setCreateTime(new Date());
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(MallOrderCoupon entity) {
// 可以添加业务校验逻辑
}
/**
* 批量删除订单优惠券使用记录信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 可以添加业务校验逻辑
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 计算并使用优惠券(订单创建时调用)
* 计算优惠券折扣金额,创建订单优惠券使用记录,并标记用户优惠券为已使用
*
* @param couponUserId 用户优惠券ID
* @param orderId 订单ID
* @param orderNo 订单号
* @param orderAmount 订单金额(单位:分)
* @return 优惠金额(单位:分)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Long calculateAndUseCoupon(Long couponUserId, Long orderId, String orderNo, Long orderAmount) {
if (couponUserId == null) {
throw new ServiceException("用户优惠券ID不能为空");
}
if (orderId == null) {
throw new ServiceException("订单ID不能为空");
}
if (orderNo == null || orderNo.trim().isEmpty()) {
throw new ServiceException("订单号不能为空");
}
if (orderAmount == null || orderAmount <= 0) {
throw new ServiceException("订单金额必须大于0");
}
// 查询用户优惠券信息
org.dromara.mall.coupon.domain.vo.MallCouponUserVo couponUser = couponUserService.queryById(couponUserId);
if (couponUser == null) {
throw new ServiceException("用户优惠券不存在");
}
if (couponUser.getStatus() != 0) {
throw new ServiceException("优惠券已使用或已过期");
}
// 查询优惠券信息
MallCouponVo coupon = couponService.queryById(couponUser.getCouponId());
if (coupon == null) {
throw new ServiceException("优惠券不存在");
}
// 计算优惠金额
Long discountAmount = CouponCalculator.calculateDiscountAmount(coupon, orderAmount);
if (discountAmount <= 0) {
throw new ServiceException("订单金额不满足优惠券使用门槛");
}
// 标记用户优惠券为已使用(调用优惠券服务的useCoupon方法)
couponUserService.useCoupon(couponUserId, orderId);
// 创建订单优惠券使用记录
MallOrderCouponBo orderCouponBo = new MallOrderCouponBo();
orderCouponBo.setOrderId(orderId);
orderCouponBo.setOrderNo(orderNo);
orderCouponBo.setCouponId(coupon.getId());
orderCouponBo.setCouponUserId(couponUserId);
orderCouponBo.setDiscountAmount(discountAmount.intValue());
boolean flag = insertByBo(orderCouponBo);
if (!flag) {
throw new ServiceException("创建订单优惠券使用记录失败");
}
log.info("优惠券使用成功 - 用户优惠券ID: {}, 订单ID: {}, 订单号: {}, 优惠金额: {}",
couponUserId, orderId, orderNo, discountAmount);
return discountAmount;
}
/**
* 根据订单ID统计优惠金额
*
* @param orderId 订单ID
* @return 优惠金额总和(单位:分)
*/
@Override
public Long sumDiscountAmountByOrderId(Long orderId) {
if (orderId == null) {
return 0L;
}
List<MallOrderCouponVo> list = queryListByOrderId(orderId);
return list.stream()
.mapToLong(vo -> vo.getDiscountAmount() != null ? vo.getDiscountAmount().longValue() : 0L)
.sum();
}
/**
* 根据订单号统计优惠金额
*
* @param orderNo 订单号
* @return 优惠金额总和(单位:分)
*/
@Override
public Long sumDiscountAmountByOrderNo(String orderNo) {
if (orderNo == null || orderNo.trim().isEmpty()) {
return 0L;
}
List<MallOrderCouponVo> list = queryListByOrderNo(orderNo);
return list.stream()
.mapToLong(vo -> vo.getDiscountAmount() != null ? vo.getDiscountAmount().longValue() : 0L)
.sum();
}
/**
* 根据优惠券ID统计核销金额
*
* @param couponId 优惠券ID
* @return 核销金额总和(单位:分)
*/
@Override
public Long sumDiscountAmountByCouponId(Long couponId) {
if (couponId == null) {
return 0L;
}
LambdaQueryWrapper<MallOrderCoupon> lqw = new LambdaQueryWrapper<>();
lqw.eq(MallOrderCoupon::getCouponId, couponId);
List<MallOrderCouponVo> list = baseMapper.selectVoList(lqw);
return list.stream()
.mapToLong(vo -> vo.getDiscountAmount() != null ? vo.getDiscountAmount().longValue() : 0L)
.sum();
}
}
@@ -0,0 +1,172 @@
package org.dromara.mall.trade.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.mall.trade.domain.MallOrderEvaluate;
import org.dromara.mall.trade.domain.bo.MallOrderEvaluateBo;
import org.dromara.mall.trade.domain.vo.MallOrderEvaluateVo;
import org.dromara.mall.trade.mapper.MallOrderEvaluateMapper;
import org.dromara.mall.trade.service.MallOrderEvaluateService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 订单评价Service业务层处理
*
* @author pengles
*/
@RequiredArgsConstructor
@Service
public class MallOrderEvaluateServiceImpl implements MallOrderEvaluateService {
private final MallOrderEvaluateMapper baseMapper;
/**
* 查询订单评价
*/
@Override
public MallOrderEvaluateVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* 查询订单评价列表
*/
@Override
public TableDataInfo<MallOrderEvaluateVo> queryPageList(MallOrderEvaluateBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<MallOrderEvaluate> lqw = buildQueryWrapper(bo);
Page<MallOrderEvaluateVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询订单评价列表
*/
@Override
public List<MallOrderEvaluateVo> queryList(MallOrderEvaluateBo bo) {
LambdaQueryWrapper<MallOrderEvaluate> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<MallOrderEvaluate> buildQueryWrapper(MallOrderEvaluateBo bo) {
LambdaQueryWrapper<MallOrderEvaluate> lqw = new LambdaQueryWrapper<>();
lqw.eq(bo.getOrderId() != null, MallOrderEvaluate::getOrderId, bo.getOrderId());
lqw.eq(StringUtils.isNotBlank(bo.getOrderNo()), MallOrderEvaluate::getOrderNo, bo.getOrderNo());
lqw.eq(bo.getOrderItemId() != null, MallOrderEvaluate::getOrderItemId, bo.getOrderItemId());
lqw.eq(bo.getProductId() != null, MallOrderEvaluate::getProductId, bo.getProductId());
lqw.eq(bo.getSkuId() != null, MallOrderEvaluate::getSkuId, bo.getSkuId());
lqw.eq(bo.getUserId() != null, MallOrderEvaluate::getUserId, bo.getUserId());
lqw.eq(bo.getScore() != null, MallOrderEvaluate::getScore, bo.getScore());
lqw.eq(bo.getStatus() != null, MallOrderEvaluate::getStatus, bo.getStatus());
lqw.orderByDesc(MallOrderEvaluate::getCreateTime);
return lqw;
}
/**
* 新增订单评价
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(MallOrderEvaluateBo bo) {
MallOrderEvaluate add = MapstructUtils.convert(bo, MallOrderEvaluate.class);
validEntityBeforeSave(add);
// 设置用户ID(移动端用户)
if (add.getUserId() == null) {
add.setUserId(LoginHelper.getUserId());
}
// 设置默认状态为待审核
if (add.getStatus() == null) {
add.setStatus(0);
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改订单评价
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(MallOrderEvaluateBo bo) {
MallOrderEvaluate update = MapstructUtils.convert(bo, MallOrderEvaluate.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(MallOrderEvaluate entity) {
// 可以添加业务校验逻辑
}
/**
* 批量删除订单评价
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 可以添加业务校验逻辑
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 审核评价(通过/驳回)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean audit(Long id, Integer status) {
MallOrderEvaluate evaluate = baseMapper.selectById(id);
if (evaluate == null) {
throw new ServiceException("评价不存在");
}
if (evaluate.getStatus() != 0) {
throw new ServiceException("评价已审核,不能重复审核");
}
if (status == null || (status != 1 && status != 2)) {
throw new ServiceException("审核状态不正确");
}
evaluate.setStatus(status);
return baseMapper.updateById(evaluate) > 0;
}
/**
* 回复评价(商家回复)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean reply(Long id, String replyContent) {
MallOrderEvaluate evaluate = baseMapper.selectById(id);
if (evaluate == null) {
throw new ServiceException("评价不存在");
}
if (evaluate.getStatus() != 1) {
throw new ServiceException("只有已通过审核的评价才能回复");
}
evaluate.setReplyContent(replyContent);
evaluate.setReplyTime(new Date());
return baseMapper.updateById(evaluate) > 0;
}
}
@@ -0,0 +1,175 @@
package org.dromara.mall.trade.service.impl;
import cn.hutool.core.lang.Dict;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.mall.promotion.domain.MallPromotion;
import org.dromara.mall.promotion.domain.vo.MallPromotionVo;
import org.dromara.mall.promotion.domain.vo.MallSeckillVo;
import org.dromara.mall.promotion.mapper.MallPromotionMapper;
import org.dromara.mall.promotion.service.MallSeckillService;
import org.dromara.mall.trade.service.SeckillOrderService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 秒杀订单处理Service业务层实现
*
* @author pengles
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class SeckillOrderServiceImpl implements SeckillOrderService {
private final MallSeckillService mallSeckillService;
private final MallPromotionMapper promotionMapper;
/**
* 活动类型:秒杀
*/
private static final Integer PROMOTION_TYPE_SECKILL = 2;
/**
* 默认限购数量
*/
private static final Integer DEFAULT_PURCHASE_LIMIT = 1;
/**
* 验证秒杀活动是否可用
*/
@Override
public SeckillValidationResult validateSeckillActivity(Long seckillId, Integer quantity) {
// 查询秒杀活动
MallSeckillVo seckillVo = mallSeckillService.queryById(seckillId);
if (seckillVo == null) {
return new SeckillValidationResult(false, "秒杀活动不存在");
}
// 验证秒杀活动状态
if (seckillVo.getStatus() != 0) {
return new SeckillValidationResult(false, "秒杀活动已禁用");
}
// 验证秒杀活动时间
Date now = new Date();
if (seckillVo.getStartTime() != null && seckillVo.getStartTime().after(now)) {
return new SeckillValidationResult(false, "秒杀活动尚未开始");
}
if (seckillVo.getEndTime() != null && seckillVo.getEndTime().before(now)) {
return new SeckillValidationResult(false, "秒杀活动已结束");
}
// 验证秒杀库存
if (seckillVo.getSeckillStock() == null || seckillVo.getSeckillStock() < quantity) {
return new SeckillValidationResult(false, "秒杀库存不足");
}
// 验证关联的营销活动
if (seckillVo.getPromotionId() != null) {
MallPromotionVo promotionVo = promotionMapper.selectVoById(seckillVo.getPromotionId());
if (promotionVo == null) {
return new SeckillValidationResult(false, "关联的营销活动不存在");
}
if (promotionVo.getStatus() != 0) {
return new SeckillValidationResult(false, "关联的营销活动已禁用");
}
if (!PROMOTION_TYPE_SECKILL.equals(promotionVo.getType())) {
return new SeckillValidationResult(false, "关联的营销活动类型不正确");
}
}
// 返回验证通过的结果,包含秒杀信息
Map<String, Object> data = new HashMap<>();
data.put("seckillPrice", seckillVo.getSeckillPrice());
data.put("promotionId", seckillVo.getPromotionId());
data.put("skuId", seckillVo.getSkuId());
return new SeckillValidationResult(true, null, data);
}
/**
* 验证秒杀限购
*/
@Override
public Boolean validatePurchaseLimit(Long seckillId, Long userId, Long skuId) {
// 查询秒杀活动信息
MallSeckillVo seckillVo = mallSeckillService.queryById(seckillId);
if (seckillVo == null) {
throw new ServiceException("秒杀活动不存在");
}
// 获取限购数量(从营销活动的rules JSON中读取,或使用默认值)
Integer purchaseLimit = DEFAULT_PURCHASE_LIMIT;
if (seckillVo.getPromotionId() != null) {
MallPromotion promotion = promotionMapper.selectById(seckillVo.getPromotionId());
if (promotion != null && promotion.getRules() != null) {
try {
Dict rules = JsonUtils.parseMap(promotion.getRules());
if (rules != null && rules.containsKey("purchaseLimit")) {
Object limitObj = rules.get("purchaseLimit");
if (limitObj instanceof Number) {
purchaseLimit = ((Number) limitObj).intValue();
}
}
} catch (Exception e) {
log.warn("解析营销活动规则JSON失败,使用默认限购数量: {}", e.getMessage());
}
}
}
// TODO: 查询用户已购买的秒杀订单数量
// 注意:由于订单实体类和服务尚未实现,这里暂时返回true(允许购买)
// 后续需要在订单服务中实现此逻辑:
// 1. 查询用户已购买的秒杀订单数量(同一秒杀活动、同一SKU,仅统计已支付的订单)
// 2. 校验是否超过限购数量
// 示例代码(需要等订单服务实现后):
// LambdaQueryWrapper<MallOrder> lqw = new LambdaQueryWrapper<>();
// lqw.eq(MallOrder::getUserId, userId);
// lqw.eq(MallOrder::getSeckillId, seckillId);
// lqw.eq(MallOrder::getPayStatus, PAY_STATUS_PAID);
// lqw.ne(MallOrder::getOrderStatus, 4);
// Long purchasedCount = orderMapper.selectCount(lqw);
// return purchasedCount == null || purchasedCount < purchaseLimit;
log.info("秒杀限购校验 - 秒杀ID: {}, 用户ID: {}, SKU ID: {}, 限购数量: {} (待订单服务实现后完善)", seckillId, userId, skuId, purchaseLimit);
return true; // 临时返回true,待订单服务实现后完善
}
/**
* 预扣秒杀库存
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean preDeductSeckillStock(Long seckillId, Integer quantity) {
return mallSeckillService.reduceStock(seckillId, quantity);
}
/**
* 恢复秒杀库存(订单取消时调用)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean restoreSeckillStock(Long seckillId, Integer quantity) {
return mallSeckillService.restoreStock(seckillId, quantity);
}
/**
* 获取秒杀价格
*/
@Override
public Integer getSeckillPrice(Long seckillId) {
MallSeckillVo seckillVo = mallSeckillService.queryById(seckillId);
if (seckillVo == null) {
throw new ServiceException("秒杀活动不存在");
}
return seckillVo.getSeckillPrice();
}
}
@@ -0,0 +1,139 @@
package org.dromara.mall.trade.utils;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.mall.coupon.domain.vo.MallCouponVo;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 优惠券计算工具类
* 用于计算优惠券的折扣金额
*
* @author pengles
*/
@Slf4j
public class CouponCalculator {
/**
* 计算优惠券的折扣金额(单位:分)
*
* @param coupon 优惠券信息
* @param orderAmount 订单金额(单位:分)
* @return 折扣金额(单位:分),如果订单金额不满足使用门槛,返回0
*/
public static Long calculateDiscountAmount(MallCouponVo coupon, Long orderAmount) {
if (coupon == null) {
throw new ServiceException("优惠券信息不能为空");
}
if (orderAmount == null || orderAmount <= 0) {
throw new ServiceException("订单金额必须大于0");
}
// 检查使用门槛
Integer minAmount = coupon.getMinAmount();
if (minAmount != null && minAmount > 0 && orderAmount < minAmount) {
log.debug("订单金额{}不满足优惠券{}的使用门槛{}", orderAmount, coupon.getId(), minAmount);
return 0L;
}
Integer type = coupon.getType();
if (type == null) {
throw new ServiceException("优惠券类型不能为空");
}
Long discountAmount = 0L;
// 0-满减,1-折扣,2-现金
if (type == 0 || type == 2) {
// 满减券或现金券:直接使用 discountAmount
Integer couponDiscountAmount = coupon.getDiscountAmount();
if (couponDiscountAmount == null || couponDiscountAmount <= 0) {
throw new ServiceException("优惠券优惠金额配置错误");
}
discountAmount = couponDiscountAmount.longValue();
// 确保折扣金额不超过订单金额
if (discountAmount > orderAmount) {
discountAmount = orderAmount;
}
} else if (type == 1) {
// 折扣券:使用 discountRate(百分比)
BigDecimal discountRate = coupon.getDiscountRate();
if (discountRate == null || discountRate.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("优惠券折扣率配置错误");
}
if (discountRate.compareTo(new BigDecimal("100")) > 0) {
throw new ServiceException("折扣率不能大于100%");
}
// 计算折扣金额:订单金额 * (折扣率 / 100)
BigDecimal orderAmountDecimal = new BigDecimal(orderAmount);
BigDecimal discountAmountDecimal = orderAmountDecimal
.multiply(discountRate)
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
discountAmount = discountAmountDecimal.setScale(0, RoundingMode.HALF_UP).longValue();
// 确保折扣金额不超过订单金额
if (discountAmount > orderAmount) {
discountAmount = orderAmount;
}
} else {
throw new ServiceException("不支持的优惠券类型: " + type);
}
log.debug("优惠券{}计算折扣金额: 订单金额={}, 折扣金额={}", coupon.getId(), orderAmount, discountAmount);
return discountAmount;
}
/**
* 计算优惠券的折扣金额(单位:分)
* 使用优惠券ID和订单金额,需要传入优惠券查询方法
*
* @param couponService 优惠券服务,用于查询优惠券信息
* @param couponId 优惠券ID
* @param orderAmount 订单金额(单位:分)
* @return 折扣金额(单位:分),如果订单金额不满足使用门槛或优惠券不存在,返回0
*/
public static Long calculateDiscountAmount(
org.dromara.mall.coupon.service.MallCouponService couponService,
Long couponId,
Long orderAmount) {
if (couponService == null) {
throw new ServiceException("优惠券服务不能为空");
}
if (couponId == null) {
throw new ServiceException("优惠券ID不能为空");
}
org.dromara.mall.coupon.domain.vo.MallCouponVo coupon = couponService.queryById(couponId);
if (coupon == null) {
log.warn("优惠券{}不存在", couponId);
return 0L;
}
return calculateDiscountAmount(coupon, orderAmount);
}
/**
* 验证优惠券是否可用
*
* @param coupon 优惠券信息
* @param orderAmount 订单金额(单位:分)
* @return true表示可用,false表示不可用
*/
public static boolean isValidCoupon(MallCouponVo coupon, Long orderAmount) {
if (coupon == null) {
return false;
}
// 检查状态
if (coupon.getStatus() != null && coupon.getStatus() != 0) {
return false;
}
// 检查使用门槛
Integer minAmount = coupon.getMinAmount();
if (minAmount != null && minAmount > 0 && orderAmount < minAmount) {
return false;
}
// 检查有效期(如果订单服务需要,可以添加时间校验)
return true;
}
}
@@ -0,0 +1,3 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.dromara.mall.trade.config.MallTradeAutoConfiguration