共富商城
This commit is contained in:
1 parent
10e8120314
commit
bd35814198
14 files changed
+1552
-8
No files matched your search
@@ -77,5 +77,6 @@
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations-jakarta</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,348 @@
|
||||
# 商品新增接口使用说明
|
||||
|
||||
## 商品创建接口
|
||||
|
||||
### 接口地址
|
||||
```
|
||||
POST /mallProduct
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
支持两种请求体类型:
|
||||
|
||||
#### 1. 包含SKU的创建(使用MallProductCreateDTO)
|
||||
|
||||
##### 多SKU模式示例(priceType = 1)
|
||||
```json
|
||||
{
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 1,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机",
|
||||
"deptId": 1,
|
||||
"skuList": [
|
||||
{
|
||||
"skuCode": "IPHONE15-128-BLACK",
|
||||
"skuName": "iPhone 15 128GB 黑色",
|
||||
"price": 5999.00,
|
||||
"costPrice": 4500.00,
|
||||
"stock": 100,
|
||||
"barcode": "1234567890123",
|
||||
"weightG": 171,
|
||||
"volumeCm3": 200,
|
||||
"status": 1,
|
||||
"deptId": 1
|
||||
},
|
||||
{
|
||||
"skuCode": "IPHONE15-256-BLACK",
|
||||
"skuName": "iPhone 15 256GB 黑色",
|
||||
"price": 6999.00,
|
||||
"costPrice": 5200.00,
|
||||
"stock": 50,
|
||||
"barcode": "1234567890124",
|
||||
"weightG": 171,
|
||||
"volumeCm3": 200,
|
||||
"status": 1,
|
||||
"deptId": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
##### 单价SPU模式示例(priceType = 2)
|
||||
```json
|
||||
{
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 2,
|
||||
"spuPrice": 5999.00,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机",
|
||||
"deptId": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 普通SPU创建(使用MallProductEntity)
|
||||
```json
|
||||
{
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 2,
|
||||
"spuPrice": 5999.00,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机"
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
#### 成功响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "商品创建成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
#### 失败响应
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "商品创建失败:SKU编码不能为空",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
#### SPU字段
|
||||
- `merchantId`: 商户ID(必填)
|
||||
- `categoryId`: 类目ID(必填)
|
||||
- `title`: 商品标题(必填)
|
||||
- `subTitle`: 副标题
|
||||
- `brand`: 品牌
|
||||
- `coverImage`: 主图
|
||||
- `carouselImages`: SPU轮播图URL(逗号分隔)
|
||||
- `priceType`: 定价方式(1=多SKU,2=单价SPU)(必填)
|
||||
- `spuPrice`: SPU售价(当priceType=2时有效)
|
||||
- `originalPrice`: 划线价
|
||||
- `unit`: 计量单位
|
||||
- `status`: 状态(1=上架,0=下架)
|
||||
- `saleStartTime`: 开售时间
|
||||
- `saleEndTime`: 停售时间
|
||||
- `description`: 详情描述
|
||||
- `deptId`: 部门ID
|
||||
|
||||
#### SKU字段(当priceType=1时必填)
|
||||
- `skuCode`: SKU编码(必填)
|
||||
- `skuName`: SKU名称(必填)
|
||||
- `price`: 售价(必填)
|
||||
- `costPrice`: 成本价
|
||||
- `stock`: 库存(必填)
|
||||
- `barcode`: 条码
|
||||
- `weightG`: 重量(克)
|
||||
- `volumeCm3`: 体积(立方厘米)
|
||||
- `status`: 状态(1=启用,0=停用)
|
||||
- `deptId`: 部门ID
|
||||
|
||||
### 业务逻辑说明
|
||||
|
||||
1. **多SKU模式(priceType = 1)**:
|
||||
- 必须提供skuList
|
||||
- 系统会自动计算总库存(所有SKU库存之和)
|
||||
- 每个SKU都会保存到数据库
|
||||
|
||||
2. **单价SPU模式(priceType = 2)**:
|
||||
- 不需要提供skuList
|
||||
- 使用spuPrice作为商品价格
|
||||
- 总库存设置为0(可根据业务需求调整)
|
||||
|
||||
3. **事务处理**:
|
||||
- 整个创建过程在一个事务中执行
|
||||
- 如果任何步骤失败,所有操作都会回滚
|
||||
|
||||
4. **数据验证**:
|
||||
- 使用JSR-303验证注解进行参数校验
|
||||
- 必填字段验证
|
||||
- 业务逻辑验证
|
||||
|
||||
### 权限要求
|
||||
需要 `supermarket_mallProduct_add` 权限
|
||||
|
||||
## 商品编辑接口
|
||||
|
||||
### 接口地址
|
||||
```
|
||||
PUT /mallProduct
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
支持两种请求体类型:
|
||||
|
||||
#### 1. 包含SKU的编辑(使用MallProductCreateDTO)
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机(已更新)",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 1,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机",
|
||||
"skuList": [
|
||||
{
|
||||
"skuCode": "IPHONE15-128-BLACK-UPDATED",
|
||||
"skuName": "iPhone 15 128GB 黑色(已更新)",
|
||||
"price": 5999.00,
|
||||
"costPrice": 4500.00,
|
||||
"stock": 100,
|
||||
"barcode": "1234567890123",
|
||||
"weightG": 171,
|
||||
"volumeCm3": 200,
|
||||
"status": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 普通SPU编辑(使用MallProductEntity)
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机(已更新)",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 2,
|
||||
"spuPrice": 5999.00,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机"
|
||||
}
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "商品更新成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 权限要求
|
||||
需要 `supermarket_mallProduct_edit` 权限
|
||||
|
||||
## 商品详情查询接口
|
||||
|
||||
### 接口地址
|
||||
```
|
||||
GET /mallProduct/{id}
|
||||
```
|
||||
|
||||
### 请求参数
|
||||
- `id`: 商品ID(路径参数)
|
||||
- `withSkus`: 是否包含SKU信息(查询参数,可选,默认false)
|
||||
|
||||
### 使用示例
|
||||
|
||||
#### 1. 查询基本信息(不包含SKU)
|
||||
```
|
||||
GET /mallProduct/1
|
||||
```
|
||||
|
||||
#### 2. 查询完整信息(包含SKU)
|
||||
```
|
||||
GET /mallProduct/1?withSkus=true
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
#### 基本信息响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 1,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 完整信息响应(withSkus=true)
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"merchantId": 1,
|
||||
"categoryId": 100,
|
||||
"title": "苹果手机",
|
||||
"subTitle": "最新款智能手机",
|
||||
"brand": "Apple",
|
||||
"coverImage": "https://example.com/cover.jpg",
|
||||
"carouselImages": "https://example.com/img1.jpg,https://example.com/img2.jpg",
|
||||
"priceType": 1,
|
||||
"originalPrice": 9999.00,
|
||||
"unit": "台",
|
||||
"status": 1,
|
||||
"saleStartTime": "2025-01-27T00:00:00",
|
||||
"saleEndTime": "2025-12-31T23:59:59",
|
||||
"description": "这是一款高性能的智能手机",
|
||||
"skuList": [
|
||||
{
|
||||
"skuCode": "IPHONE15-128-BLACK",
|
||||
"skuName": "iPhone 15 128GB 黑色",
|
||||
"price": 5999.00,
|
||||
"costPrice": 4500.00,
|
||||
"stock": 100,
|
||||
"barcode": "1234567890123",
|
||||
"weightG": 171,
|
||||
"volumeCm3": 200,
|
||||
"status": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 权限要求
|
||||
需要 `supermarket_mallProduct_view` 权限
|
||||
@@ -0,0 +1,308 @@
|
||||
# 下拉选择接口使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
为了方便后台管理系统使用下拉选择组件,我们提供了一系列统一的下拉选择接口。这些接口返回标准化的选项数据格式,便于前端组件使用。
|
||||
|
||||
## 统一返回格式
|
||||
|
||||
所有下拉选择接口都返回以下格式的数据:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "选项名称",
|
||||
"disabled": false,
|
||||
"extra": "扩展信息"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
- `value`: 选项值,可以是数字、字符串等类型
|
||||
- `label`: 选项显示名称
|
||||
- `disabled`: 是否禁用,默认为false
|
||||
- `extra`: 扩展信息,可以包含额外的数据
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 1. 商户下拉选择
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
GET /selectOption/merchant?keyWord=关键字
|
||||
```
|
||||
|
||||
#### 请求参数
|
||||
- `keyWord` (可选): 关键字,支持商户名称和手机号模糊查询
|
||||
|
||||
#### 权限要求
|
||||
需要 `supermarket_mallMerchant_view` 权限
|
||||
|
||||
#### 使用示例
|
||||
```
|
||||
# 获取所有商户
|
||||
GET /selectOption/merchant
|
||||
|
||||
# 按商户名称搜索
|
||||
GET /selectOption/merchant?keyWord=农场
|
||||
|
||||
# 按手机号搜索
|
||||
GET /selectOption/merchant?keyWord=138
|
||||
|
||||
# 按关键字搜索(商户名称或手机号)
|
||||
GET /selectOption/merchant?keyWord=张三
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "张三农场",
|
||||
"disabled": false,
|
||||
"extra": "13800138000"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "李四果园",
|
||||
"disabled": false,
|
||||
"extra": "13900139000"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 分类下拉选择
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
GET /selectOption/category
|
||||
```
|
||||
|
||||
#### 权限要求
|
||||
需要 `supermarket_mallCategory_view` 权限
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "水果",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "蔬菜",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": "苹果",
|
||||
"disabled": false,
|
||||
"extra": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 分类下拉选择(树形结构)
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
GET /selectOption/category/tree
|
||||
```
|
||||
|
||||
#### 权限要求
|
||||
需要 `supermarket_mallCategory_view` 权限
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "操作成功",
|
||||
"data": [
|
||||
{
|
||||
"value": 1,
|
||||
"label": "水果",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": " └─ 苹果",
|
||||
"disabled": false,
|
||||
"extra": 1
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "蔬菜",
|
||||
"disabled": false,
|
||||
"extra": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 传统接口
|
||||
|
||||
除了统一的下拉选择接口,原有的控制器也提供了下拉选择接口:
|
||||
|
||||
### 商户下拉选择(传统接口)
|
||||
```
|
||||
GET /mallMerchant/selectList
|
||||
```
|
||||
|
||||
### 分类下拉选择(传统接口)
|
||||
```
|
||||
GET /mallCategory/selectList
|
||||
```
|
||||
|
||||
## 前端使用示例
|
||||
|
||||
### Vue.js + Element Plus
|
||||
|
||||
```javascript
|
||||
// 获取商户下拉选择
|
||||
async getMerchantOptions(keyword = '') {
|
||||
try {
|
||||
const params = keyword ? { keyWord: keyword } : {};
|
||||
const response = await this.$http.get('/selectOption/merchant', { params });
|
||||
this.merchantOptions = response.data.data.map(item => ({
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
disabled: item.disabled
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('获取商户选项失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索商户
|
||||
async searchMerchants(keyword) {
|
||||
await this.getMerchantOptions(keyword);
|
||||
}
|
||||
|
||||
// 获取分类下拉选择
|
||||
async getCategoryOptions() {
|
||||
try {
|
||||
const response = await this.$http.get('/selectOption/category');
|
||||
this.categoryOptions = response.data.data.map(item => ({
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
disabled: item.disabled
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('获取分类选项失败:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### HTML模板
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<!-- 商户选择(支持搜索) -->
|
||||
<el-select
|
||||
v-model="form.merchantId"
|
||||
placeholder="请选择商户"
|
||||
filterable
|
||||
remote
|
||||
:remote-method="searchMerchants"
|
||||
:loading="merchantLoading">
|
||||
<el-option
|
||||
v-for="item in merchantOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled">
|
||||
<span style="float: left">{{ item.label }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.extra }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<!-- 分类选择 -->
|
||||
<el-select v-model="form.categoryId" placeholder="请选择分类">
|
||||
<el-option
|
||||
v-for="item in categoryOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
merchantId: null,
|
||||
categoryId: null
|
||||
},
|
||||
merchantOptions: [],
|
||||
categoryOptions: [],
|
||||
merchantLoading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getMerchantOptions();
|
||||
this.getCategoryOptions();
|
||||
},
|
||||
methods: {
|
||||
// 搜索商户
|
||||
async searchMerchants(keyword) {
|
||||
this.merchantLoading = true;
|
||||
try {
|
||||
await this.getMerchantOptions(keyword);
|
||||
} finally {
|
||||
this.merchantLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 扩展说明
|
||||
|
||||
### 添加新的下拉选择类型
|
||||
|
||||
如果需要添加新的下拉选择类型,可以:
|
||||
|
||||
1. 在 `SelectOptionService` 中添加新的方法
|
||||
2. 在 `SelectOptionController` 中添加对应的接口
|
||||
3. 按照统一的格式返回数据
|
||||
|
||||
### 自定义扩展信息
|
||||
|
||||
`extra` 字段可以包含任何额外的信息,例如:
|
||||
- 联系电话
|
||||
- 父级ID
|
||||
- 状态信息
|
||||
- 其他业务相关数据
|
||||
|
||||
前端可以根据需要解析和使用这些扩展信息。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有接口都需要相应的权限验证
|
||||
2. 只返回启用状态的数据
|
||||
3. 数据按照指定的排序规则返回
|
||||
4. 建议前端对接口返回的数据进行缓存,避免频繁请求
|
||||
+2
@@ -155,4 +155,6 @@ public class MallCategoryController {
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+16
@@ -120,4 +120,20 @@ public class MallMerchantController {
|
||||
public List<MallMerchantEntity> export(MallMerchantEntity mallMerchant,Long[] ids) {
|
||||
return mallMerchantService.list(Wrappers.lambdaQuery(mallMerchant).in(ArrayUtil.isNotEmpty(ids), MallMerchantEntity::getId, ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商户下拉选择列表
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "获取商户下拉选择列表" , description = "获取商户下拉选择列表" )
|
||||
@GetMapping("/selectList")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')" )
|
||||
public R getSelectList() {
|
||||
LambdaQueryWrapper<MallMerchantEntity> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.select(MallMerchantEntity::getId, MallMerchantEntity::getName, MallMerchantEntity::getContactName, MallMerchantEntity::getContactPhone);
|
||||
wrapper.eq(MallMerchantEntity::getStatus, 1); // 只查询启用状态的商户
|
||||
wrapper.orderByAsc(MallMerchantEntity::getName);
|
||||
List<MallMerchantEntity> list = mallMerchantService.list(wrapper);
|
||||
return R.ok(list);
|
||||
}
|
||||
}
|
||||
+56
-8
@@ -10,6 +10,8 @@ import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.log.annotation.SysLog;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallProductEntity;
|
||||
import com.bsd.pjcloud.supermarket.service.MallProductService;
|
||||
import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
@@ -58,39 +60,85 @@ public class MallProductController {
|
||||
/**
|
||||
* 通过id查询共富商城商品信息
|
||||
* @param id id
|
||||
* @param withSkus 是否包含SKU信息
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "通过id查询" , description = "通过id查询" )
|
||||
@GetMapping("/{id}" )
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallProduct_view')" )
|
||||
public R getById(@PathVariable("id" ) Long id) {
|
||||
return R.ok(mallProductService.getById(id));
|
||||
public R getById(@PathVariable("id" ) Long id, @RequestParam(value = "withSkus", defaultValue = "false") Boolean withSkus) {
|
||||
try {
|
||||
if (withSkus) {
|
||||
MallProductCreateDTO productDTO = mallProductService.getProductWithSkus(id);
|
||||
return R.ok(productDTO);
|
||||
} else {
|
||||
return R.ok(mallProductService.getById(id));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return R.failed("获取商品详情失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增共富商城商品信息
|
||||
* @param mallProduct 共富商城商品信息
|
||||
* @param requestBody 请求体(可以是MallProductEntity或MallProductCreateDTO)
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "新增共富商城商品信息" , description = "新增共富商城商品信息" )
|
||||
@SysLog("新增共富商城商品信息" )
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallProduct_add')" )
|
||||
public R save(@RequestBody MallProductEntity mallProduct) {
|
||||
return R.ok(mallProductService.save(mallProduct));
|
||||
public R save(@Valid @RequestBody Object requestBody) {
|
||||
try {
|
||||
// 判断请求体类型
|
||||
if (requestBody instanceof MallProductCreateDTO) {
|
||||
// 包含SKU的创建
|
||||
MallProductCreateDTO productCreateDTO = (MallProductCreateDTO) requestBody;
|
||||
boolean success = mallProductService.createProductWithSkus(productCreateDTO);
|
||||
if (success) {
|
||||
return R.ok("商品创建成功");
|
||||
} else {
|
||||
return R.failed("商品创建失败");
|
||||
}
|
||||
} else {
|
||||
// 普通SPU创建
|
||||
MallProductEntity mallProduct = (MallProductEntity) requestBody;
|
||||
return R.ok(mallProductService.save(mallProduct));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return R.failed("商品创建失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改共富商城商品信息
|
||||
* @param mallProduct 共富商城商品信息
|
||||
* @param requestBody 请求体(可以是MallProductEntity或MallProductCreateDTO)
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "修改共富商城商品信息" , description = "修改共富商城商品信息" )
|
||||
@SysLog("修改共富商城商品信息" )
|
||||
@PutMapping
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallProduct_edit')" )
|
||||
public R updateById(@RequestBody MallProductEntity mallProduct) {
|
||||
return R.ok(mallProductService.updateById(mallProduct));
|
||||
public R updateById(@RequestBody Object requestBody) {
|
||||
try {
|
||||
// 判断请求体类型
|
||||
if (requestBody instanceof MallProductCreateDTO) {
|
||||
// 包含SKU的更新
|
||||
MallProductCreateDTO productCreateDTO = (MallProductCreateDTO) requestBody;
|
||||
boolean success = mallProductService.updateProductWithSkus(productCreateDTO);
|
||||
if (success) {
|
||||
return R.ok("商品更新成功");
|
||||
} else {
|
||||
return R.failed("商品更新失败");
|
||||
}
|
||||
} else {
|
||||
// 普通SPU更新
|
||||
MallProductEntity mallProduct = (MallProductEntity) requestBody;
|
||||
return R.ok(mallProductService.updateById(mallProduct));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return R.failed("商品更新失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.bsd.pjcloud.supermarket.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.supermarket.dto.SelectOptionDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallMerchantEntity;
|
||||
import com.bsd.pjcloud.supermarket.service.SelectOptionService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 下拉选择控制器
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-27
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/selectOption")
|
||||
@Tag(description = "selectOption", name = "下拉选择管理")
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
public class SelectOptionController {
|
||||
|
||||
private final SelectOptionService selectOptionService;
|
||||
|
||||
/**
|
||||
* 获取商户下拉选择列表
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "获取商户下拉选择列表", description = "获取商户下拉选择列表")
|
||||
@GetMapping("/merchant")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')")
|
||||
public R<List<SelectOptionDTO>> getMerchantSelectList(@RequestParam(value = "keyWord",required = false)String keyWord) {
|
||||
List<SelectOptionDTO> list = selectOptionService.getMerchantSelectList(keyWord);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类下拉选择列表
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "获取分类下拉选择列表", description = "获取分类下拉选择列表")
|
||||
@GetMapping("/category")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallCategory_view')")
|
||||
public R<List<SelectOptionDTO>> getCategorySelectList() {
|
||||
List<SelectOptionDTO> list = selectOptionService.getCategorySelectList();
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类下拉选择列表(树形结构)
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "获取分类下拉选择列表(树形结构)", description = "获取分类下拉选择列表(树形结构)")
|
||||
@GetMapping("/category/tree")
|
||||
@PreAuthorize("@pms.hasPermission('supermarket_mallCategory_view')")
|
||||
public R<List<SelectOptionDTO>> getCategoryTreeSelectList() {
|
||||
List<SelectOptionDTO> list = selectOptionService.getCategoryTreeSelectList();
|
||||
return R.ok(list);
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.bsd.pjcloud.supermarket.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 共富商城商品创建DTO(包含SPU和SKU信息)
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-27
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "共富商城商品创建DTO")
|
||||
public class MallProductCreateDTO {
|
||||
|
||||
/**
|
||||
* 主键(编辑时使用)
|
||||
*/
|
||||
@Schema(description = "主键")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 商户ID
|
||||
*/
|
||||
@NotNull(message = "商户ID不能为空")
|
||||
@Schema(description = "商户ID")
|
||||
private Long merchantId;
|
||||
|
||||
/**
|
||||
* 类目ID
|
||||
*/
|
||||
@NotNull(message = "类目ID不能为空")
|
||||
@Schema(description = "类目ID")
|
||||
private Long categoryId;
|
||||
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
@NotBlank(message = "商品标题不能为空")
|
||||
@Schema(description = "商品标题")
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 副标题
|
||||
*/
|
||||
@Schema(description = "副标题")
|
||||
private String subTitle;
|
||||
|
||||
/**
|
||||
* 品牌
|
||||
*/
|
||||
@Schema(description = "品牌")
|
||||
private String brand;
|
||||
|
||||
/**
|
||||
* 主图
|
||||
*/
|
||||
@Schema(description = "主图")
|
||||
private String coverImage;
|
||||
|
||||
/**
|
||||
* SPU轮播图URL(逗号分隔,按顺序)
|
||||
*/
|
||||
@Schema(description = "SPU轮播图URL(逗号分隔,按顺序)")
|
||||
private String carouselImages;
|
||||
|
||||
/**
|
||||
* 定价方式(1多SKU 2单价SPU)
|
||||
*/
|
||||
@NotNull(message = "定价方式不能为空")
|
||||
@Schema(description = "定价方式(1多SKU 2单价SPU)")
|
||||
private Integer priceType;
|
||||
|
||||
/**
|
||||
* SPU售价(当price_type=2有效)
|
||||
*/
|
||||
@Schema(description = "SPU售价(当price_type=2有效)")
|
||||
private BigDecimal spuPrice;
|
||||
|
||||
/**
|
||||
* 划线价
|
||||
*/
|
||||
@Schema(description = "划线价")
|
||||
private BigDecimal originalPrice;
|
||||
|
||||
/**
|
||||
* 计量单位
|
||||
*/
|
||||
@Schema(description = "计量单位")
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 状态(1上架 0下架)
|
||||
*/
|
||||
@Schema(description = "状态(1上架 0下架)")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 开售时间
|
||||
*/
|
||||
@Schema(description = "开售时间")
|
||||
private LocalDateTime saleStartTime;
|
||||
|
||||
/**
|
||||
* 停售时间
|
||||
*/
|
||||
@Schema(description = "停售时间")
|
||||
private LocalDateTime saleEndTime;
|
||||
|
||||
/**
|
||||
* 详情描述(HTML/文本)
|
||||
*/
|
||||
@Schema(description = "详情描述(HTML/文本)")
|
||||
private String description;
|
||||
|
||||
|
||||
/**
|
||||
* SKU列表(当priceType=1时必填)
|
||||
*/
|
||||
@Valid
|
||||
@Schema(description = "SKU列表")
|
||||
private List<MallProductSkuCreateDTO> skuList;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.bsd.pjcloud.supermarket.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 共富商城商品SKU创建DTO
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-27
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "共富商城商品SKU创建DTO")
|
||||
public class MallProductSkuCreateDTO {
|
||||
|
||||
/**
|
||||
* SKU编码
|
||||
*/
|
||||
@NotBlank(message = "SKU编码不能为空")
|
||||
@Schema(description = "SKU编码")
|
||||
private String skuCode;
|
||||
|
||||
/**
|
||||
* SKU名称(规格组合)
|
||||
*/
|
||||
@NotBlank(message = "SKU名称不能为空")
|
||||
@Schema(description = "SKU名称(规格组合)")
|
||||
private String skuName;
|
||||
|
||||
/**
|
||||
* 售价
|
||||
*/
|
||||
@NotNull(message = "售价不能为空")
|
||||
@Schema(description = "售价")
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 成本价
|
||||
*/
|
||||
@Schema(description = "成本价")
|
||||
private BigDecimal costPrice;
|
||||
|
||||
/**
|
||||
* 库存
|
||||
*/
|
||||
@NotNull(message = "库存不能为空")
|
||||
@Schema(description = "库存")
|
||||
private Integer stock;
|
||||
|
||||
/**
|
||||
* 条码
|
||||
*/
|
||||
@Schema(description = "条码")
|
||||
private String barcode;
|
||||
|
||||
/**
|
||||
* 重量(克)
|
||||
*/
|
||||
@Schema(description = "重量(克)")
|
||||
private Integer weightG;
|
||||
|
||||
/**
|
||||
* 体积(立方厘米)
|
||||
*/
|
||||
@Schema(description = "体积(立方厘米)")
|
||||
private Integer volumeCm3;
|
||||
|
||||
/**
|
||||
* 状态(1启用 0停用)
|
||||
*/
|
||||
@Schema(description = "状态(1启用 0停用)")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.bsd.pjcloud.supermarket.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 下拉选择选项DTO
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-27
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "下拉选择选项DTO")
|
||||
public class SelectOptionDTO {
|
||||
|
||||
/**
|
||||
* 选项值
|
||||
*/
|
||||
@Schema(description = "选项值")
|
||||
private Object value;
|
||||
|
||||
/**
|
||||
* 选项标签
|
||||
*/
|
||||
@Schema(description = "选项标签")
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 是否禁用
|
||||
*/
|
||||
@Schema(description = "是否禁用")
|
||||
private Boolean disabled = false;
|
||||
|
||||
/**
|
||||
* 扩展信息
|
||||
*/
|
||||
@Schema(description = "扩展信息")
|
||||
private Object extra;
|
||||
|
||||
public SelectOptionDTO() {
|
||||
}
|
||||
|
||||
public SelectOptionDTO(Object value, String label) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public SelectOptionDTO(Object value, String label, Boolean disabled) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
public SelectOptionDTO(Object value, String label, Object extra) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
public SelectOptionDTO(Object value, String label, Boolean disabled, Object extra) {
|
||||
this.value = value;
|
||||
this.label = label;
|
||||
this.disabled = disabled;
|
||||
this.extra = extra;
|
||||
}
|
||||
}
|
||||
+21
@@ -2,7 +2,28 @@ package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallProductEntity;
|
||||
import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO;
|
||||
|
||||
public interface MallProductService extends IService<MallProductEntity> {
|
||||
|
||||
/**
|
||||
* 创建商品(包含SPU和SKU)
|
||||
* @param productCreateDTO 商品创建DTO
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean createProductWithSkus(MallProductCreateDTO productCreateDTO);
|
||||
|
||||
/**
|
||||
* 更新商品(包含SPU和SKU)
|
||||
* @param productCreateDTO 商品更新DTO
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean updateProductWithSkus(MallProductCreateDTO productCreateDTO);
|
||||
|
||||
/**
|
||||
* 获取商品详情(包含SPU和SKU)
|
||||
* @param id 商品ID
|
||||
* @return 商品详情DTO
|
||||
*/
|
||||
MallProductCreateDTO getProductWithSkus(Long id);
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.bsd.pjcloud.supermarket.dto.SelectOptionDTO;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallCategoryEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallMerchantEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 下拉选择服务
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-27
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SelectOptionService {
|
||||
|
||||
private final MallMerchantService mallMerchantService;
|
||||
private final MallCategoryService mallCategoryService;
|
||||
|
||||
/**
|
||||
* 获取商户下拉选择列表
|
||||
* @param keyword 关键字,支持商户名称和手机号模糊查询
|
||||
* @return 商户下拉选择列表
|
||||
*/
|
||||
public List<SelectOptionDTO> getMerchantSelectList(String keyword) {
|
||||
LambdaQueryWrapper<MallMerchantEntity> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.select(MallMerchantEntity::getId, MallMerchantEntity::getName, MallMerchantEntity::getContactName, MallMerchantEntity::getContactPhone);
|
||||
wrapper.eq(MallMerchantEntity::getStatus, 0); // 只查询启用状态的商户
|
||||
|
||||
// 如果关键字不为空,添加模糊查询条件
|
||||
if (keyword != null && !keyword.trim().isEmpty()) {
|
||||
wrapper.and(w -> w.like(MallMerchantEntity::getName, keyword.trim())
|
||||
.or()
|
||||
.like(MallMerchantEntity::getContactPhone, keyword.trim()));
|
||||
}
|
||||
|
||||
wrapper.orderByAsc(MallMerchantEntity::getName);
|
||||
|
||||
return mallMerchantService.list(wrapper)
|
||||
.stream()
|
||||
.map(merchant -> new SelectOptionDTO(
|
||||
merchant.getId(),
|
||||
merchant.getName(),
|
||||
false,
|
||||
merchant.getContactPhone() // 扩展信息:联系电话
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类下拉选择列表
|
||||
* @return 分类下拉选择列表
|
||||
*/
|
||||
public List<SelectOptionDTO> getCategorySelectList() {
|
||||
return mallCategoryService.listScope()
|
||||
.stream()
|
||||
.filter(category -> category.getStatus() == 1) // 只返回启用状态的分类
|
||||
.map(category -> new SelectOptionDTO(
|
||||
category.getId(),
|
||||
category.getName(),
|
||||
false,
|
||||
category.getParentId() // 扩展信息:父级ID
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类下拉选择列表(树形结构)
|
||||
* @return 分类下拉选择列表
|
||||
*/
|
||||
public List<SelectOptionDTO> getCategoryTreeSelectList() {
|
||||
List<MallCategoryEntity> allCategories = mallCategoryService.listScope();
|
||||
return buildCategorySelectTree(allCategories, 0L, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建分类选择树
|
||||
* @param allCategories 所有分类
|
||||
* @param parentId 父级ID
|
||||
* @param prefix 前缀
|
||||
* @return 分类选择列表
|
||||
*/
|
||||
private List<SelectOptionDTO> buildCategorySelectTree(List<MallCategoryEntity> allCategories, Long parentId, String prefix) {
|
||||
return allCategories.stream()
|
||||
.filter(category -> category.getStatus() == 1 &&
|
||||
(parentId == null ? category.getParentId() == null : parentId.equals(category.getParentId())))
|
||||
.map(category -> {
|
||||
String label = prefix + category.getName();
|
||||
SelectOptionDTO option = new SelectOptionDTO(category.getId(), label);
|
||||
option.setExtra(category.getParentId());
|
||||
return option;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+251
@@ -1,10 +1,23 @@
|
||||
package com.bsd.pjcloud.supermarket.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallProductEntity;
|
||||
import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity;
|
||||
import com.bsd.pjcloud.supermarket.mapper.MallProductMapper;
|
||||
import com.bsd.pjcloud.supermarket.service.MallProductService;
|
||||
import com.bsd.pjcloud.supermarket.service.MallProductSkuService;
|
||||
import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO;
|
||||
import com.bsd.pjcloud.supermarket.dto.MallProductSkuCreateDTO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 共富商城商品信息
|
||||
*
|
||||
@@ -12,5 +25,243 @@ import org.springframework.stereotype.Service;
|
||||
* @date 2025-09-02 17:47:01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MallProductServiceImpl extends ServiceImpl<MallProductMapper, MallProductEntity> implements MallProductService {
|
||||
|
||||
private final MallProductSkuService mallProductSkuService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean createProductWithSkus(MallProductCreateDTO productCreateDTO) {
|
||||
// 业务验证
|
||||
validateProductData(productCreateDTO);
|
||||
// 1. 创建SPU
|
||||
MallProductEntity product = new MallProductEntity();
|
||||
product.setMerchantId(productCreateDTO.getMerchantId());
|
||||
product.setCategoryId(productCreateDTO.getCategoryId());
|
||||
product.setTitle(productCreateDTO.getTitle());
|
||||
product.setSubTitle(productCreateDTO.getSubTitle());
|
||||
product.setBrand(productCreateDTO.getBrand());
|
||||
product.setCoverImage(productCreateDTO.getCoverImage());
|
||||
product.setCarouselImages(productCreateDTO.getCarouselImages());
|
||||
product.setPriceType(productCreateDTO.getPriceType());
|
||||
product.setSpuPrice(productCreateDTO.getSpuPrice());
|
||||
product.setOriginalPrice(productCreateDTO.getOriginalPrice());
|
||||
product.setUnit(productCreateDTO.getUnit());
|
||||
product.setStatus(productCreateDTO.getStatus());
|
||||
product.setSaleStartTime(productCreateDTO.getSaleStartTime());
|
||||
product.setSaleEndTime(productCreateDTO.getSaleEndTime());
|
||||
product.setDescription(productCreateDTO.getDescription());
|
||||
|
||||
// 2. 保存SPU
|
||||
boolean spuSaved = this.save(product);
|
||||
if (!spuSaved) {
|
||||
throw new RuntimeException("保存商品SPU失败");
|
||||
}
|
||||
|
||||
// 3. 处理SKU
|
||||
if (productCreateDTO.getPriceType() == 1 && CollUtil.isNotEmpty(productCreateDTO.getSkuList())) {
|
||||
// 多SKU模式,保存SKU列表
|
||||
List<MallProductSkuEntity> skuList = productCreateDTO.getSkuList().stream()
|
||||
.map(skuDTO -> {
|
||||
MallProductSkuEntity sku = new MallProductSkuEntity();
|
||||
sku.setProductId(product.getId());
|
||||
sku.setSkuCode(skuDTO.getSkuCode());
|
||||
sku.setSkuName(skuDTO.getSkuName());
|
||||
sku.setPrice(skuDTO.getPrice());
|
||||
sku.setCostPrice(skuDTO.getCostPrice());
|
||||
sku.setStock(skuDTO.getStock());
|
||||
sku.setBarcode(skuDTO.getBarcode());
|
||||
sku.setWeightG(skuDTO.getWeightG());
|
||||
sku.setVolumeCm3(skuDTO.getVolumeCm3());
|
||||
sku.setStatus(skuDTO.getStatus());
|
||||
return sku;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
boolean skuSaved = mallProductSkuService.saveBatch(skuList);
|
||||
if (!skuSaved) {
|
||||
throw new RuntimeException("保存商品SKU失败");
|
||||
}
|
||||
|
||||
// 计算总库存
|
||||
int totalStock = skuList.stream()
|
||||
.mapToInt(sku -> sku.getStock() != null ? sku.getStock() : 0)
|
||||
.sum();
|
||||
product.setTotalStock(totalStock);
|
||||
this.updateById(product);
|
||||
} else if (productCreateDTO.getPriceType() == 2) {
|
||||
// 单价SPU模式,设置总库存为0(或根据业务需求设置)
|
||||
product.setTotalStock(0);
|
||||
this.updateById(product);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateProductWithSkus(MallProductCreateDTO productCreateDTO) {
|
||||
// 业务验证
|
||||
validateProductData(productCreateDTO);
|
||||
|
||||
// 1. 更新SPU
|
||||
MallProductEntity product = this.getById(productCreateDTO.getId());
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
|
||||
// 更新SPU字段
|
||||
product.setMerchantId(productCreateDTO.getMerchantId());
|
||||
product.setCategoryId(productCreateDTO.getCategoryId());
|
||||
product.setTitle(productCreateDTO.getTitle());
|
||||
product.setSubTitle(productCreateDTO.getSubTitle());
|
||||
product.setBrand(productCreateDTO.getBrand());
|
||||
product.setCoverImage(productCreateDTO.getCoverImage());
|
||||
product.setCarouselImages(productCreateDTO.getCarouselImages());
|
||||
product.setPriceType(productCreateDTO.getPriceType());
|
||||
product.setSpuPrice(productCreateDTO.getSpuPrice());
|
||||
product.setOriginalPrice(productCreateDTO.getOriginalPrice());
|
||||
product.setUnit(productCreateDTO.getUnit());
|
||||
product.setStatus(productCreateDTO.getStatus());
|
||||
product.setSaleStartTime(productCreateDTO.getSaleStartTime());
|
||||
product.setSaleEndTime(productCreateDTO.getSaleEndTime());
|
||||
product.setDescription(productCreateDTO.getDescription());
|
||||
|
||||
// 2. 保存SPU
|
||||
boolean spuUpdated = this.updateById(product);
|
||||
if (!spuUpdated) {
|
||||
throw new RuntimeException("更新商品SPU失败");
|
||||
}
|
||||
|
||||
// 3. 处理SKU
|
||||
if (productCreateDTO.getPriceType() == 1 && CollUtil.isNotEmpty(productCreateDTO.getSkuList())) {
|
||||
// 多SKU模式,先删除原有SKU,再保存新的SKU列表
|
||||
LambdaQueryWrapper<MallProductSkuEntity> deleteWrapper = Wrappers.lambdaQuery();
|
||||
deleteWrapper.eq(MallProductSkuEntity::getProductId, product.getId());
|
||||
mallProductSkuService.remove(deleteWrapper);
|
||||
|
||||
List<MallProductSkuEntity> skuList = productCreateDTO.getSkuList().stream()
|
||||
.map(skuDTO -> {
|
||||
MallProductSkuEntity sku = new MallProductSkuEntity();
|
||||
sku.setProductId(product.getId());
|
||||
sku.setSkuCode(skuDTO.getSkuCode());
|
||||
sku.setSkuName(skuDTO.getSkuName());
|
||||
sku.setPrice(skuDTO.getPrice());
|
||||
sku.setCostPrice(skuDTO.getCostPrice());
|
||||
sku.setStock(skuDTO.getStock());
|
||||
sku.setBarcode(skuDTO.getBarcode());
|
||||
sku.setWeightG(skuDTO.getWeightG());
|
||||
sku.setVolumeCm3(skuDTO.getVolumeCm3());
|
||||
sku.setStatus(skuDTO.getStatus());
|
||||
return sku;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
boolean skuSaved = mallProductSkuService.saveBatch(skuList);
|
||||
if (!skuSaved) {
|
||||
throw new RuntimeException("保存商品SKU失败");
|
||||
}
|
||||
|
||||
// 计算总库存
|
||||
int totalStock = skuList.stream()
|
||||
.mapToInt(sku -> sku.getStock() != null ? sku.getStock() : 0)
|
||||
.sum();
|
||||
product.setTotalStock(totalStock);
|
||||
this.updateById(product);
|
||||
} else if (productCreateDTO.getPriceType() == 2) {
|
||||
// 单价SPU模式,删除所有SKU
|
||||
LambdaQueryWrapper<MallProductSkuEntity> deleteWrapper = Wrappers.lambdaQuery();
|
||||
deleteWrapper.eq(MallProductSkuEntity::getProductId, product.getId());
|
||||
mallProductSkuService.remove(deleteWrapper);
|
||||
|
||||
product.setTotalStock(0);
|
||||
this.updateById(product);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MallProductCreateDTO getProductWithSkus(Long id) {
|
||||
// 1. 获取SPU信息
|
||||
MallProductEntity product = this.getById(id);
|
||||
if (product == null) {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
|
||||
// 2. 转换为DTO
|
||||
MallProductCreateDTO dto = new MallProductCreateDTO();
|
||||
dto.setId(product.getId());
|
||||
dto.setMerchantId(product.getMerchantId());
|
||||
dto.setCategoryId(product.getCategoryId());
|
||||
dto.setTitle(product.getTitle());
|
||||
dto.setSubTitle(product.getSubTitle());
|
||||
dto.setBrand(product.getBrand());
|
||||
dto.setCoverImage(product.getCoverImage());
|
||||
dto.setCarouselImages(product.getCarouselImages());
|
||||
dto.setPriceType(product.getPriceType());
|
||||
dto.setSpuPrice(product.getSpuPrice());
|
||||
dto.setOriginalPrice(product.getOriginalPrice());
|
||||
dto.setUnit(product.getUnit());
|
||||
dto.setStatus(product.getStatus());
|
||||
dto.setSaleStartTime(product.getSaleStartTime());
|
||||
dto.setSaleEndTime(product.getSaleEndTime());
|
||||
dto.setDescription(product.getDescription());
|
||||
|
||||
// 3. 获取SKU信息
|
||||
if (product.getPriceType() == 1) {
|
||||
LambdaQueryWrapper<MallProductSkuEntity> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(MallProductSkuEntity::getProductId, id);
|
||||
List<MallProductSkuEntity> skuList = mallProductSkuService.list(wrapper);
|
||||
|
||||
List<MallProductSkuCreateDTO> skuDTOList = skuList.stream()
|
||||
.map(sku -> {
|
||||
MallProductSkuCreateDTO skuDTO = new MallProductSkuCreateDTO();
|
||||
skuDTO.setSkuCode(sku.getSkuCode());
|
||||
skuDTO.setSkuName(sku.getSkuName());
|
||||
skuDTO.setPrice(sku.getPrice());
|
||||
skuDTO.setCostPrice(sku.getCostPrice());
|
||||
skuDTO.setStock(sku.getStock());
|
||||
skuDTO.setBarcode(sku.getBarcode());
|
||||
skuDTO.setWeightG(sku.getWeightG());
|
||||
skuDTO.setVolumeCm3(sku.getVolumeCm3());
|
||||
skuDTO.setStatus(sku.getStatus());
|
||||
return skuDTO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
dto.setSkuList(skuDTOList);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品业务数据验证
|
||||
* @param productCreateDTO 商品创建DTO
|
||||
*/
|
||||
private void validateProductData(MallProductCreateDTO productCreateDTO) {
|
||||
// 多SKU模式验证
|
||||
if (productCreateDTO.getPriceType() == 1) {
|
||||
if (CollUtil.isEmpty(productCreateDTO.getSkuList())) {
|
||||
throw new RuntimeException("多SKU模式下必须提供SKU列表");
|
||||
}
|
||||
// 验证SKU编码唯一性
|
||||
long distinctSkuCodes = productCreateDTO.getSkuList().stream()
|
||||
.map(MallProductSkuCreateDTO::getSkuCode)
|
||||
.distinct()
|
||||
.count();
|
||||
if (distinctSkuCodes != productCreateDTO.getSkuList().size()) {
|
||||
throw new RuntimeException("SKU编码不能重复");
|
||||
}
|
||||
}
|
||||
|
||||
// 单价SPU模式验证
|
||||
if (productCreateDTO.getPriceType() == 2) {
|
||||
if (productCreateDTO.getSpuPrice() == null || productCreateDTO.getSpuPrice().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new RuntimeException("单价SPU模式下必须设置有效的SPU价格");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package com.bsd.pjcloud.supermarket.service;
|
||||
|
||||
import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO;
|
||||
import com.bsd.pjcloud.supermarket.dto.MallProductSkuCreateDTO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 商品服务测试类
|
||||
*
|
||||
* @author zjf
|
||||
* @date 2025-01-27
|
||||
*/
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
public class MallProductServiceTest {
|
||||
|
||||
@Resource
|
||||
private MallProductService mallProductService;
|
||||
|
||||
@Test
|
||||
public void testCreateProductWithSkus() {
|
||||
// 创建测试数据
|
||||
MallProductCreateDTO productCreateDTO = new MallProductCreateDTO();
|
||||
productCreateDTO.setMerchantId(1L);
|
||||
productCreateDTO.setCategoryId(100L);
|
||||
productCreateDTO.setTitle("测试商品");
|
||||
productCreateDTO.setSubTitle("测试副标题");
|
||||
productCreateDTO.setBrand("测试品牌");
|
||||
productCreateDTO.setCoverImage("https://example.com/cover.jpg");
|
||||
productCreateDTO.setCarouselImages("https://example.com/img1.jpg,https://example.com/img2.jpg");
|
||||
productCreateDTO.setPriceType(1); // 多SKU模式
|
||||
productCreateDTO.setOriginalPrice(new BigDecimal("999.00"));
|
||||
productCreateDTO.setUnit("台");
|
||||
productCreateDTO.setStatus(1);
|
||||
productCreateDTO.setSaleStartTime(LocalDateTime.now());
|
||||
productCreateDTO.setSaleEndTime(LocalDateTime.now().plusYears(1));
|
||||
productCreateDTO.setDescription("测试商品描述");
|
||||
productCreateDTO.setDeptId(1L);
|
||||
|
||||
// 创建SKU列表
|
||||
MallProductSkuCreateDTO sku1 = new MallProductSkuCreateDTO();
|
||||
sku1.setSkuCode("TEST-SKU-001");
|
||||
sku1.setSkuName("测试SKU 1");
|
||||
sku1.setPrice(new BigDecimal("599.00"));
|
||||
sku1.setCostPrice(new BigDecimal("400.00"));
|
||||
sku1.setStock(100);
|
||||
sku1.setBarcode("1234567890123");
|
||||
sku1.setWeightG(200);
|
||||
sku1.setVolumeCm3(300);
|
||||
sku1.setStatus(1);
|
||||
sku1.setDeptId(1L);
|
||||
|
||||
MallProductSkuCreateDTO sku2 = new MallProductSkuCreateDTO();
|
||||
sku2.setSkuCode("TEST-SKU-002");
|
||||
sku2.setSkuName("测试SKU 2");
|
||||
sku2.setPrice(new BigDecimal("699.00"));
|
||||
sku2.setCostPrice(new BigDecimal("500.00"));
|
||||
sku2.setStock(50);
|
||||
sku2.setBarcode("1234567890124");
|
||||
sku2.setWeightG(250);
|
||||
sku2.setVolumeCm3(350);
|
||||
sku2.setStatus(1);
|
||||
sku2.setDeptId(1L);
|
||||
|
||||
productCreateDTO.setSkuList(Arrays.asList(sku1, sku2));
|
||||
|
||||
// 执行测试
|
||||
boolean result = mallProductService.createProductWithSkus(productCreateDTO);
|
||||
assert result : "商品创建应该成功";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProductWithSinglePrice() {
|
||||
// 创建测试数据 - 单价SPU模式
|
||||
MallProductCreateDTO productCreateDTO = new MallProductCreateDTO();
|
||||
productCreateDTO.setMerchantId(1L);
|
||||
productCreateDTO.setCategoryId(100L);
|
||||
productCreateDTO.setTitle("测试商品-单价模式");
|
||||
productCreateDTO.setSubTitle("测试副标题");
|
||||
productCreateDTO.setBrand("测试品牌");
|
||||
productCreateDTO.setCoverImage("https://example.com/cover.jpg");
|
||||
productCreateDTO.setCarouselImages("https://example.com/img1.jpg");
|
||||
productCreateDTO.setPriceType(2); // 单价SPU模式
|
||||
productCreateDTO.setSpuPrice(new BigDecimal("599.00"));
|
||||
productCreateDTO.setOriginalPrice(new BigDecimal("999.00"));
|
||||
productCreateDTO.setUnit("台");
|
||||
productCreateDTO.setStatus(1);
|
||||
productCreateDTO.setSaleStartTime(LocalDateTime.now());
|
||||
productCreateDTO.setSaleEndTime(LocalDateTime.now().plusYears(1));
|
||||
productCreateDTO.setDescription("测试商品描述");
|
||||
productCreateDTO.setDeptId(1L);
|
||||
|
||||
// 执行测试
|
||||
boolean result = mallProductService.createProductWithSkus(productCreateDTO);
|
||||
assert result : "商品创建应该成功";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user