mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2025-01-03 17:42:20 +08:00
Merge branch 'develop' of https://gitee.com/youlaitech/youlai-mall into develop
This commit is contained in:
commit
4efad306bf
186
README.md
186
README.md
@ -1,4 +1,4 @@
|
||||
<div align="center">
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/SpringBoot-2.6.3-brightgreen.svg"/>
|
||||
<img src="https://img.shields.io/badge/SpringCloud & Alibaba -2021-green.svg"/>
|
||||
<a src="https://github.com/hxrui" target="_blank">
|
||||
@ -12,25 +12,41 @@
|
||||
<a href="https://gitee.com/youlaiorg" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Author-有来开源组织-orange.svg"/>
|
||||
</a>
|
||||
</div>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>在线预览:</strong><a target="_blank" href="http://www.youlai.tech">www.youlai.tech</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<a target="_blank" href="https://gitee.com/youlaiorg"> Gitee 仓库</a> |
|
||||
<a target="_blank" href="https://gitee.com/youlaitech"> Github 仓库</a> |
|
||||
<a target="_blank" href="https://www.cnblogs.com/haoxianrui/"> 博客主页</a> |
|
||||
<a target="_blank" href="http://youlaitech.gitee.io/youlai-mall"> 官方文档</a>
|
||||
</div>
|
||||
|
||||
# 📚️ 项目介绍
|
||||
|
||||
### 🗀 项目简介
|
||||
<p align="center">
|
||||
<strong>开源地址:</strong> <a target="_blank" href='https://github.com/hxrui'>Github</a> | <a target="_blank" href='https://gitee.com/haoxr'>Gitee</a> | <a target="_blank" href='https://gitcode.net/youlai'>GitCode</a>
|
||||
</p>
|
||||
|
||||
[youlai-mall](https://gitee.com/haoxr) 是基于Spring Boot 2.6、Spring Cloud 2021 & Alibaba
|
||||
2021、Vue3、Element-Plus、uni-app等主流技术栈构建的一整套全栈开源商城项目, 涉及 [后端微服务](https://gitee.com/youlaitech/youlai-mall)
|
||||
、 [前端管理](https://gitee.com/youlaitech/youlai-mall-admin) 、 [微信小程序](https://gitee.com/youlaitech/youlai-mall-weapp)
|
||||
和 [APP应用](https://gitee.com/youlaitech/youlai-mall-weapp) 等多端的开发。
|
||||
|
||||
### 🗀 项目特色
|
||||
|
||||
<p align="center">
|
||||
<strong>博客主页:</strong><a target="_blank" href="https://www.cnblogs.com/haoxianrui/"> https://www.cnblogs.com/haoxianrui</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p align="center">
|
||||
<strong>开发者文档:</strong><a target="_blank" href="http://youlaitech.gitee.io/youlai-mall">http://youlaitech.gitee.io/youlai-mall</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## 🈶 项目介绍
|
||||
|
||||
|
||||
### 🗁 项目简介
|
||||
|
||||
[youlai-mall](https://gitee.com/haoxr) 是基于Spring Boot 2.6、Spring Cloud 2021 & Alibaba 2021、Vue3、Element-Plus、uni-app等主流技术栈构建的一整套全栈开源商城项目, 涉及 [后端微服务](https://gitee.com/youlaitech/youlai-mall)、 [前端管理](https://gitee.com/youlaitech/youlai-mall-admin) 、 [微信小程序](https://gitee.com/youlaitech/youlai-mall-weapp)和 [APP应用](https://gitee.com/youlaitech/youlai-mall-weapp) 等多端的开发。
|
||||
|
||||
|
||||
### 🗁 项目特色
|
||||
|
||||
|
||||
- 项目使用皆是当前主流的技术栈,无过度自定义封装,易理解学习和二次扩展;
|
||||
- SpringBoot 2.6、SpringCloud 2021 & Alibaba 2021 一站式微服务开箱即用的解决方案;
|
||||
@ -38,74 +54,83 @@
|
||||
- 移动端采用终极跨平台解决方案 uni-app, 一套代码编译iOS、Android、H5和小程序等多个平台;
|
||||
- Jenkins、K8s、Docker实现微服务持续集成与交付(CI/CD)。
|
||||
|
||||
### 🗀 在线预览
|
||||
|
||||
| Vue3| Vue2 | H5| 微信小程序|
|
||||
| --- | --- | --- |--- |
|
||||
| [www.youlai.tech](http://www.youlai.tech) | [www.youlai.tech:9527](http://www.youlai.tech:9527) | [www.youlai.tech:81](http://www.youlai.tech:9527)| 加我微信申请体验|
|
||||
### 🗁 在线预览
|
||||
|
||||
|
||||
### 🗀 预览截图
|
||||
| | 地址 |
|
||||
| ------------- | --------------------------------------------------- |
|
||||
| 管理前端 Vue3 | [www.youlai.tech](http://www.youlai.tech) |
|
||||
| 管理前端 Vue2 | [www.youlai.tech:9527](http://www.youlai.tech:9527) |
|
||||
| H5 移动端 | [www.youlai.tech:81](http://www.youlai.tech:9527) |
|
||||
|
||||
|
||||
### 🗁 预览截图
|
||||
|
||||
|
||||
| 「App」Spring Security OAuth2 手机短信验证码模式 | 「小程序」Spring Security OAuth2 微信授权模式 |
|
||||
| ------------------------------------------------------------ |----------------------------------------------------------- |
|
||||
| ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| <img src="https://gitee.com/haoxr/image/raw/master/default/smscode%20(1).gif" width="100%" height="400px"/> | <img src="https://gitee.com/haoxr/image/raw/master/default/wechatlogin.gif" width="100%" height="400px"/> |
|
||||
| **「管理前端」Spring Security OAuth2 密码模式** | **「管理前端」Spring Security OAuth2 验证码模式** |
|
||||
| <img src="https://gitee.com/haoxr/image/raw/master/default/password.gif" width="100%" height="400px"/> | <img src="https://gitee.com/haoxr/image/raw/master/default/captcha.gif" width="100%" height="400px"/> |
|
||||
|
||||
# ⛺ 源码地址
|
||||
|
||||
| 名称 | Gitee | Github |
|
||||
|---|---|---|
|
||||
| 开源组织 | [有来开源组织](https://gitee.com/youlaiorg) |[有来开源组织](https://github.com/youlaitech) |
|
||||
| 后端 | [youlai-mall](https://gitee.com/youlaiorg/youlai-mall) | [youlai-mall](https://github.com/youlaitech/youlai-mall) |
|
||||
| 管理前端 |[mall-admin-web](https://gitee.com/youlaiorg/mall-admin-web) | [mall-admin-web](https://github.com/youlaitech/mall-admin-web) |
|
||||
| 小程序/H5/移动端 | [mall-app](https://gitee.com/youlaiorg/mall-app) | [mall-app](https://github.com/youlaitech/mall-app) |
|
||||
## 🔱 源码地址
|
||||
|
||||
# 🚤 项目启动
|
||||
|
||||
### 🗀 后端启动
|
||||
| | Gitee | Github | GitCode |
|
||||
| ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| 开源组织 | [有来开源组织](https://gitee.com/youlaiorg) | [有来开源组织](https://github.com/youlaitech) | [有来开源组织](https://gitcode.net/youlai) |
|
||||
| 后端 | [youlai-mall](https://gitee.com/youlaiorg/youlai-mall) | [youlai-mall](https://github.com/youlaitech/youlai-mall) | [youlai-mall](https://gitcode.net/youlai/youlai-mall) |
|
||||
| 管理前端 | [mall-admin-web](https://gitee.com/youlaiorg/mall-admin-web) | [mall-admin-web](https://github.com/youlaitech/mall-admin-web) | [mall-admin-web](https://github.com/youlaitech/mall-admin-web) |
|
||||
| 小程序/H5/移动端 | [mall-app](https://gitee.com/youlaiorg/mall-app) | [mall-app](https://github.com/youlaitech/mall-app) | [mall-app](https://gitcode.net/youlai/mall-app) |
|
||||
|
||||
|
||||
## 🚤 项目启动
|
||||
|
||||
### 🗁 后端启动
|
||||
|
||||
|
||||
> `极速启动` 是方便快速启动查看效果的启动方式,其中的数据库和Redis等中间件使用的是有来提供的云环境,切勿修改数据,有时间条件建议`本地启动`。
|
||||
|
||||
#### 一. 极速启动
|
||||
|
||||
|
||||
#### 1️⃣ 极速启动
|
||||
|
||||
|
||||
1. **启动 Nacos**
|
||||
|
||||
IDEA 打开命令行终端 Terminal,输入 `cd middleware/nacos/bin` 切换到 Nacos 的 bin 目录,执行 `startup -m standalone` 启动 Nacos 服务。
|
||||
|
||||
2. **导入Nacos配置**
|
||||
- IDEA 打开命令行终端 Terminal,输入 `cd middleware/nacos/bin` 切换到 Nacos 的 bin 目录,执行 `startup -m standalone` 启动 Nacos 服务。
|
||||
|
||||
打开浏览器,地址栏输入 Nacos 管控台的地址 [ http://localhost:8848/nacos]( http://localhost:8848/nacos) ;
|
||||
|
||||
输入用户名/密码:nacos/nacos ;
|
||||
2. **服务启动**
|
||||
|
||||
进入管控台,点击左侧菜单 `配置管理` → `配置列表` 进入列表页面,点击 `导入配置` 选择项目中的 `doc/nacos/DEFAULT_GROUP.zip` 文件 。
|
||||
- `youlai-gateway` 模块的启动类 GatewayApplication 启动网关;
|
||||
|
||||
3. **服务启动**
|
||||
- `youlai-auth` 模块的启动类 AuthApplication 启动认证中心;
|
||||
|
||||
进入 `youlai-gateway` 模块的启动类 GatewayApplication 启动网关;
|
||||
- `youlai-admin` → `admin-boot` 模块的启动类 AdminApplication 启动系统服务;
|
||||
|
||||
进入 `youlai-auth` 模块的启动类 AuthApplication 启动认证授权中心;
|
||||
- 至此完成基础服务的启动,商城服务按需启动,启动方式和 `youlai-admin` 一致;
|
||||
|
||||
进入 `youlai-admin` → `admin-boot` 模块的启动类 AdminApplication 启动系统服务;
|
||||
- 访问接口文档地址测试: [http://localhost:9999/doc.html](http://localhost:9999/doc.html)
|
||||
|
||||
至此已完成基础服务的启动,商城服务按需启动,启动方式和 `youlai-admin` 一致。
|
||||
|
||||
4. **启动测试**
|
||||
#### 2️⃣ 本地启动
|
||||
|
||||
访问接口文档地址测试 [http://localhost:9999/doc.html](http://localhost:9999/doc.html)
|
||||
|
||||
#### 二. 本地启动
|
||||
|
||||
1. **中间件安装(🔴必装 ⚪可选)**
|
||||
|
||||
> 为了避免数据和线上环境冲突,MySQL 和 Redis 必装,不安装可默认使用有来线上环境
|
||||
|
||||
- 🔴MySQL 安装
|
||||
- 🔴Redis 安装
|
||||
- ⚪RabbitMQ
|
||||
- ⚪Seata 安装
|
||||
- ⚪Sentinel 安装
|
||||
|
||||
|
||||
2. **数据库创建和数据初始化**
|
||||
|
||||
- **系统数据库**
|
||||
@ -118,23 +143,22 @@
|
||||
|
||||
- **Nacos 数据库**
|
||||
|
||||
创建名为 nacos 的数据库;
|
||||
|
||||
执行 `middleware/nacos/conf/nacos-mysql.sql` 脚本完成 Nacos 数据库初始化。
|
||||
|
||||
创建名为 `nacos` 的数据库,执行 `middleware/nacos/conf/nacos-mysql.sql` 脚本完成 Nacos 数据库初始化。
|
||||
|
||||
3. **Nacos 配置和启动**
|
||||
|
||||
1. **Nacos 配置持久化至 MySQL**
|
||||
> Nacos 默认配置持久化到内嵌的Derby数据库,开发无特殊情况可使用默认配置,如需持久化配置到MySQL,完成下面配置修改即可。
|
||||
|
||||
进入 `middleware/nacos/conf/application.properties` 文件修改 Nacos 配置的数据连接,需要修改配置如下:
|
||||
1. **Nacos 配置持久化至 MySQL**
|
||||
|
||||
进入项目的 `middleware/nacos/conf/application.properties` 文件修改 Nacos 配置的数据连接,需要修改配置如下:
|
||||
|
||||
```properties
|
||||
spring.datasource.platform=mysql
|
||||
db.num=1
|
||||
db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
|
||||
db.user.0=root
|
||||
db.password.0=123456
|
||||
```
|
||||
|
||||
2. **启动Nacos**
|
||||
|
||||
@ -142,39 +166,43 @@
|
||||
|
||||
3. **导入Nacos配置**
|
||||
|
||||
打开浏览器,地址栏输入 Nacos 管控台的地址 [ http://localhost:8848/nacos]( http://localhost:8848/nacos) ;
|
||||
打开浏览器,地址栏输入 Nacos 控制台的地址 [ http://localhost:8848/nacos]( http://localhost:8848/nacos) ;
|
||||
|
||||
输入用户名/密码:nacos/nacos ;
|
||||
|
||||
进入管控台,点击左侧菜单 `配置管理` → `配置列表` 进入列表页面,点击 `导入配置` 选择项目中的 `doc/nacos/DEFAULT_GROUP.zip` 文件 ;
|
||||
进入控制台,点击左侧菜单 `配置管理` → `配置列表` 进入列表页面,点击 `导入配置` 选择项目中的 `doc/nacos/DEFAULT_GROUP.zip` 文件 。
|
||||
|
||||
4. **修改Nacos配置**
|
||||
|
||||
进入共享配置 `youlai-common.yaml` ,修改 MySQL、Redis、RabbitMQ等中间件信息为您自己本地环境,默认为有来云环境。
|
||||
在 Nacos 控制台配置列表选择共享配置 `youlai-common.yaml` 进行编辑,修改 MySQL、Redis、RabbitMQ等中间件信息为您自己本地环境,默认有来 线上环境。
|
||||
|
||||
|
||||
4. **服务启动**
|
||||
|
||||
进入 `youlai-gateway` 模块的启动类 GatewayApplication 启动网关;
|
||||
- 进入 `youlai-gateway` 模块的启动类 GatewayApplication 启动网关;
|
||||
|
||||
进入 `youlai-auth` 模块的启动类 AuthApplication 启动认证授权中心;
|
||||
- 进入 `youlai-auth` 模块的启动类 AuthApplication 启动认证授权中心;
|
||||
|
||||
进入 `youlai-admin` → `admin-boot` 模块的启动类 AdminApplication 启动系统服务;
|
||||
- 进入 `youlai-admin` → `admin-boot` 模块的启动类 AdminApplication 启动系统服务;
|
||||
|
||||
至此已完成基础服务的启动,商城服务按需启动,启动方式和 `youlai-admin` 一致。
|
||||
- 至此完成基础服务的启动,商城服务按需启动,启动方式和 `youlai-admin` 一致;
|
||||
|
||||
5. **启动测试**
|
||||
- 访问接口文档地址测试: [http://localhost:9999/doc.html](http://localhost:9999/doc.html))
|
||||
|
||||
访问接口文档地址测试 [http://localhost:9999/doc.html](http://localhost:9999/doc.html)
|
||||
|
||||
### 🗀 管理前端启动
|
||||
|
||||
|
||||
### 🗁 管理前端启动
|
||||
|
||||
|
||||
1. 本机安装 Node 环境
|
||||
2. npm install
|
||||
3. npm run dev
|
||||
4. 访问 http://localhost:9527
|
||||
|
||||
### 🗀 微信小程序启动
|
||||
|
||||
### 🗁 微信小程序启动
|
||||
|
||||
|
||||
1. 下载 `HBuilder X` 和 `微信开发者工具` ;
|
||||
2. 导入 [mall-app](https://gitee.com/youlaitech/youlai-mall-weapp) 源码至 `HBuilder X` ;
|
||||
@ -184,15 +212,41 @@
|
||||
6. Nacos控制台替换 `youlai-auth` 配置中的微信小程序 AppID 和 AppSecret 为自己申请的小程序 ;
|
||||
7. `Hbuilder X` 工具栏点击 `运行` -> `运行到小程序模拟器` -> `微信开发者工具`。
|
||||
|
||||
### 🗀 H5/移动端启动
|
||||
|
||||
### 🗁 H5/移动端启动
|
||||
|
||||
|
||||
1. 下载 `HBuilder X` ;
|
||||
2. 导入 [mall-app](https://gitee.com/youlaitech/youlai-mall-weapp) 源码至 `HBuilder X`;
|
||||
3. `Hbuilder X` 工具栏点击 `运行` -> `运行到内置浏览器` 。
|
||||
|
||||
# 🚀 联系信息
|
||||
|
||||
## 💹 趋势统计
|
||||
|
||||
|
||||
- Gitee
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href='https://whnb.wang/stars/youlaitech/youlai-mall'><img src="https://whnb.wang/stars/youlaitech/youlai-mall"></a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
- Github
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a target="_blank" href='https://starchart.cc/hxrui/youlai-mall'><img src="https://starchart.cc/hxrui/youlai-mall.svg"></a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## 💬 联系信息
|
||||
|
||||
|
||||
> 欢迎添加开发者微信,备注「有来」进群
|
||||
|
||||
|
||||
| ![](https://gitee.com/haoxr/image/raw/master/hxr.jpg) | ![](https://gitee.com/haoxr/image/raw/master/default/jialin.jpg) | ![](https://gitee.com/haoxr/image/raw/master/default/ba695a5e70410a066b7052c5dc9db5c.jpg) |
|
||||
|---|---|---|
|
||||
| ----------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
@ -48,24 +48,16 @@
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
|
||||
<exclusions>
|
||||
<!-- 排除依赖 指定版本和服务器端一致 -->
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
<version>${seata.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
@ -1,8 +1,7 @@
|
||||
package com.youlai.mall.oms;
|
||||
|
||||
import com.youlai.mall.pms.api.GoodsFeignClient;
|
||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||
import com.youlai.mall.ums.api.MemberFeignClient;
|
||||
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
@ -12,8 +11,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients(basePackageClasses = { MemberFeignClient.class, GoodsFeignClient.class})
|
||||
@EnableRabbit
|
||||
@EnableFeignClients(basePackageClasses = { MemberFeignClient.class, SkuFeignClient.class})
|
||||
@EnableTransactionManagement
|
||||
public class OmsApplication {
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.youlai.mall.oms.config;
|
||||
|
||||
import com.youlai.common.factory.NamedThreadFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@ -10,12 +11,15 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/13
|
||||
*/
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Bean
|
||||
public ThreadPoolExecutor threadPoolExecutor() {
|
||||
return new ThreadPoolExecutor(50,500,30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10000));
|
||||
return new ThreadPoolExecutor(50, 500, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10000), new NamedThreadFactory("订单线程"));
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.pojo.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.service.IOrderItemService;
|
||||
import com.youlai.mall.oms.service.IOrderService;
|
||||
import com.youlai.mall.ums.api.MemberFeignClient;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberDTO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
@ -31,14 +29,13 @@ import java.util.Optional;
|
||||
* @date 2020-12-30 22:31:10
|
||||
*/
|
||||
@Api(tags = "「系统端」订单管理")
|
||||
@RestController("adminOrderController")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/orders")
|
||||
@RequiredArgsConstructor
|
||||
public class OrderController {
|
||||
public class OmsOrderController {
|
||||
|
||||
private final IOrderService orderService;
|
||||
private final IOrderItemService orderItemService;
|
||||
private final MemberFeignClient memberFeignClient;
|
||||
|
||||
@ApiOperation("订单列表")
|
||||
@GetMapping
|
||||
@ -47,7 +44,6 @@ public class OrderController {
|
||||
return Result.success(result.getRecords(), result.getTotal());
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "订单详情")
|
||||
@GetMapping("/{orderId}")
|
||||
public Result getOrderDetail(
|
||||
@ -63,10 +59,7 @@ public class OrderController {
|
||||
);
|
||||
orderItems = Optional.ofNullable(orderItems).orElse(new ArrayList<>());
|
||||
|
||||
// 会员明细
|
||||
Result<MemberDTO> result = memberFeignClient.getUserById(order.getMemberId());
|
||||
MemberDTO member = result.getData();
|
||||
orderDTO.setOrder(order).setOrderItems(orderItems).setMember(member);
|
||||
orderDTO.setOrder(order).setOrderItems(orderItems);
|
||||
return Result.success(orderDTO);
|
||||
}
|
||||
|
@ -2,14 +2,13 @@ package com.youlai.mall.oms.controller.app;
|
||||
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.service.ICartService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -24,18 +23,17 @@ import java.util.List;
|
||||
@Api(tags = "「移动端」购物车管理")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/carts")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
@RequiredArgsConstructor
|
||||
public class CartController {
|
||||
|
||||
private ICartService cartService;
|
||||
private final ICartService cartService;
|
||||
|
||||
@ApiOperation(value = "查询购物车")
|
||||
@GetMapping
|
||||
@ApiOperationSupport(order = 1)
|
||||
public <T> Result<T> getCart() {
|
||||
try {
|
||||
Long memberId = JwtUtils.getUserId();
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
List<CartItemDTO> result = cartService.listCartItemByMemberId(memberId);
|
||||
return Result.success((T) result);
|
||||
} catch (Exception e) {
|
||||
|
@ -4,11 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.oms.enums.PayTypeEnum;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.pojo.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.pojo.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
|
||||
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
|
||||
@ -18,12 +18,10 @@ import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
@ -32,20 +30,19 @@ import javax.validation.Valid;
|
||||
@Api(tags = "「移动端」订单管理")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/orders")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class OrderController {
|
||||
|
||||
final IOrderService orderService;
|
||||
|
||||
@ApiOperation("订单列表")
|
||||
@ApiOperation("分页列表")
|
||||
@GetMapping
|
||||
public Result listOrdersWithPage(OrderPageQuery queryParams) {
|
||||
IPage<OmsOrder> result = orderService.page(
|
||||
new Page<>(queryParams.getPageNum(), queryParams.getPageSize()),
|
||||
new LambdaQueryWrapper<OmsOrder>()
|
||||
.eq(OmsOrder::getStatus, queryParams.getStatus())
|
||||
.eq(OmsOrder::getMemberId, JwtUtils.getUserId())
|
||||
.eq(queryParams.getStatus() != null, OmsOrder::getStatus, queryParams.getStatus())
|
||||
.eq(OmsOrder::getMemberId, MemberUtils.getMemberId())
|
||||
);
|
||||
return Result.success(result.getRecords(), result.getTotal());
|
||||
}
|
||||
@ -59,8 +56,8 @@ public class OrderController {
|
||||
|
||||
@ApiOperation("订单提交")
|
||||
@PostMapping("/_submit")
|
||||
public Result submit(@Valid @RequestBody OrderSubmitDTO orderSubmitDTO) {
|
||||
OrderSubmitVO result = orderService.submit(orderSubmitDTO);
|
||||
public Result submit(@Valid @RequestBody OrderSubmitForm orderSubmitForm) {
|
||||
OrderSubmitVO result = orderService.submit(orderSubmitForm);
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,30 @@
|
||||
package com.youlai.mall.oms.listener;
|
||||
|
||||
import com.rabbitmq.client.Channel;
|
||||
import com.youlai.mall.oms.service.IOrderItemService;
|
||||
import com.youlai.mall.oms.service.IOrderService;
|
||||
import com.youlai.mall.pms.api.StockFeignClient;
|
||||
import lombok.AllArgsConstructor;
|
||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author hxr
|
||||
* @date 2021-03-16
|
||||
* 订单超时取消
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2021/3/16
|
||||
*/
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class OrderListener {
|
||||
|
||||
private final IOrderService orderService;
|
||||
private final StockFeignClient stockFeignClient;
|
||||
private final SkuFeignClient skuFeignClient;
|
||||
|
||||
/**
|
||||
* 订单超时未支付,关闭订单,释放库存
|
||||
@ -36,7 +35,7 @@ public class OrderListener {
|
||||
try {
|
||||
if (orderService.closeOrder(orderToken)) {
|
||||
log.info("=======================关闭订单成功,开始释放已锁定的库存=======================");
|
||||
stockFeignClient.unlockStock(orderToken);
|
||||
skuFeignClient.unlockStock(orderToken);
|
||||
} else {
|
||||
log.info("=======================关单失败,订单状态已处理,手动确认消息处理完毕=======================");
|
||||
// basicAck(tag,multiple),multiple为true开启批量确认,小于tag值队列中未被消费的消息一次性确认
|
||||
|
@ -7,40 +7,47 @@ import java.io.Serializable;
|
||||
/**
|
||||
* 购物车商品传输层实体
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@ToString
|
||||
@Builder
|
||||
@Data
|
||||
public class CartItemDTO implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* 商品库存ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 商品库存单元名称
|
||||
* 商品库存名称
|
||||
*/
|
||||
private String skuName;
|
||||
|
||||
/**
|
||||
* 商品库存单元编码
|
||||
* 商品编码
|
||||
*/
|
||||
private String skuSn;
|
||||
|
||||
/**
|
||||
* 商品库存单元图片
|
||||
* 商品图片
|
||||
*/
|
||||
private String picUrl;
|
||||
|
||||
private Integer count; // 商品数量
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
|
||||
/**
|
||||
* 加入购物车价格,因会变动,不能作为订单计算因子,订单验价时需重新获取商品价格即可
|
||||
* 加入购物车时价格,因会变动,不能作为订单计算因子,订单提交时需验价
|
||||
*/
|
||||
private Long price;
|
||||
|
||||
/**
|
||||
* 优惠券金额
|
||||
*/
|
||||
private Long coupon;
|
||||
|
||||
/**
|
||||
* 是否选中
|
||||
*/
|
||||
private Boolean checked;
|
||||
|
||||
/**
|
||||
@ -51,6 +58,6 @@ public class CartItemDTO implements Serializable {
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String goodsName;
|
||||
private String spuName;
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package com.youlai.mall.oms.pojo.dto;
|
||||
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberDTO;
|
||||
import com.youlai.mall.ums.dto.MemberDTO;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
|
@ -1,23 +1,45 @@
|
||||
package com.youlai.mall.oms.pojo.dto;
|
||||
|
||||
import com.youlai.common.base.BaseVO;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 订单商品
|
||||
* 订单商品明细
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OrderItemDTO extends BaseVO {
|
||||
public class OrderItemDTO {
|
||||
|
||||
/**
|
||||
* 商品库存单元ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* SKU编码
|
||||
*/
|
||||
private String skuSn;
|
||||
|
||||
/**
|
||||
* SKU名称
|
||||
*/
|
||||
private String skuName;
|
||||
private String skuCode;
|
||||
private Integer count;
|
||||
private String pic;
|
||||
|
||||
/**
|
||||
* 商品图片地址
|
||||
*/
|
||||
private String picUrl;
|
||||
|
||||
/**
|
||||
* 商品价格
|
||||
*/
|
||||
private Long price;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String spuName;
|
||||
|
||||
/**
|
||||
* 订单商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
package com.youlai.mall.oms.pojo.dto;
|
||||
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @desc 订单提交
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/16
|
||||
*/
|
||||
@Data
|
||||
public class OrderSubmitDTO {
|
||||
|
||||
// 提交订单确认页面签发的令牌
|
||||
private String orderToken;
|
||||
|
||||
private List<OrderItemDTO> orderItems;
|
||||
|
||||
// 验价前台传值
|
||||
private Long totalPrice;
|
||||
|
||||
// 收货地址
|
||||
private UmsAddress deliveryAddress;
|
||||
|
||||
@Size(max = 500, message = "订单备注长度不能超过500")
|
||||
private String remark;
|
||||
|
||||
private String couponId;
|
||||
|
||||
private Long payAmount;
|
||||
|
||||
}
|
@ -3,14 +3,11 @@ package com.youlai.mall.oms.pojo.entity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 订单商品信息表
|
||||
* 订单明细表
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
@ -18,77 +15,53 @@ import lombok.experimental.Accessors;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class OmsOrderItem extends BaseEntity {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* order_id
|
||||
* 订单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 商品sku id
|
||||
* 商品ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 商品sku编号
|
||||
*/
|
||||
private String skuCode;
|
||||
|
||||
private String skuSn;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String skuName;
|
||||
|
||||
/**
|
||||
* 商品sku图片
|
||||
*/
|
||||
private String skuPic;
|
||||
private String picUrl;
|
||||
|
||||
/**
|
||||
* 商品sku价格(分)
|
||||
* 商品单价(单位:分)
|
||||
*/
|
||||
private Long skuPrice;
|
||||
private Long price;
|
||||
|
||||
/**
|
||||
* 商品购买的数量
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer skuQuantity;
|
||||
private Integer count;
|
||||
|
||||
/**
|
||||
* 商品sku总价格(分)
|
||||
* 商品总金额(单位:分)
|
||||
*/
|
||||
private Long skuTotalPrice;
|
||||
private Long totalAmount;
|
||||
|
||||
/**
|
||||
* spu_id
|
||||
*/
|
||||
private Long spuId;
|
||||
/**
|
||||
* spu_name
|
||||
*/
|
||||
private String spuName;
|
||||
/**
|
||||
* spu_pic
|
||||
*/
|
||||
private String spuPic;
|
||||
/**
|
||||
* 品牌id
|
||||
*/
|
||||
private Long brandId;
|
||||
/**
|
||||
* 品牌名称
|
||||
*/
|
||||
private String brandName;
|
||||
/**
|
||||
* 商品分类id
|
||||
*/
|
||||
private Long categoryId;
|
||||
/**
|
||||
* 商品分类名称
|
||||
*/
|
||||
private String categoryName;
|
||||
/**
|
||||
* 逻辑删除【0->正常;1->已删除】
|
||||
* 逻辑删除(0:正常;1:已删除)
|
||||
*/
|
||||
private Integer deleted;
|
||||
|
||||
|
@ -0,0 +1,58 @@
|
||||
package com.youlai.mall.oms.pojo.form;
|
||||
|
||||
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 订单提交表单对象
|
||||
*
|
||||
* @author huawei
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/16
|
||||
*/
|
||||
@Data
|
||||
public class OrderSubmitForm {
|
||||
|
||||
/**
|
||||
* 提交订单确认页面签发的令牌
|
||||
*/
|
||||
private String orderToken;
|
||||
|
||||
/**
|
||||
* 订单总金额-用于验价(单位:分)
|
||||
*/
|
||||
private Long totalAmount;
|
||||
|
||||
|
||||
/**
|
||||
* 支付金额(单位:分)
|
||||
*/
|
||||
private Long payAmount;
|
||||
|
||||
/**
|
||||
* 订单的商品明细
|
||||
*/
|
||||
private List<OrderItemDTO> orderItems;
|
||||
|
||||
// 收货地址
|
||||
|
||||
|
||||
@Size(max = 500, message = "订单备注长度不能超过500")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private String couponId;
|
||||
|
||||
|
||||
/**
|
||||
* 收获地址
|
||||
*/
|
||||
private MemberAddressDTO deliveryAddress;
|
||||
|
||||
}
|
@ -2,19 +2,25 @@ package com.youlai.mall.oms.pojo.vo;
|
||||
|
||||
import com.youlai.common.base.BaseVO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ApiModel("订单确认视图层对象")
|
||||
@Data
|
||||
public class OrderConfirmVO extends BaseVO {
|
||||
public class OrderConfirmVO {
|
||||
|
||||
@ApiModelProperty("订单token")
|
||||
private String orderToken;
|
||||
|
||||
@ApiModelProperty("订单明细")
|
||||
private List<OrderItemDTO> orderItems;
|
||||
|
||||
private List<UmsAddress> addresses;
|
||||
@ApiModelProperty("会员收获地址列表")
|
||||
private List<MemberAddressDTO> addresses;
|
||||
|
||||
}
|
||||
|
@ -1,16 +1,17 @@
|
||||
package com.youlai.mall.oms.pojo.vo;
|
||||
|
||||
import com.youlai.common.base.BaseVO;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 订单创建响应视图对象
|
||||
*
|
||||
* @author huawei
|
||||
* @desc 订单创建响应结果VO
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/21
|
||||
*/
|
||||
@Data
|
||||
public class OrderSubmitVO extends BaseVO {
|
||||
public class OrderSubmitVO {
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.youlai.mall.oms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
|
||||
@ -12,7 +11,7 @@ import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
|
||||
import com.youlai.mall.oms.pojo.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
|
||||
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||
import com.youlai.mall.oms.pojo.form.OrderSubmitForm;
|
||||
|
||||
/**
|
||||
* 订单业务接口
|
||||
@ -31,12 +30,7 @@ public interface IOrderService extends IService<OmsOrder> {
|
||||
/**
|
||||
* 订单提交
|
||||
*/
|
||||
OrderSubmitVO submit(OrderSubmitDTO orderSubmitDTO) ;
|
||||
|
||||
/**
|
||||
* 订单提交
|
||||
*/
|
||||
OrderSubmitVO submitTcc(OrderSubmitDTO orderSubmitDTO) ;
|
||||
OrderSubmitVO submit(OrderSubmitForm orderSubmitForm) ;
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
|
@ -1,16 +1,20 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.oms.constant.OmsConstants;
|
||||
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.service.ICartService;
|
||||
import com.youlai.mall.pms.api.GoodsFeignClient;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.beanutils.BeanUtilsBean;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.data.redis.core.BoundHashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -35,7 +39,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
public class CartServiceImpl implements ICartService {
|
||||
|
||||
private RedisTemplate redisTemplate;
|
||||
private GoodsFeignClient skuFeignService;
|
||||
private SkuFeignClient skuFeignService;
|
||||
|
||||
@Override
|
||||
public List<CartItemDTO> listCartItemByMemberId(Long memberId) {
|
||||
@ -49,7 +53,7 @@ public class CartServiceImpl implements ICartService {
|
||||
*/
|
||||
@Override
|
||||
public boolean deleteCart() {
|
||||
String key = OmsConstants.CART_PREFIX + JwtUtils.getUserId();
|
||||
String key = OmsConstants.CART_PREFIX + MemberUtils.getMemberId();
|
||||
redisTemplate.delete(key);
|
||||
return true;
|
||||
}
|
||||
@ -61,7 +65,7 @@ public class CartServiceImpl implements ICartService {
|
||||
public boolean addCartItem(Long skuId) {
|
||||
Long memberId;
|
||||
try {
|
||||
memberId = JwtUtils.getUserId();
|
||||
memberId = MemberUtils.getMemberId();
|
||||
} catch (Exception e) {
|
||||
throw new BizException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
@ -80,22 +84,16 @@ public class CartServiceImpl implements ICartService {
|
||||
// 购物车不存在该商品,添加商品至购物车
|
||||
cartItem = new CartItemDTO();
|
||||
CompletableFuture<Void> cartItemCompletableFuture = CompletableFuture.runAsync(() -> {
|
||||
SkuDTO sku = skuFeignService.getSkuById(skuId).getData();
|
||||
if (sku != null) {
|
||||
cartItem.setSkuId(sku.getId());
|
||||
SkuInfoDTO skuInfo = skuFeignService.getSkuInfo(skuId).getData();
|
||||
if (skuInfo != null) {
|
||||
BeanUtil.copyProperties(skuInfo,cartItem);
|
||||
cartItem.setCount(1);
|
||||
cartItem.setPrice(sku.getPrice());
|
||||
cartItem.setPicUrl(sku.getPicUrl());
|
||||
cartItem.setSkuName(sku.getName());
|
||||
cartItem.setStock(sku.getStock());
|
||||
cartItem.setSkuSn(sku.getSn());
|
||||
cartItem.setGoodsName(sku.getGoodsName());
|
||||
cartItem.setChecked(true);
|
||||
}
|
||||
});
|
||||
CompletableFuture.allOf(cartItemCompletableFuture).join();
|
||||
|
||||
Assert.isTrue(cartItem.getSkuId() != null && cartItem.getSkuId() > 0,"商品不存在");
|
||||
Assert.isTrue(cartItem.getSkuId() != null,"商品不存在");
|
||||
cartHashOperations.put(hKey, cartItem);
|
||||
return true;
|
||||
}
|
||||
@ -107,7 +105,7 @@ public class CartServiceImpl implements ICartService {
|
||||
public boolean updateCartItem(CartItemDTO cartItem) {
|
||||
Long memberId;
|
||||
try {
|
||||
memberId = JwtUtils.getUserId();
|
||||
memberId = MemberUtils.getMemberId();
|
||||
} catch (Exception e) {
|
||||
throw new BizException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
@ -133,7 +131,7 @@ public class CartServiceImpl implements ICartService {
|
||||
public boolean removeCartItem(Long skuId) {
|
||||
Long memberId;
|
||||
try {
|
||||
memberId = JwtUtils.getUserId();
|
||||
memberId = MemberUtils.getMemberId();
|
||||
} catch (Exception e) {
|
||||
throw new BizException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
@ -151,7 +149,7 @@ public class CartServiceImpl implements ICartService {
|
||||
public boolean checkAll(boolean checked) {
|
||||
Long memberId;
|
||||
try {
|
||||
memberId = JwtUtils.getUserId();
|
||||
memberId = MemberUtils.getMemberId();
|
||||
} catch (Exception e) {
|
||||
throw new BizException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
@ -174,7 +172,7 @@ public class CartServiceImpl implements ICartService {
|
||||
public boolean removeCheckedItem() {
|
||||
Long memberId;
|
||||
try {
|
||||
memberId = JwtUtils.getUserId();
|
||||
memberId = MemberUtils.getMemberId();
|
||||
} catch (Exception e) {
|
||||
throw new BizException(ResultCode.TOKEN_INVALID_OR_EXPIRED);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.oms.mapper.OrderLogMapper;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrderLog;
|
||||
import com.youlai.mall.oms.service.IOrderLogService;
|
||||
@ -24,8 +24,8 @@ public class OrderLogServiceImpl extends ServiceImpl<OrderLogMapper, OmsOrderLog
|
||||
|
||||
@Override
|
||||
public void addOrderLogs(Long orderId, Integer orderStatus, String detail) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
addOrderLogs(orderId, orderStatus, userId.toString(), detail);
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
addOrderLogs(orderId, orderStatus, memberId.toString(), detail);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package com.youlai.mall.oms.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
@ -22,33 +24,31 @@ import com.youlai.common.enums.BusinessTypeEnum;
|
||||
import com.youlai.common.redis.BusinessNoGenerator;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.oms.config.WxPayProperties;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.OrderTypeEnum;
|
||||
import com.youlai.mall.oms.enums.PayTypeEnum;
|
||||
import com.youlai.mall.oms.mapper.OrderMapper;
|
||||
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||
import com.youlai.mall.oms.pojo.dto.CartItemDTO;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.pojo.form.OrderSubmitForm;
|
||||
import com.youlai.mall.oms.pojo.query.OrderPageQuery;
|
||||
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
|
||||
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
|
||||
import com.youlai.mall.oms.service.ICartService;
|
||||
import com.youlai.mall.oms.service.IOrderItemService;
|
||||
import com.youlai.mall.oms.service.IOrderService;
|
||||
import com.youlai.mall.oms.tcc.service.SeataTccOrderService;
|
||||
import com.youlai.mall.pms.api.GoodsFeignClient;
|
||||
import com.youlai.mall.pms.api.StockFeignClient;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||
import com.youlai.mall.pms.pojo.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import com.youlai.mall.ums.api.MemberAddressFeignClient;
|
||||
import com.youlai.mall.ums.api.MemberFeignClient;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsMember;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import io.seata.spring.annotation.GlobalTransactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -71,6 +71,12 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static com.youlai.mall.oms.constant.OmsConstants.*;
|
||||
|
||||
/**
|
||||
* 订单业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -85,61 +91,44 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
private final ThreadPoolExecutor threadPoolExecutor;
|
||||
private final MemberFeignClient memberFeignClient;
|
||||
private final BusinessNoGenerator businessNoGenerator;
|
||||
private final GoodsFeignClient goodsFeignClient;
|
||||
private final StockFeignClient stockFeignClient;
|
||||
private final SeataTccOrderService seataTccOrderService;
|
||||
private final SkuFeignClient skuFeignClient;
|
||||
private final RedissonClient redissonClient;
|
||||
private final WxPayService wxPayService;
|
||||
|
||||
/**
|
||||
* 订单分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IPage<OmsOrder> listOrdersWithPage(OrderPageQuery queryParams) {
|
||||
Page<OmsOrder> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<OmsOrder> list = this.baseMapper.listUsersWithPage(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单确认
|
||||
*/
|
||||
@Override
|
||||
public OrderConfirmVO confirm(OrderConfirmDTO orderConfirmDTO) {
|
||||
log.info("=======================订单确认=======================\n订单确认信息:{}", orderConfirmDTO);
|
||||
log.info("订单确认:{}", orderConfirmDTO);
|
||||
OrderConfirmVO orderConfirmVO = new OrderConfirmVO();
|
||||
Long memberId = JwtUtils.getUserId();
|
||||
// 获取购买商品信息
|
||||
// 获取订单的商品信息
|
||||
CompletableFuture<Void> orderItemsCompletableFuture = CompletableFuture.runAsync(() -> {
|
||||
List<OrderItemDTO> orderItems = new ArrayList<>();
|
||||
if (orderConfirmDTO.getSkuId() != null) { // 直接购买商品结算
|
||||
OrderItemDTO orderItemDTO = OrderItemDTO.builder()
|
||||
.skuId(orderConfirmDTO.getSkuId())
|
||||
.count(orderConfirmDTO.getCount())
|
||||
.build();
|
||||
SkuDTO sku = goodsFeignClient.getSkuById(orderConfirmDTO.getSkuId()).getData();
|
||||
orderItemDTO.setPrice(sku.getPrice());
|
||||
orderItemDTO.setPic(sku.getPicUrl());
|
||||
orderItemDTO.setSkuName(sku.getName());
|
||||
orderItemDTO.setSkuCode(sku.getSn());
|
||||
orderItemDTO.setSpuName(sku.getGoodsName());
|
||||
orderItems.add(orderItemDTO);
|
||||
} else { // 购物车中商品结算
|
||||
List<CartItemDTO> cartItems = cartService.listCartItemByMemberId(memberId);
|
||||
List<OrderItemDTO> items = cartItems.stream()
|
||||
.filter(CartItemDTO::getChecked)
|
||||
.map(cartItem -> OrderItemDTO.builder()
|
||||
.skuId(cartItem.getSkuId())
|
||||
.count(cartItem.getCount())
|
||||
.price(cartItem.getPrice())
|
||||
.skuName(cartItem.getSkuName())
|
||||
.skuCode(cartItem.getSkuSn())
|
||||
.spuName(cartItem.getGoodsName())
|
||||
.pic(cartItem.getPicUrl())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
orderItems.addAll(items);
|
||||
}
|
||||
List<OrderItemDTO> orderItems = this.getOrderItems(orderConfirmDTO.getSkuId());
|
||||
orderConfirmVO.setOrderItems(orderItems);
|
||||
}, threadPoolExecutor);
|
||||
|
||||
// 获取会员地址列表
|
||||
// 获取会员收获地址
|
||||
CompletableFuture<Void> addressesCompletableFuture = CompletableFuture.runAsync(() -> {
|
||||
List<UmsAddress> addresses = addressFeignService.list(memberId).getData();
|
||||
List<MemberAddressDTO> addresses = addressFeignService.listCurrMemberAddresses().getData();
|
||||
orderConfirmVO.setAddresses(addresses);
|
||||
}, threadPoolExecutor);
|
||||
|
||||
// 生成唯一标识,防止订单重复提交
|
||||
// 生成唯一 token,防止订单重复提交
|
||||
CompletableFuture<Void> orderTokenCompletableFuture = CompletableFuture.runAsync(() -> {
|
||||
String orderToken = businessNoGenerator.generate(BusinessTypeEnum.ORDER);
|
||||
orderConfirmVO.setOrderToken(orderToken);
|
||||
@ -156,145 +145,70 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
*/
|
||||
@Override
|
||||
@GlobalTransactional
|
||||
public OrderSubmitVO submit(OrderSubmitDTO submitDTO) {
|
||||
log.info("=======================订单提交=======================\n订单提交信息:{}", submitDTO);
|
||||
public OrderSubmitVO submit(OrderSubmitForm orderSubmitForm) {
|
||||
log.info("订单提交数据:{}", JSONUtil.toJsonStr(orderSubmitForm));
|
||||
|
||||
// 订单基础信息校验
|
||||
List<OrderItemDTO> orderItems = orderSubmitForm.getOrderItems();
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(orderItems), "订单没有商品");
|
||||
|
||||
// 订单重复提交校验
|
||||
String orderToken = submitDTO.getOrderToken();
|
||||
String orderToken = orderSubmitForm.getOrderToken();
|
||||
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class);
|
||||
Long execute = this.redisTemplate.execute(redisScript, Collections.singletonList(ORDER_TOKEN_PREFIX + orderToken), orderToken);
|
||||
Assert.isTrue(execute.equals(1l), "订单不可重复提交");
|
||||
|
||||
List<OrderItemDTO> orderItems = submitDTO.getOrderItems();
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(orderItems), "订单商品为空");
|
||||
|
||||
// 订单验价
|
||||
Long currentTotalPrice = orderItems.stream().map(item -> {
|
||||
SkuDTO sku = goodsFeignClient.getSkuById(item.getSkuId()).getData();
|
||||
if (sku != null) {
|
||||
return sku.getPrice() * item.getCount();
|
||||
}
|
||||
return 0L;
|
||||
}).reduce(0L, Long::sum);
|
||||
Long orderTotalAmount = orderSubmitForm.getTotalAmount();
|
||||
boolean checkResult = this.checkOrderPrice(orderTotalAmount, orderItems);
|
||||
Assert.isTrue(checkResult, "当前页面已过期,请重新刷新页面再提交");
|
||||
|
||||
Assert.isTrue(currentTotalPrice.compareTo(submitDTO.getTotalPrice()) == 0, "当前页面已过期,请重新刷新页面再提交");
|
||||
// 锁定商品库存
|
||||
this.lockStock(orderToken, orderItems);
|
||||
|
||||
// 校验库存是否足够和锁库存
|
||||
List<LockStockDTO> skuLockList = orderItems.stream()
|
||||
.map(item -> LockStockDTO.builder().skuId(item.getSkuId())
|
||||
.count(item.getCount())
|
||||
.orderToken(orderToken)
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Result<SkuDTO> goodsResult = goodsFeignClient.getSkuById(1l);
|
||||
System.out.println(goodsResult);
|
||||
|
||||
// 锁定库存
|
||||
Result lockResult = stockFeignClient.lockStock(skuLockList);
|
||||
Assert.isTrue(Result.isSuccess(lockResult), "锁定商品库存失败:{}", lockResult.getMsg());
|
||||
|
||||
// 创建订单(状态:待支付)
|
||||
OmsOrder order = new OmsOrder();
|
||||
order.setOrderSn(orderToken) // 把orderToken赋值给订单编号【!】
|
||||
// 创建订单
|
||||
OmsOrder order = new OmsOrder().setOrderSn(orderToken) // 把orderToken赋值给订单编号
|
||||
.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode())
|
||||
.setSourceType(OrderTypeEnum.APP.getCode())
|
||||
.setMemberId(JwtUtils.getUserId())
|
||||
.setRemark(submitDTO.getRemark())
|
||||
.setPayAmount(submitDTO.getPayAmount())
|
||||
.setMemberId(MemberUtils.getMemberId())
|
||||
.setRemark(orderSubmitForm.getRemark())
|
||||
.setPayAmount(orderSubmitForm.getPayAmount())
|
||||
.setTotalQuantity(orderItems.stream().map(OrderItemDTO::getCount).reduce(0, Integer::sum))
|
||||
.setTotalAmount(orderItems.stream().map(item -> item.getPrice() * item.getCount()).reduce(0L, Long::sum));
|
||||
this.save(order);
|
||||
boolean result = this.save(order);
|
||||
|
||||
// 创建订单商品
|
||||
List<OmsOrderItem> orderItemList = orderItems.stream().map(item -> OmsOrderItem.builder()
|
||||
.orderId(order.getId())
|
||||
.skuId(item.getSkuId())
|
||||
.skuName(item.getSkuName())
|
||||
.skuPrice(item.getPrice())
|
||||
.skuPic(item.getPic())
|
||||
.skuQuantity(item.getCount())
|
||||
.skuTotalPrice(item.getCount() * item.getPrice())
|
||||
.skuCode(item.getSkuCode())
|
||||
.build()).collect(Collectors.toList());
|
||||
orderItemService.saveBatch(orderItemList);
|
||||
|
||||
// 将订单放入延时队列,超时未支付由交换机order.exchange切换到死信队列完成系统自动关单
|
||||
log.info("订单超时取消RabbitMQ消息发送,订单SN:{}", orderToken);
|
||||
// 添加订单明细
|
||||
if (result) {
|
||||
List<OmsOrderItem> orderItemList = orderItems.stream().map(orderFormItem -> {
|
||||
OmsOrderItem omsOrderItem = new OmsOrderItem();
|
||||
BeanUtil.copyProperties(orderFormItem, omsOrderItem);
|
||||
omsOrderItem.setOrderId(order.getId());
|
||||
omsOrderItem.setTotalAmount(orderFormItem.getPrice() * orderFormItem.getCount());
|
||||
return omsOrderItem;
|
||||
}).collect(Collectors.toList());
|
||||
result = orderItemService.saveBatch(orderItemList);
|
||||
if (result) {
|
||||
// 订单超时取消
|
||||
rabbitTemplate.convertAndSend("order.exchange", "order.create", orderToken);
|
||||
}
|
||||
}
|
||||
Assert.isTrue(result, "订单提交失败");
|
||||
|
||||
// 成功响应返回值构建
|
||||
OrderSubmitVO submitVO = new OrderSubmitVO();
|
||||
submitVO.setOrderId(order.getId());
|
||||
submitVO.setOrderSn(order.getOrderSn());
|
||||
log.info("订单提交响应:{}", submitVO);
|
||||
return submitVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@GlobalTransactional(rollbackFor = Exception.class)
|
||||
public OrderSubmitVO submitTcc(OrderSubmitDTO submitDTO) {
|
||||
log.info("=======================订单提交=======================\n订单提交信息:{}", submitDTO);
|
||||
// 订单重复提交校验
|
||||
String orderToken = submitDTO.getOrderToken();
|
||||
// DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class);
|
||||
// Long result = this.redisTemplate.execute(redisScript, Collections.singletonList(ORDER_TOKEN_PREFIX + orderToken), orderToken);
|
||||
//
|
||||
// if (!ObjectUtil.equals(result, RELEASE_LOCK_SUCCESS_RESULT)) {
|
||||
// throw new BizException("订单不可重复提交");
|
||||
// }
|
||||
|
||||
List<OrderItemDTO> orderItems = submitDTO.getOrderItems();
|
||||
if (CollectionUtil.isEmpty(orderItems)) {
|
||||
throw new BizException("订单没有商品,请选择商品后提交");
|
||||
}
|
||||
|
||||
// 订单验价
|
||||
Long currentTotalPrice = orderItems.stream().map(item -> {
|
||||
SkuDTO sku = goodsFeignClient.getSkuById(item.getSkuId()).getData();
|
||||
if (sku != null) {
|
||||
return sku.getPrice() * item.getCount();
|
||||
}
|
||||
return 0L;
|
||||
}).reduce(0L, Long::sum);
|
||||
|
||||
if (currentTotalPrice.compareTo(submitDTO.getTotalPrice()) != 0) {
|
||||
throw new BizException("页面已过期,请重新刷新页面再提交");
|
||||
}
|
||||
|
||||
// 校验库存是否足够和锁库存
|
||||
List<LockStockDTO> skuLockList = orderItems.stream()
|
||||
.map(item -> LockStockDTO.builder().skuId(item.getSkuId())
|
||||
.count(item.getCount())
|
||||
.orderToken(orderToken)
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Result<?> lockResult = stockFeignClient.lockStock(skuLockList);
|
||||
|
||||
if (!Result.success().getCode().equals(lockResult.getCode())) {
|
||||
throw new BizException(Result.failed().getMsg());
|
||||
}
|
||||
// TCC模式创建订单(状态:待支付)
|
||||
OmsOrder order = seataTccOrderService.prepareSubmitOrder(null, submitDTO);
|
||||
// 将订单放入延时队列,超时未支付由交换机order.exchange切换到死信队列完成系统自动关单
|
||||
log.info("订单超时取消RabbitMQ消息发送,订单SN:{}", orderToken);
|
||||
rabbitTemplate.convertAndSend("order.exchange", "order.create", orderToken);
|
||||
|
||||
OrderSubmitVO submitVO = new OrderSubmitVO();
|
||||
submitVO.setOrderId(order.getId());
|
||||
submitVO.setOrderSn(order.getOrderSn());
|
||||
log.info("订单提交响应:{}", submitVO);
|
||||
return submitVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
*
|
||||
* @param orderId
|
||||
* @param appId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
@GlobalTransactional(rollbackFor = Exception.class)
|
||||
public <T> T pay(Long orderId, String appId, PayTypeEnum payTypeEnum) {
|
||||
OmsOrder order = this.getById(orderId);
|
||||
@ -324,12 +238,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
}
|
||||
|
||||
// 扣减库存
|
||||
Result<?> deductStockResult = stockFeignClient.deductStock(order.getOrderSn());
|
||||
|
||||
Result<?> deductStockResult = skuFeignClient.deductStock(order.getOrderSn());
|
||||
if (!Result.isSuccess(deductStockResult)) {
|
||||
throw new BizException("扣减商品库存失败");
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
@ -346,6 +258,13 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 余额支付
|
||||
*
|
||||
* @param order
|
||||
* @return
|
||||
*/
|
||||
private Boolean balancePay(OmsOrder order) {
|
||||
// 扣减余额
|
||||
Long payAmount = order.getPayAmount();
|
||||
@ -365,13 +284,9 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
|
||||
private WxPayUnifiedOrderV3Result.JsapiResult wxJsapiPay(String appId, OmsOrder order) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
Result<UmsMember> userInfoResult = memberFeignClient.getUserEntityById(userId);
|
||||
if (!Result.isSuccess(userInfoResult)) {
|
||||
throw new BizException("用户查询失败");
|
||||
}
|
||||
UmsMember userInfo = userInfoResult.getData();
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
Long payAmount = order.getPayAmount();
|
||||
// 如果已经有outTradeNo了就先进行关单
|
||||
if (PayTypeEnum.WEIXIN_JSAPI.getCode().equals(order.getPayType()) && StrUtil.isNotBlank(order.getOutTradeNo())) {
|
||||
@ -383,7 +298,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
}
|
||||
}
|
||||
// 用户id前补零保证五位,对超出五位的保留后五位
|
||||
String userIdFilledZero = String.format("%05d", userId);
|
||||
String userIdFilledZero = String.format("%05d", memberId);
|
||||
String fiveDigitsUserId = userIdFilledZero.substring(userIdFilledZero.length() - 5);
|
||||
// 在前面加上wxo(weixin order)等前缀是为了人工可以快速分辨订单号是下单还是退款、来自哪家支付机构等
|
||||
// 将时间戳+3位随机数+五位id组成商户订单号,规则参考自<a href="https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html">大众点评</a>
|
||||
@ -394,12 +309,14 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
order.setOutTradeNo(outTradeNo);
|
||||
this.updateById(order);
|
||||
|
||||
String memberOpenId = memberFeignClient.getMemberOpenId(memberId).getData();
|
||||
|
||||
WxPayUnifiedOrderV3Request wxRequest = new WxPayUnifiedOrderV3Request()
|
||||
.setOutTradeNo(outTradeNo)
|
||||
.setAppid(appId)
|
||||
.setNotifyUrl(wxPayProperties.getPayNotifyUrl())
|
||||
.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(Math.toIntExact(payAmount)))
|
||||
.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(userInfo.getOpenid()))
|
||||
.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(memberOpenId))
|
||||
.setDescription("赅买-订单编号" + order.getOrderSn());
|
||||
WxPayUnifiedOrderV3Result.JsapiResult jsapiResult;
|
||||
try {
|
||||
@ -413,7 +330,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
|
||||
@Override
|
||||
public boolean closeOrder(String orderToken) {
|
||||
log.info("=======================订单关闭,订单SN:{}=======================", orderToken);
|
||||
log.info("订单超时取消,orderToken:{}", orderToken);
|
||||
OmsOrder order = this.getOne(new LambdaQueryWrapper<OmsOrder>()
|
||||
.eq(OmsOrder::getOrderSn, orderToken));
|
||||
if (order == null || !OrderStatusEnum.PENDING_PAYMENT.getCode().equals(order.getStatus())) {
|
||||
@ -435,7 +352,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
|
||||
@Override
|
||||
public boolean cancelOrder(Long id) {
|
||||
log.info("=======================订单取消,订单ID:{}=======================", id);
|
||||
log.info("订单超时取消,订单ID:{}", id);
|
||||
OmsOrder order = this.getById(id);
|
||||
if (order == null) {
|
||||
throw new BizException("订单不存在");
|
||||
@ -458,7 +375,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
boolean result = this.updateById(order);
|
||||
if (result) {
|
||||
// 释放被锁定的库存
|
||||
Result<?> unlockResult = stockFeignClient.unlockStock(order.getOrderSn());
|
||||
Result<?> unlockResult = skuFeignClient.unlockStock(order.getOrderSn());
|
||||
if (!Result.isSuccess(unlockResult)) {
|
||||
throw new BizException(unlockResult.getMsg());
|
||||
}
|
||||
@ -526,18 +443,79 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单分页列表
|
||||
* 订单验价,进入结算页面的订单总价和当前所有商品的总价是否一致
|
||||
*
|
||||
* @param queryParams
|
||||
* @param orderTotalAmount 订单总金额
|
||||
* @param orderItems 订单商品明细
|
||||
* @return true:订单总价和商品总价一致;false:订单总价和商品总价不一致。
|
||||
*/
|
||||
private boolean checkOrderPrice(Long orderTotalAmount, List<OrderItemDTO> orderItems) {
|
||||
CheckPriceDTO checkPriceDTO = new CheckPriceDTO();
|
||||
List<CheckPriceDTO.CheckSku> checkSkus = orderItems.stream().map(orderFormItem -> {
|
||||
CheckPriceDTO.CheckSku checkSku = new CheckPriceDTO.CheckSku();
|
||||
checkSku.setSkuId(orderFormItem.getSkuId());
|
||||
checkSku.setCount(orderFormItem.getCount());
|
||||
return checkSku;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
checkPriceDTO.setOrderTotalAmount(orderTotalAmount); // 订单总金额
|
||||
checkPriceDTO.setCheckSkus(checkSkus); // 订单的商品明细
|
||||
|
||||
// 调用验价接口,比较订单总金额和商品明细总金额,不一致则说明商品价格变动
|
||||
Result<Boolean> checkPriceResult = skuFeignClient.checkPrice(checkPriceDTO);
|
||||
|
||||
boolean result = Result.isSuccess(checkPriceResult) && Boolean.TRUE.equals(checkPriceResult.getData());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单的商品明细
|
||||
*
|
||||
* @param skuId 直接购买会有值
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IPage<OmsOrder> listOrdersWithPage(OrderPageQuery queryParams) {
|
||||
Page<OmsOrder> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<OmsOrder> list = this.baseMapper.listUsersWithPage(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
private List<OrderItemDTO> getOrderItems(Long skuId) {
|
||||
List<OrderItemDTO> orderItems;
|
||||
if (skuId != null) { // 直接购买
|
||||
orderItems = new ArrayList<>();
|
||||
SkuInfoDTO skuInfoDTO = skuFeignClient.getSkuInfo(skuId).getData();
|
||||
OrderItemDTO orderItemDTO = new OrderItemDTO();
|
||||
BeanUtil.copyProperties(skuInfoDTO, orderItemDTO);
|
||||
|
||||
orderItemDTO.setCount(1); // 直接购买商品的数量为1
|
||||
orderItems.add(orderItemDTO);
|
||||
} else { // 购物车结算
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
List<CartItemDTO> cartItems = cartService.listCartItemByMemberId(memberId);
|
||||
orderItems = cartItems.stream()
|
||||
.filter(CartItemDTO::getChecked)
|
||||
.map(cartItem -> {
|
||||
OrderItemDTO orderItemDTO = new OrderItemDTO();
|
||||
BeanUtil.copyProperties(cartItem, orderItemDTO);
|
||||
return orderItemDTO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return orderItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定商品库存
|
||||
*
|
||||
* @param orderToken
|
||||
* @param orderItems
|
||||
*/
|
||||
private void lockStock(String orderToken, List<OrderItemDTO> orderItems) {
|
||||
LockStockDTO lockStockDTO = new LockStockDTO();
|
||||
lockStockDTO.setOrderToken(orderToken);
|
||||
|
||||
List<LockStockDTO.LockedSku> lockedSkuList = orderItems.stream()
|
||||
.map(orderItem -> new LockStockDTO.LockedSku()
|
||||
.setSkuId(orderItem.getSkuId())
|
||||
.setCount(orderItem.getCount())
|
||||
).collect(Collectors.toList());
|
||||
|
||||
lockStockDTO.setLockedSkuList(lockedSkuList);
|
||||
skuFeignClient.lockStock(lockStockDTO);
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package com.youlai.mall.oms.tcc.idempotent;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
|
||||
/**
|
||||
* @Author DaniR
|
||||
* @Description TCC幂等工具类
|
||||
* @Date 2021/7/15 20:38
|
||||
**/
|
||||
public class IdempotentUtil {
|
||||
|
||||
private static Table<Class<?>,String,Long> map= HashBasedTable.create();
|
||||
|
||||
public static void addMarker(Class<?> clazz,String xid,Long marker){
|
||||
map.put(clazz,xid,marker);
|
||||
}
|
||||
|
||||
public static Long getMarker(Class<?> clazz,String xid){
|
||||
return map.get(clazz,xid);
|
||||
}
|
||||
|
||||
public static void removeMarker(Class<?> clazz,String xid){
|
||||
map.remove(clazz,xid);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package com.youlai.mall.oms.tcc.service;
|
||||
|
||||
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||
import io.seata.rm.tcc.api.BusinessActionContextParameter;
|
||||
import io.seata.rm.tcc.api.LocalTCC;
|
||||
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||
|
||||
@LocalTCC
|
||||
public interface SeataTccOrderService {
|
||||
|
||||
@TwoPhaseBusinessAction(name = "prepareSubmitOrder", commitMethod = "commitSubmitOrder", rollbackMethod = "rollbackSubmitOrder")
|
||||
OmsOrder prepareSubmitOrder(BusinessActionContext businessActionContext,
|
||||
@BusinessActionContextParameter(paramName = "orderSubmitDTO") OrderSubmitDTO orderSubmitDTO);
|
||||
|
||||
boolean commitSubmitOrder(BusinessActionContext businessActionContext);
|
||||
|
||||
boolean rollbackSubmitOrder(BusinessActionContext businessActionContext);
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package com.youlai.mall.oms.tcc.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||
import com.youlai.mall.oms.enums.OrderTypeEnum;
|
||||
import com.youlai.mall.oms.mapper.OrderMapper;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
||||
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||
import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
||||
import com.youlai.mall.oms.service.IOrderItemService;
|
||||
import com.youlai.mall.oms.tcc.idempotent.IdempotentUtil;
|
||||
import com.youlai.mall.oms.tcc.service.SeataTccOrderService;
|
||||
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SeataTccOrderServiceImpl implements SeataTccOrderService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
@Autowired
|
||||
private IOrderItemService orderItemService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public OmsOrder prepareSubmitOrder(BusinessActionContext businessActionContext, OrderSubmitDTO orderSubmitDTO) {
|
||||
log.info("==========创建 订单 第一阶段,事务组Xid:{} ==========", businessActionContext.getXid());
|
||||
List<OrderItemDTO> orderItems = orderSubmitDTO.getOrderItems();
|
||||
String orderToken = orderSubmitDTO.getOrderToken();
|
||||
// 创建订单(状态:待支付)
|
||||
OmsOrder order = new OmsOrder();
|
||||
order.setOrderSn(orderToken)
|
||||
.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode())
|
||||
.setSourceType(OrderTypeEnum.APP.getCode())
|
||||
.setMemberId(JwtUtils.getUserId())
|
||||
.setRemark(orderSubmitDTO.getRemark())
|
||||
.setPayAmount(orderSubmitDTO.getPayAmount())
|
||||
.setTotalQuantity(orderItems.stream().map(item -> item.getCount()).reduce(0, (x, y) -> x + y))
|
||||
.setTotalAmount(orderItems.stream().map(item -> item.getPrice() * item.getCount()).reduce(0l, (x, y) -> x + y));
|
||||
orderMapper.insert(order);
|
||||
int i = 1 / 0;
|
||||
// 创建订单商品
|
||||
List<OmsOrderItem> orderItemList = orderItems.stream().map(item -> OmsOrderItem.builder()
|
||||
.orderId(order.getId())
|
||||
.skuId(item.getSkuId())
|
||||
.skuName(item.getSkuName())
|
||||
.skuPrice(item.getPrice())
|
||||
.skuPic(item.getPic())
|
||||
.skuQuantity(item.getCount())
|
||||
.skuTotalPrice(item.getCount() * item.getPrice())
|
||||
.skuCode(item.getSkuCode())
|
||||
.build()).collect(Collectors.toList());
|
||||
orderItemService.saveBatch(orderItemList);
|
||||
log.info("保存订单:{} 成功", order.getOrderSn());
|
||||
IdempotentUtil.addMarker(getClass(), businessActionContext.getXid(), System.currentTimeMillis());
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean commitSubmitOrder(BusinessActionContext businessActionContext) {
|
||||
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||
|
||||
return true;
|
||||
}
|
||||
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean rollbackSubmitOrder(BusinessActionContext businessActionContext) {
|
||||
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||
return true;
|
||||
}
|
||||
JSONObject jsonObject = (JSONObject) businessActionContext.getActionContext("orderSubmitDTO");
|
||||
OrderSubmitDTO orderSubmitDTO = new OrderSubmitDTO();
|
||||
BeanUtil.copyProperties(jsonObject, orderSubmitDTO);
|
||||
OmsOrder omsOrder = orderMapper.selectOne(new LambdaQueryWrapper<OmsOrder>().eq(OmsOrder::getOrderSn, orderSubmitDTO.getOrderToken()));
|
||||
if (Objects.nonNull(omsOrder)) {
|
||||
orderItemService.remove(new LambdaQueryWrapper<OmsOrderItem>().eq(OmsOrderItem::getOrderId, omsOrder.getId()));
|
||||
orderMapper.deleteById(omsOrder.getId());
|
||||
}
|
||||
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||
return true;
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ spring:
|
||||
server-addr: http://localhost:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
file-extension: yaml
|
||||
# 公共配置
|
||||
shared-configs[0]:
|
||||
|
@ -11,11 +11,11 @@ spring:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
||||
|
@ -1,13 +1,11 @@
|
||||
package com.youlai.mall.oms.controller;
|
||||
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@Slf4j
|
||||
public class RabbitMQTest {
|
||||
|
@ -1,17 +0,0 @@
|
||||
package com.youlai.mall.pms.api;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
@FeignClient(value = "mall-pms",contextId = "goods")
|
||||
public interface GoodsFeignClient {
|
||||
|
||||
/**
|
||||
* 获取商品信息
|
||||
*/
|
||||
@GetMapping("/app-api/v1/stocks/{id}")
|
||||
Result<SkuDTO> getSkuById(@PathVariable Long id);
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.youlai.mall.pms.api;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(value = "mall-pms", contextId = "sku")
|
||||
public interface SkuFeignClient {
|
||||
|
||||
/**
|
||||
* 获取商品库存单元信息
|
||||
*/
|
||||
@GetMapping("/app-api/v1/sku/{skuId}/info")
|
||||
Result<SkuInfoDTO> getSkuInfo(@PathVariable Long skuId);
|
||||
|
||||
/**
|
||||
* 锁定商品库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/sku/_lock")
|
||||
Result lockStock(@RequestBody LockStockDTO lockStockDTO);
|
||||
|
||||
/**
|
||||
* 解锁商品库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/sku/_unlock")
|
||||
Result unlockStock(@RequestParam String orderToken);
|
||||
|
||||
/**
|
||||
* 扣减商品库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/sku/_deduct")
|
||||
Result deductStock(@RequestParam String orderToken);
|
||||
|
||||
|
||||
/**
|
||||
* 订单商品验价
|
||||
*
|
||||
* @param checkPriceDTO
|
||||
*/
|
||||
@PostMapping("/app-api/v1/sku/price/_check")
|
||||
Result<Boolean> checkPrice(@RequestBody CheckPriceDTO checkPriceDTO);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package com.youlai.mall.pms.api;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(value = "mall-pms",contextId = "stock")
|
||||
public interface StockFeignClient {
|
||||
|
||||
/**
|
||||
* 锁定库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/stocks/_lock")
|
||||
Result lockStock(@RequestBody List<LockStockDTO> list);
|
||||
|
||||
/**
|
||||
* 解锁库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/stocks/_unlock")
|
||||
Result<Boolean> unlockStock(@RequestParam String orderToken);
|
||||
|
||||
|
||||
/**
|
||||
* 扣减库存
|
||||
*/
|
||||
@PutMapping("/app-api/v1/stocks/_deduct")
|
||||
Result deductStock(@RequestParam String orderToken);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.youlai.mall.pms.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品验价传输层实体
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/7 22:52
|
||||
*/
|
||||
@Data
|
||||
public class CheckPriceDTO {
|
||||
|
||||
/**
|
||||
* 订单商品总金额
|
||||
*/
|
||||
private Long orderTotalAmount;
|
||||
|
||||
/**
|
||||
* 订单商品明细
|
||||
*/
|
||||
private List<CheckSku> checkSkus;
|
||||
|
||||
|
||||
/**
|
||||
* 订单商品明细
|
||||
*/
|
||||
@Data
|
||||
public static class CheckSku {
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.youlai.mall.pms.pojo.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/5 23:09
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class SkuInfoDTO {
|
||||
/**
|
||||
* skuId
|
||||
*/
|
||||
private Long skuId;
|
||||
/**
|
||||
* SKU 编号
|
||||
*/
|
||||
private String skuSn;
|
||||
/**
|
||||
* SKU 名称
|
||||
*/
|
||||
private String skuName;
|
||||
/**
|
||||
* SKU 图片地址
|
||||
*/
|
||||
private String picUrl;
|
||||
/**
|
||||
* SKU 价格
|
||||
*/
|
||||
private Long price;
|
||||
/**
|
||||
* SKU 库存数量
|
||||
*/
|
||||
private Integer stockNum;
|
||||
/**
|
||||
* SPU 名称
|
||||
*/
|
||||
private String spuName;
|
||||
}
|
@ -1,27 +1,44 @@
|
||||
package com.youlai.mall.pms.pojo.dto.app;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 锁定库存
|
||||
* 锁定库存传输层实体
|
||||
*
|
||||
* @author haoxr
|
||||
* @createTime 2021-03-07 15:14
|
||||
* @date 2021-03-07 15:14
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
|
||||
public class LockStockDTO {
|
||||
|
||||
private Long skuId;
|
||||
|
||||
private Integer count;
|
||||
|
||||
/**
|
||||
* 订单token
|
||||
*/
|
||||
private String orderToken;
|
||||
|
||||
private Boolean locked;
|
||||
/**
|
||||
* 锁定商品列表
|
||||
*/
|
||||
private List<LockedSku> lockedSkuList;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Data
|
||||
public static class LockedSku {
|
||||
|
||||
/**
|
||||
* 商品ID
|
||||
*/
|
||||
private Long skuId;
|
||||
|
||||
/**
|
||||
* 商品数量
|
||||
*/
|
||||
private Integer count;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
package com.youlai.mall.pms.pojo.dto.app;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author huawei
|
||||
* @desc
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/1/13
|
||||
*/
|
||||
@Data
|
||||
public class SkuDTO {
|
||||
|
||||
private Long id;
|
||||
private String sn;
|
||||
private String name;
|
||||
private String picUrl;
|
||||
private Long price;
|
||||
|
||||
private Integer stock;
|
||||
private String goodsName;
|
||||
|
||||
}
|
@ -5,16 +5,55 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品库存单元实体
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/6
|
||||
*/
|
||||
@Data
|
||||
public class PmsSku extends BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String sn;
|
||||
|
||||
/**
|
||||
* 库存单元编号
|
||||
*/
|
||||
private String skuSn;
|
||||
|
||||
/**
|
||||
* SKU 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* SPU ID
|
||||
*/
|
||||
private Long spuId;
|
||||
|
||||
/**
|
||||
* 规格ID,多个使用英文逗号(,)分割
|
||||
*/
|
||||
private String specIds;
|
||||
|
||||
/**
|
||||
* 商品价格(单位:分)
|
||||
*/
|
||||
private Long price;
|
||||
private Integer stock;
|
||||
private Integer lockedStock;
|
||||
|
||||
/**
|
||||
* 库存数量
|
||||
*/
|
||||
private Integer stockNum;
|
||||
|
||||
/**
|
||||
* 锁定库存数量
|
||||
*/
|
||||
private Integer lockedStockNum;
|
||||
|
||||
/**
|
||||
* 商品图片地址
|
||||
*/
|
||||
private String picUrl;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import lombok.ToString;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品详情视图层对象
|
||||
* 商品详情视图对象
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
*/
|
||||
@ -36,7 +36,6 @@ public class GoodsDetailVO {
|
||||
|
||||
private String detail;
|
||||
|
||||
|
||||
private List<PmsSpuAttributeValue> attrList;
|
||||
|
||||
private List<PmsSpuAttributeValue> specList;
|
||||
|
@ -1,18 +0,0 @@
|
||||
package com.youlai.mall.pms.pojo.vo.app;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品列表页-商品基础信息
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2021/8/8
|
||||
*/
|
||||
@Data
|
||||
public class GoodsVO {
|
||||
private Long id;
|
||||
private String name;
|
||||
private Long price;
|
||||
private Integer sales;
|
||||
private String picUrl;
|
||||
}
|
@ -45,27 +45,6 @@
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 分布式事务Seata 指定Seata客户端版本和服务器一致 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
@ -86,7 +65,6 @@
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>common-mybatis</artifactId>
|
||||
|
@ -1,12 +1,14 @@
|
||||
package com.youlai.mall.pms.common.constant;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2021-02-28 20:10
|
||||
*/
|
||||
public interface PmsConstants {
|
||||
|
||||
String LOCKED_STOCK_PREFIX = "stock:locked:";
|
||||
String LOCKED_STOCK_PREFIX = "pms:locked_stock:";
|
||||
|
||||
String LOCK_SKU_PREFIX = "lock:sku:";
|
||||
|
||||
|
@ -5,7 +5,7 @@ import com.google.common.hash.Funnel;
|
||||
import com.youlai.mall.pms.common.constant.PmsConstants;
|
||||
import com.youlai.mall.pms.component.BloomRedisService;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.serviceapp.IGoodsService;
|
||||
import com.youlai.mall.pms.service.IPmsSpuService;
|
||||
import com.youlai.mall.pms.utils.BloomFilterUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -26,7 +26,7 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
public class BloomFilterConfig implements InitializingBean {
|
||||
|
||||
private final IGoodsService goodsService;
|
||||
private final IPmsSpuService spuService;
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@Bean
|
||||
@ -46,7 +46,7 @@ public class BloomFilterConfig implements InitializingBean {
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
List<PmsSpu> list = goodsService.list();
|
||||
List<PmsSpu> list = spuService.list();
|
||||
log.info("加载产品到布隆过滤器当中,size:{}", list.size());
|
||||
if (!CollectionUtils.isEmpty(list)) {
|
||||
list.stream().filter(item -> item.getId() > 0).forEach(item -> {
|
||||
|
@ -0,0 +1,42 @@
|
||||
package com.youlai.mall.pms.controller.admin;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import io.swagger.annotations.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/8
|
||||
*/
|
||||
@Api(tags = "「系统端」库存信息")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/sku")
|
||||
@RequiredArgsConstructor
|
||||
public class OmsSkuController {
|
||||
|
||||
private final IPmsSkuService skuService;
|
||||
|
||||
@ApiOperation(value = "商品库存详情")
|
||||
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/{skuId}")
|
||||
public Result detail(
|
||||
@PathVariable Long skuId
|
||||
) {
|
||||
PmsSku sku = skuService.getById(skuId);
|
||||
return Result.success(sku);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "修改库存")
|
||||
@PutMapping(value = "/{skuId}")
|
||||
public Result update(
|
||||
@ApiParam("商品库存单元ID") @PathVariable Long skuId,
|
||||
@RequestBody PmsSku sku
|
||||
) {
|
||||
boolean status = skuService.updateById(sku);
|
||||
return Result.judge(status);
|
||||
}
|
||||
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package com.youlai.mall.pms.controller.admin;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
*/
|
||||
@Api(tags = "「系统端」库存信息")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/inventories")
|
||||
@RequiredArgsConstructor
|
||||
public class StockController {
|
||||
|
||||
private final IPmsSkuService iPmsSkuService;
|
||||
|
||||
@ApiOperation(value = "商品库存详情")
|
||||
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/{id}")
|
||||
public Result detail(@PathVariable Long id) {
|
||||
PmsSku sku = iPmsSkuService.getById(id);
|
||||
return Result.success(sku);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "修改库存")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long"),
|
||||
@ApiImplicitParam(name = "sku", value = "实体JSON对象", required = true, paramType = "body", dataType = "PmsSku")
|
||||
})
|
||||
@PutMapping(value = "/{id}")
|
||||
public Result update(@PathVariable Long id, @RequestBody PmsSku sku) {
|
||||
boolean status = iPmsSkuService.updateById(sku);
|
||||
return Result.judge(status);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "修改商品库存")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long"),
|
||||
@ApiImplicitParam(name = "num", value = "库存数量", required = true, paramType = "query", dataType = "Long")
|
||||
})
|
||||
@PatchMapping("/{id}/stock")
|
||||
public Result updateStock(@PathVariable Long id, @RequestParam Integer num) {
|
||||
PmsSku sku = iPmsSkuService.getById(id);
|
||||
sku.setStock(sku.getStock() + num);
|
||||
boolean result = iPmsSkuService.updateById(sku);
|
||||
return Result.judge(result);
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package com.youlai.mall.pms.controller.app;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.pojo.vo.app.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.pojo.vo.app.GoodsVO;
|
||||
import com.youlai.mall.pms.serviceapp.IGoodsService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import jodd.util.StringUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Api(tags = "「移动端」商品信息")
|
||||
@RestController(value = "appGoodsController")
|
||||
@RequestMapping("/app-api/v1/goods")
|
||||
@AllArgsConstructor
|
||||
public class AppGoodsController {
|
||||
|
||||
private IGoodsService goodsService;
|
||||
|
||||
@ApiOperation(value = "商品分页列表")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "page", value = "页码", example = "1", paramType = "query", dataType = "Long"),
|
||||
@ApiImplicitParam(name = "limit", value = "每页数量", example = "10", paramType = "query", dataType = "Long"),
|
||||
@ApiImplicitParam(name = "name", value = "商品名称", example = "华为P50", paramType = "query", dataType = "String"),
|
||||
@ApiImplicitParam(name = "categoryId", value = "商品类目", example = "1", paramType = "query", dataType = "Long"),
|
||||
@ApiImplicitParam(name = "orderBy", value = "排序字段", example = "price", paramType = "query", dataType = "String"),
|
||||
@ApiImplicitParam(name = "isAsc", value = "是否升序", example = "false", paramType = "query", dataType = "Boolean")
|
||||
})
|
||||
@GetMapping
|
||||
public Result<List<GoodsVO>> list(@RequestParam(name = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(name = "limit", defaultValue = "10") Integer limit,
|
||||
String name,
|
||||
Long categoryId,
|
||||
@RequestParam(name = "orderBy", defaultValue = "id") String orderBy,
|
||||
@RequestParam(name = "isAsc", defaultValue = "false") Boolean isAsc) {
|
||||
Page<PmsSpu> pageResult = goodsService.page(new Page<>(page, limit), new QueryWrapper<PmsSpu>()
|
||||
.eq(categoryId != null, "category_id", categoryId)
|
||||
.like(StrUtil.isNotBlank(name), "name", name)
|
||||
.select("id", "name", "pic_url", "price", "sales")
|
||||
.orderBy(StringUtil.isNotBlank(orderBy), isAsc, StrUtil.toUnderlineCase(orderBy))
|
||||
);
|
||||
|
||||
List<GoodsVO> list = pageResult.getRecords().stream()
|
||||
.map(item -> {
|
||||
GoodsVO goodsVO = new GoodsVO();
|
||||
BeanUtil.copyProperties(item, goodsVO);
|
||||
return goodsVO;
|
||||
}).collect(Collectors.toList());
|
||||
return Result.success(list, pageResult.getTotal());
|
||||
}
|
||||
|
||||
@ApiOperation(value = "商品详情")
|
||||
@ApiImplicitParam(name = "id", value = "商品ID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/{id}")
|
||||
public Result<GoodsDetailVO> detail(@PathVariable Long id) {
|
||||
GoodsDetailVO goodsDetailVO = goodsService.getGoodsById(id);
|
||||
return Result.success(goodsDetailVO);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "商品详情")
|
||||
@ApiImplicitParam(name = "id", value = "商品SkuID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/sku/{skuId}")
|
||||
public Result<GoodsDetailVO> detailBySkuId(@PathVariable Long skuId) {
|
||||
GoodsDetailVO goodsDetailVO = goodsService.getGoodsBySkuId(skuId);
|
||||
return Result.success(goodsDetailVO);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package com.youlai.mall.pms.controller.app;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stock Keeping Unit
|
||||
*/
|
||||
@Api(tags = "「移动端」商品库存")
|
||||
@RestController(value = "appStockController")
|
||||
@RequestMapping("/app-api/v1/stocks")
|
||||
@AllArgsConstructor
|
||||
public class AppStockController {
|
||||
|
||||
private IPmsSkuService iPmsSkuService;
|
||||
|
||||
@ApiOperation(value = "商品库存单元详情")
|
||||
@GetMapping("/{skuId}")
|
||||
public Result detail(@PathVariable Long skuId) {
|
||||
SkuDTO sku = iPmsSkuService.getSkuById(skuId);
|
||||
return Result.success(sku);
|
||||
}
|
||||
|
||||
@ApiOperation("获取商品的库存数量")
|
||||
@GetMapping("/{skuId}/stock")
|
||||
public Result<Integer> getStockById(@PathVariable Long skuId) {
|
||||
Integer stock = iPmsSkuService.getStockById(skuId);
|
||||
return Result.success(stock);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "锁定库存")
|
||||
@PutMapping("/_lock")
|
||||
public Result lockStock(@RequestBody List<LockStockDTO> list) {
|
||||
return iPmsSkuService.lockStock(list);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "解锁库存")
|
||||
@PutMapping("/_unlock")
|
||||
public Result<Boolean> unlockStock(String orderToken) {
|
||||
boolean result = iPmsSkuService.unlockStock(orderToken);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "扣减库存")
|
||||
@PutMapping("/_deduct")
|
||||
public Result<Boolean> deductStock(String orderToken) {
|
||||
boolean result = iPmsSkuService.deductStock(orderToken);
|
||||
return Result.judge(result);
|
||||
}
|
||||
}
|
@ -4,10 +4,9 @@ import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.vo.CategoryVO;
|
||||
import com.youlai.mall.pms.service.IPmsCategoryService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@ -15,21 +14,23 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品分类控制器
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/5
|
||||
*/
|
||||
@Api(tags = "「移动端」商品分类")
|
||||
@RestController("appCategoryController")
|
||||
@RequestMapping("/app-api/v1/categories")
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class AppCategoryController {
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryController {
|
||||
|
||||
private IPmsCategoryService iPmsCategoryService;
|
||||
private final IPmsCategoryService iPmsCategoryService;
|
||||
|
||||
@ApiOperation(value = "分类列表")
|
||||
@ApiImplicitParam(name = "parentId",value = "上级分类ID", paramType = "query", dataType = "Long")
|
||||
@GetMapping
|
||||
public Result list(Long parentId) {
|
||||
public Result list(
|
||||
@ApiParam("上级分类ID") Long parentId) {
|
||||
List<CategoryVO> list = iPmsCategoryService.listCategory(parentId);
|
||||
return Result.success(list);
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.youlai.mall.pms.controller.app;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品库存单元控制器 (Stock Keeping Unit)
|
||||
*/
|
||||
@Api(tags = "「移动端」商品库存")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/sku")
|
||||
@RequiredArgsConstructor
|
||||
public class SkuController {
|
||||
|
||||
private final IPmsSkuService skuService;
|
||||
|
||||
@ApiOperation(value = "获取商品库存信息")
|
||||
@GetMapping("/{skuId}/info")
|
||||
public Result<SkuInfoDTO> getSkuInfo(
|
||||
@ApiParam("SKU ID") @PathVariable Long skuId
|
||||
) {
|
||||
SkuInfoDTO skuInfo = skuService.getSkuInfo(skuId);
|
||||
return Result.success(skuInfo);
|
||||
}
|
||||
|
||||
@ApiOperation("获取商品库存数量")
|
||||
@GetMapping("/{skuId}/stock_num")
|
||||
public Result<Integer> getStockNum(
|
||||
@ApiParam("商品库存单元ID") @PathVariable Long skuId
|
||||
) {
|
||||
Integer stockNum = skuService.getStockNum(skuId);
|
||||
return Result.success(stockNum);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "锁定库存")
|
||||
@PutMapping("/_lock")
|
||||
public Result lockStock(@RequestBody LockStockDTO lockStockDTO) {
|
||||
boolean lockResult = skuService.lockStock(lockStockDTO);
|
||||
return Result.success(lockResult);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "解锁库存")
|
||||
@PutMapping("/_unlock")
|
||||
public Result<Boolean> unlockStock(String orderToken) {
|
||||
boolean result = skuService.unlockStock(orderToken);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "扣减库存")
|
||||
@PutMapping("/_deduct")
|
||||
public Result<Boolean> deductStock(String orderToken) {
|
||||
boolean result = skuService.deductStock(orderToken);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "商品验价")
|
||||
@PostMapping("/price/_check")
|
||||
public Result<Boolean> checkPrice(@RequestBody CheckPriceDTO checkPriceDTO) {
|
||||
boolean result = skuService.checkPrice(checkPriceDTO);
|
||||
return Result.success(result);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.youlai.mall.pms.controller.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsPageVO;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.service.IPmsSpuService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Api(tags = "「移动端」商品信息")
|
||||
@RestController("appSpuController")
|
||||
@RequestMapping("/app-api/v1/spu")
|
||||
@RequiredArgsConstructor
|
||||
public class SpuController {
|
||||
|
||||
private final IPmsSpuService iPmsSpuService;
|
||||
|
||||
@ApiOperation(value = "商品分页列表")
|
||||
@GetMapping("/page")
|
||||
public Result<List<GoodsPageVO>> listGoodsWithPage(SpuPageQuery queryParams) {
|
||||
IPage<GoodsPageVO> result = iPmsSpuService.listAppSpuWithPage(queryParams);
|
||||
return Result.success(result.getRecords(), result.getTotal());
|
||||
}
|
||||
|
||||
@ApiOperation(value = "获取商品详情")
|
||||
@GetMapping("/{spuId}")
|
||||
public Result<GoodsDetailVO> getGoodsDetail(
|
||||
@ApiParam("商品ID") @PathVariable Long spuId
|
||||
) {
|
||||
GoodsDetailVO goodsDetailVO = iPmsSpuService.getAppSpuDetail(spuId);
|
||||
return Result.success(goodsDetailVO);
|
||||
}
|
||||
|
||||
}
|
@ -1,22 +1,24 @@
|
||||
package com.youlai.mall.pms.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品库存单元(SKU)持久层
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/6
|
||||
*/
|
||||
@Mapper
|
||||
public interface PmsSkuMapper extends BaseMapper<PmsSku> {
|
||||
|
||||
@Select("<script>" +
|
||||
" select * from pms_sku where spu_id=#{spuId}" +
|
||||
"</script>")
|
||||
List<PmsSku> listBySpuId(Long spuId);
|
||||
|
||||
|
||||
|
||||
SkuDTO getSkuById(Long id);
|
||||
/**
|
||||
* 获取商品库存单元信息
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
SkuInfoDTO getSkuInfo(Long skuId);
|
||||
}
|
||||
|
@ -3,14 +3,30 @@ package com.youlai.mall.pms.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.pojo.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsPageVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品持久层
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/5
|
||||
*/
|
||||
@Mapper
|
||||
public interface PmsSpuMapper extends BaseMapper<PmsSpu> {
|
||||
|
||||
/**
|
||||
* 「移动端」商品分页列表
|
||||
*
|
||||
* @param page
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
List<GoodsPageVO> listAppSpuWithPage(Page<GoodsPageVO> page, SpuPageQuery queryParams);
|
||||
|
||||
List<PmsSpu> list(Page<PmsSpu> page, String name, Long categoryId);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
package com.youlai.mall.pms.pojo.query;
|
||||
|
||||
import com.youlai.common.base.BasePageQuery;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品分页查询对象
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/5 13:09
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class SpuPageQuery extends BasePageQuery {
|
||||
|
||||
private String keywords;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
private String sortField;
|
||||
|
||||
private String sort;
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.mall.pms.pojo.vo.app;
|
||||
package com.youlai.mall.pms.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
@ -7,7 +7,7 @@ import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品详情页-商品详细信息
|
||||
* 商品详情视图对象
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2021/8/8
|
||||
@ -103,12 +103,10 @@ public class GoodsDetailVO {
|
||||
private Long price;
|
||||
|
||||
@ApiModelProperty("库存")
|
||||
private Integer stock;
|
||||
private Integer stockNum;
|
||||
|
||||
@ApiModelProperty("商品图片URL")
|
||||
private String picUrl;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.youlai.mall.pms.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品分页对象
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2021/8/8
|
||||
*/
|
||||
@ApiModel("商品分页对象")
|
||||
@Data
|
||||
public class GoodsPageVO {
|
||||
|
||||
@ApiModelProperty("商品ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("商品名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("商品价格(单位:分)")
|
||||
private Long price;
|
||||
|
||||
@ApiModelProperty("销量")
|
||||
private Integer sales;
|
||||
|
||||
@ApiModelProperty("图片地址")
|
||||
private String picUrl;
|
||||
|
||||
}
|
@ -2,23 +2,42 @@ package com.youlai.mall.pms.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品库存单元接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/5 17:11
|
||||
*/
|
||||
public interface IPmsSkuService extends IService<PmsSku> {
|
||||
|
||||
/**
|
||||
* 锁定库存
|
||||
* 获取商品的库存数量
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
Result lockStock(List<LockStockDTO> list);
|
||||
Integer getStockNum(Long skuId);
|
||||
|
||||
/**
|
||||
* 锁定库存
|
||||
* 获取商品库存信息
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
// Boolean lockStockTcc(List<LockStockDTO> list);
|
||||
SkuInfoDTO getSkuInfo(Long skuId);
|
||||
|
||||
|
||||
/**
|
||||
* 锁定商品库存
|
||||
*/
|
||||
boolean lockStock(LockStockDTO lockStockDTO);
|
||||
|
||||
/**
|
||||
* 解锁库存
|
||||
@ -30,10 +49,12 @@ public interface IPmsSkuService extends IService<PmsSku> {
|
||||
*/
|
||||
boolean deductStock(String orderToken);
|
||||
|
||||
/**
|
||||
* 获取商品库存数量
|
||||
*/
|
||||
Integer getStockById(Long id);
|
||||
|
||||
SkuDTO getSkuById(Long id);
|
||||
/**
|
||||
* 商品验价
|
||||
*
|
||||
* @param checkPriceDTO
|
||||
* @return
|
||||
*/
|
||||
boolean checkPrice(CheckPriceDTO checkPriceDTO);
|
||||
}
|
||||
|
@ -5,13 +5,46 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.pms.pojo.dto.admin.GoodsFormDTO;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.pojo.vo.admin.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.pojo.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsPageVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 商品业务接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/5
|
||||
*/
|
||||
public interface IPmsSpuService extends IService<PmsSpu> {
|
||||
|
||||
/**
|
||||
* 「移动端」商品分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
IPage<GoodsPageVO> listAppSpuWithPage(SpuPageQuery queryParams);
|
||||
|
||||
|
||||
/**
|
||||
* 「移动端」获取商品详情
|
||||
*
|
||||
* @param spuId
|
||||
* @return
|
||||
*/
|
||||
GoodsDetailVO getAppSpuDetail(Long spuId);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
com.youlai.mall.pms.pojo.vo.admin.GoodsDetailVO getGoodsById(Long id);
|
||||
|
||||
|
||||
IPage<PmsSpu> list(Page<PmsSpu> page, String name,Long categoryId);
|
||||
|
||||
boolean addGoods(GoodsFormDTO goodsFormDTO);
|
||||
@ -20,5 +53,6 @@ public interface IPmsSpuService extends IService<PmsSpu> {
|
||||
|
||||
boolean updateGoods(GoodsFormDTO goodsFormDTO);
|
||||
|
||||
GoodsDetailVO getGoodsById(Long id);
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,26 +1,27 @@
|
||||
package com.youlai.mall.pms.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.mall.pms.common.constant.PmsConstants;
|
||||
import com.youlai.mall.pms.mapper.PmsSkuMapper;
|
||||
import com.youlai.mall.pms.pojo.dto.CheckPriceDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@ -34,75 +35,71 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 创建订单时锁定库存
|
||||
* 获取商品库存数量
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Result lockStock(List<LockStockDTO> skuLockList) {
|
||||
log.info("=======================创建订单,开始锁定商品库存=======================");
|
||||
log.info("锁定商品信息:{}", skuLockList.toString());
|
||||
if (CollectionUtil.isEmpty(skuLockList)) {
|
||||
return Result.failed("锁定的商品列表为空");
|
||||
@Cacheable(cacheNames = "pms", key = "'stock_num:'+#skuId")
|
||||
public Integer getStockNum(Long skuId) {
|
||||
Integer stockNum = 0;
|
||||
PmsSku pmsSku = this.getOne(new LambdaQueryWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, skuId)
|
||||
.select(PmsSku::getStockNum));
|
||||
|
||||
if (pmsSku != null) {
|
||||
stockNum = pmsSku.getStockNum();
|
||||
}
|
||||
//prepareSkuLockList(null, skuLockList);
|
||||
return stockNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定库存 - 订单提交
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean lockStock(LockStockDTO lockStockDTO) {
|
||||
log.info("锁定商品库存:{}", JSONUtil.toJsonStr(lockStockDTO));
|
||||
|
||||
List<LockStockDTO.LockedSku> lockedSkuList = lockStockDTO.getLockedSkuList();
|
||||
Assert.isTrue(CollectionUtil.isNotEmpty(lockedSkuList), "锁定的商品为空");
|
||||
|
||||
// 锁定商品
|
||||
skuLockList.forEach(item -> {
|
||||
RLock lock = redissonClient.getLock(PmsConstants.LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
|
||||
lockedSkuList.forEach(lockedSku -> {
|
||||
RLock lock = redissonClient.getLock(PmsConstants.LOCK_SKU_PREFIX + lockedSku.getSkuId()); // 获取商品的分布式锁
|
||||
lock.lock();
|
||||
boolean result = this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.setSql("locked_stock = locked_stock + " + item.getCount())
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.apply("stock - locked_stock >= {0}", item.getCount())
|
||||
boolean lockResult = this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.setSql("locked_stock_num = locked_stock_num + " + lockedSku.getCount())
|
||||
.eq(PmsSku::getId, lockedSku.getSkuId())
|
||||
.apply("stock_num - locked_stock_num >= {0}", lockedSku.getCount())
|
||||
);
|
||||
if (result) {
|
||||
item.setLocked(true);
|
||||
} else {
|
||||
item.setLocked(false);
|
||||
}
|
||||
if (lockResult) {
|
||||
lock.unlock();
|
||||
} else {
|
||||
throw new BizException("锁定商品" + lockedSku.getSkuId() + "失败");
|
||||
}
|
||||
});
|
||||
|
||||
// 锁定失败的商品集合
|
||||
List<LockStockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(unlockSkuList)) {
|
||||
// 恢复已被锁定的库存
|
||||
List<LockStockDTO> lockSkuList = skuLockList.stream().filter(LockStockDTO::getLocked).collect(Collectors.toList());
|
||||
lockSkuList.forEach(item ->
|
||||
this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.setSql("locked_stock = locked_stock - " + item.getCount()))
|
||||
);
|
||||
// 提示订单哪些商品库存不足
|
||||
String ids= unlockSkuList.stream().map(sku -> sku.getSkuId().toString()).collect(Collectors.joining(","));
|
||||
return Result.failed("商品" + ids + "库存不足");
|
||||
}
|
||||
|
||||
// 将锁定的商品保存至Redis中
|
||||
String orderToken = skuLockList.get(0).getOrderToken();
|
||||
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订单超时关单解锁库存
|
||||
*/
|
||||
@Override
|
||||
public boolean unlockStock(String orderToken) {
|
||||
log.info("=======================订单超时未支付系统自动关单释放库存=======================");
|
||||
String json = redisTemplate.opsForValue().get(PmsConstants.LOCKED_STOCK_PREFIX + orderToken);
|
||||
log.info("释放库存信息:{}", json);
|
||||
if (StrUtil.isBlank(json)) {
|
||||
// 将锁定商品库存信息保存至Redis
|
||||
String orderToken = lockStockDTO.getOrderToken();
|
||||
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(lockedSkuList));
|
||||
return true;
|
||||
}
|
||||
|
||||
List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
|
||||
|
||||
skuLockList.forEach(item ->
|
||||
/**
|
||||
* 释放库存 - 订单超时未支付
|
||||
*/
|
||||
@Override
|
||||
public boolean unlockStock(String orderToken) {
|
||||
log.info("释放库存,orderToken:{}", orderToken);
|
||||
String lockedSkuJsonStr = redisTemplate.opsForValue().get(PmsConstants.LOCKED_STOCK_PREFIX + orderToken);
|
||||
List<LockStockDTO.LockedSku> lockedSkuList = JSONUtil.toList(lockedSkuJsonStr, LockStockDTO.LockedSku.class);
|
||||
lockedSkuList.forEach(item ->
|
||||
this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.setSql("locked_stock = locked_stock - " + item.getCount()))
|
||||
.setSql("locked_stock_num = locked_stock_num - " + item.getCount()))
|
||||
);
|
||||
|
||||
// 删除redis中锁定的库存
|
||||
@ -111,24 +108,19 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付成功时扣减库存
|
||||
* 扣减库存 - 支付成功
|
||||
*/
|
||||
@Override
|
||||
public boolean deductStock(String orderToken) {
|
||||
log.info("=======================支付成功扣减订单中商品库存=======================");
|
||||
String json = redisTemplate.opsForValue().get(PmsConstants.LOCKED_STOCK_PREFIX + orderToken);
|
||||
log.info("订单商品信息:{}", json);
|
||||
if (StrUtil.isBlank(json)) {
|
||||
return true;
|
||||
}
|
||||
log.info("扣减库存,orderToken:{}",orderToken);
|
||||
String lockedSkuJsonStr = redisTemplate.opsForValue().get(PmsConstants.LOCKED_STOCK_PREFIX + orderToken);
|
||||
List<LockStockDTO.LockedSku> lockedSkuList = JSONUtil.toList(lockedSkuJsonStr, LockStockDTO.LockedSku.class);
|
||||
|
||||
List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
|
||||
|
||||
skuLockList.forEach(item -> {
|
||||
lockedSkuList.forEach(item -> {
|
||||
boolean result = this.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.setSql("stock = stock - " + item.getCount()) // 扣减库存
|
||||
.setSql("locked_stock = locked_stock - " + item.getCount())
|
||||
.setSql("stock_num = stock_num - " + item.getCount())
|
||||
.setSql("locked_stock_num = locked_stock_num - " + item.getCount())
|
||||
);
|
||||
if (!result) {
|
||||
throw new BizException("扣减库存失败,商品" + item.getSkuId() + "库存不足");
|
||||
@ -140,54 +132,49 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cache-Aside pattern 缓存、数据库读写模式
|
||||
* 1. 读取数据,先读缓存,没有就去读数据库,然后将结果写入缓存
|
||||
* 2. 写入数据,先更新数据库,再删除缓存
|
||||
* 商品验价
|
||||
*
|
||||
* @param id 库存ID
|
||||
* @param checkPriceDTO
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Integer getStockById(Long id) {
|
||||
Integer stock = 0;
|
||||
// 读->缓存
|
||||
Object cacheVal = redisTemplate.opsForValue().get(PmsConstants.LOCKED_STOCK_PREFIX + id);
|
||||
if (cacheVal != null) {
|
||||
stock = Convert.toInt(cacheVal);
|
||||
return stock;
|
||||
}
|
||||
|
||||
// 读->数据库
|
||||
PmsSku pmsSku = this.getOne(new LambdaQueryWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, id)
|
||||
.select(PmsSku::getStock));
|
||||
public boolean checkPrice(CheckPriceDTO checkPriceDTO) {
|
||||
Long orderTotalAmount = checkPriceDTO.getOrderTotalAmount(); // 订单总金额
|
||||
|
||||
// 计算商品总金额
|
||||
List<CheckPriceDTO.CheckSku> checkOrderItems = checkPriceDTO.getCheckSkus();
|
||||
if (CollectionUtil.isNotEmpty(checkOrderItems)) {
|
||||
List<Long> skuIds = checkOrderItems.stream()
|
||||
.map(orderItem -> orderItem.getSkuId()).collect(Collectors.toList());
|
||||
List<PmsSku> skuList = this.list(new LambdaQueryWrapper<PmsSku>().in(PmsSku::getId, skuIds)
|
||||
.select(PmsSku::getId, PmsSku::getPrice));
|
||||
// 商品总金额
|
||||
Long skuTotalAmount = checkOrderItems.stream().map(checkOrderItem -> {
|
||||
Long skuId = checkOrderItem.getSkuId();
|
||||
PmsSku pmsSku = skuList.stream().filter(sku -> sku.getId().equals(skuId)).findFirst().orElse(null);
|
||||
if (pmsSku != null) {
|
||||
stock = pmsSku.getStock();
|
||||
// 写->缓存
|
||||
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + id, String.valueOf(stock));
|
||||
return pmsSku.getPrice() * checkOrderItem.getCount();
|
||||
}
|
||||
return 0L;
|
||||
}).reduce(0L, Long::sum);
|
||||
return orderTotalAmount.compareTo(skuTotalAmount) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return stock;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品库存信息
|
||||
*
|
||||
* @param skuId
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public SkuDTO getSkuById(Long id) {
|
||||
return this.baseMapper.getSkuById(id);
|
||||
public SkuInfoDTO getSkuInfo(Long skuId) {
|
||||
SkuInfoDTO skuInfo = this.baseMapper.getSkuInfo(skuId);
|
||||
return skuInfo;
|
||||
}
|
||||
|
||||
|
||||
/* private final SeataTccSkuService seataTccSkuService;
|
||||
|
||||
@Override
|
||||
@GlobalTransactional
|
||||
public Boolean lockStockTcc(List<LockStockDTO> skuLockList) {
|
||||
seataTccSkuService.prepareSkuLockList(null, skuLockList);
|
||||
String orderToken = skuLockList.get(0).getOrderToken();
|
||||
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
|
||||
return true;
|
||||
}*/
|
||||
|
||||
}
|
||||
|
@ -5,23 +5,26 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.pms.common.constant.PmsConstants;
|
||||
import com.youlai.mall.pms.common.enums.AttributeTypeEnum;
|
||||
import com.youlai.mall.pms.component.BloomRedisService;
|
||||
import com.youlai.mall.pms.mapper.PmsSpuMapper;
|
||||
import com.youlai.mall.pms.pojo.dto.admin.GoodsFormDTO;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpuAttributeValue;
|
||||
import com.youlai.mall.pms.pojo.vo.admin.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.pojo.query.SpuPageQuery;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.pojo.vo.GoodsPageVO;
|
||||
import com.youlai.mall.pms.pojo.vo.ProductHistoryVO;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import com.youlai.mall.pms.service.IPmsSpuAttributeValueService;
|
||||
import com.youlai.mall.pms.service.IPmsSpuService;
|
||||
import com.youlai.mall.ums.api.MemberFeignClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -31,14 +34,166 @@ import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 商品业务实现类
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2021/08/08
|
||||
* @date 2021/8/8
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> implements IPmsSpuService {
|
||||
private final IPmsSkuService iPmsSkuService;
|
||||
private final IPmsSpuAttributeValueService iPmsSpuAttributeValueService;
|
||||
|
||||
private final IPmsSkuService skuService;
|
||||
private final IPmsSpuAttributeValueService spuAttributeValueService;
|
||||
private final MemberFeignClient memberFeignClient;
|
||||
|
||||
/**
|
||||
* 「移动端」商品分页列表
|
||||
*
|
||||
* @param queryParams
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public IPage<GoodsPageVO> listAppSpuWithPage(SpuPageQuery queryParams) {
|
||||
Page<GoodsPageVO> page = new Page<>(queryParams.getPageNum(), queryParams.getPageSize());
|
||||
List<GoodsPageVO> list = this.baseMapper.listAppSpuWithPage(page, queryParams);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 「移动端」获取商品详情
|
||||
*
|
||||
* @param spuId 商品ID
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public GoodsDetailVO getAppSpuDetail(Long spuId) {
|
||||
PmsSpu pmsSpu = this.getById(spuId);
|
||||
Assert.isTrue(pmsSpu != null, "商品不存在");
|
||||
|
||||
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
|
||||
|
||||
// 商品基本信息
|
||||
GoodsDetailVO.GoodsInfo goodsInfo = new GoodsDetailVO.GoodsInfo();
|
||||
BeanUtil.copyProperties(pmsSpu, goodsInfo, "album");
|
||||
|
||||
List<String> album = new ArrayList<>();
|
||||
|
||||
if (StrUtil.isNotBlank(pmsSpu.getPicUrl())) {
|
||||
album.add(pmsSpu.getPicUrl());
|
||||
}
|
||||
if (pmsSpu.getAlbum() != null && pmsSpu.getAlbum().length > 0) {
|
||||
album.addAll(Arrays.asList(pmsSpu.getAlbum()));
|
||||
goodsInfo.setAlbum(album);
|
||||
}
|
||||
goodsDetailVO.setGoodsInfo(goodsInfo);
|
||||
|
||||
// 商品属性列表
|
||||
List<GoodsDetailVO.Attribute> attributeList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
|
||||
.eq(PmsSpuAttributeValue::getSpuId, spuId)
|
||||
.select(PmsSpuAttributeValue::getId, PmsSpuAttributeValue::getName, PmsSpuAttributeValue::getValue)
|
||||
).stream().map(item -> {
|
||||
GoodsDetailVO.Attribute attribute = new GoodsDetailVO.Attribute();
|
||||
BeanUtil.copyProperties(item, attribute);
|
||||
return attribute;
|
||||
}).collect(Collectors.toList());
|
||||
goodsDetailVO.setAttributeList(attributeList);
|
||||
|
||||
|
||||
// 商品规格列表
|
||||
List<PmsSpuAttributeValue> specSourceList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
|
||||
.eq(PmsSpuAttributeValue::getSpuId, spuId)
|
||||
.select(PmsSpuAttributeValue::getId, PmsSpuAttributeValue::getName, PmsSpuAttributeValue::getValue)
|
||||
);
|
||||
|
||||
List<GoodsDetailVO.Specification> specList = new ArrayList<>();
|
||||
// 规格Map [key:"颜色",value:[{id:1,value:"黑"},{id:2,value:"白"}]]
|
||||
Map<String, List<PmsSpuAttributeValue>> specValueMap = specSourceList.stream()
|
||||
.collect(Collectors.groupingBy(PmsSpuAttributeValue::getName));
|
||||
|
||||
for (Map.Entry<String, List<PmsSpuAttributeValue>> entry : specValueMap.entrySet()) {
|
||||
String specName = entry.getKey();
|
||||
List<PmsSpuAttributeValue> specValueSourceList = entry.getValue();
|
||||
|
||||
// 规格映射处理
|
||||
GoodsDetailVO.Specification spec = new GoodsDetailVO.Specification();
|
||||
spec.setName(specName);
|
||||
if (CollectionUtil.isNotEmpty(specValueSourceList)) {
|
||||
List<GoodsDetailVO.Specification.Value> specValueList = specValueSourceList.stream().map(item -> {
|
||||
GoodsDetailVO.Specification.Value specValue = new GoodsDetailVO.Specification.Value();
|
||||
specValue.setId(item.getId());
|
||||
specValue.setValue(item.getValue());
|
||||
return specValue;
|
||||
}).collect(Collectors.toList());
|
||||
spec.setValues(specValueList);
|
||||
specList.add(spec);
|
||||
}
|
||||
}
|
||||
goodsDetailVO.setSpecList(specList);
|
||||
|
||||
// 商品SKU列表
|
||||
List<PmsSku> skuSourceList = skuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, spuId));
|
||||
if (CollectionUtil.isNotEmpty(skuSourceList)) {
|
||||
List<GoodsDetailVO.Sku> skuList = skuSourceList.stream().map(item -> {
|
||||
GoodsDetailVO.Sku sku = new GoodsDetailVO.Sku();
|
||||
BeanUtil.copyProperties(item, sku);
|
||||
return sku;
|
||||
}).collect(Collectors.toList());
|
||||
goodsDetailVO.setSkuList(skuList);
|
||||
}
|
||||
|
||||
// 添加用户浏览历史记录
|
||||
Long loginUserId = MemberUtils.getMemberId();
|
||||
if (loginUserId != null) {
|
||||
ProductHistoryVO vo = new ProductHistoryVO();
|
||||
vo.setId(goodsInfo.getId());
|
||||
vo.setName(goodsInfo.getName());
|
||||
vo.setPicUrl(goodsInfo.getAlbum() != null ? goodsInfo.getAlbum().get(0) : null);
|
||||
memberFeignClient.addProductViewHistory(vo);
|
||||
}
|
||||
return goodsDetailVO;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品(SPU)详情
|
||||
*
|
||||
* @param id 商品(SPU)ID
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public com.youlai.mall.pms.pojo.vo.admin.GoodsDetailVO getGoodsById(Long id) {
|
||||
com.youlai.mall.pms.pojo.vo.admin.GoodsDetailVO goodsDetailVO = new com.youlai.mall.pms.pojo.vo.admin.GoodsDetailVO();
|
||||
|
||||
PmsSpu spu = this.getById(id);
|
||||
Assert.isTrue(spu != null, "商品不存在");
|
||||
|
||||
BeanUtil.copyProperties(spu, goodsDetailVO);
|
||||
|
||||
// 商品属性列表
|
||||
List<PmsSpuAttributeValue> attrList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getSpuId, id)
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
|
||||
);
|
||||
goodsDetailVO.setAttrList(attrList);
|
||||
|
||||
// 商品规格列表
|
||||
List<PmsSpuAttributeValue> specList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getSpuId, id)
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
|
||||
);
|
||||
goodsDetailVO.setSpecList(specList);
|
||||
|
||||
// 商品SKU列表
|
||||
List<PmsSku> skuList = skuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, id));
|
||||
goodsDetailVO.setSkuList(skuList);
|
||||
return goodsDetailVO;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 商品分页列表
|
||||
@ -105,42 +260,6 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
return saveResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品(SPU)详情
|
||||
*
|
||||
* @param id 商品(SPU)ID
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public GoodsDetailVO getGoodsById(Long id) {
|
||||
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
|
||||
|
||||
PmsSpu spu = this.getById(id);
|
||||
Assert.isTrue(spu != null, "商品不存在");
|
||||
|
||||
BeanUtil.copyProperties(spu, goodsDetailVO);
|
||||
|
||||
// 商品属性列表
|
||||
List<PmsSpuAttributeValue> attrList = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getSpuId, id)
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
|
||||
);
|
||||
goodsDetailVO.setAttrList(attrList);
|
||||
|
||||
// 商品规格列表
|
||||
List<PmsSpuAttributeValue> specList = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getSpuId, id)
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
|
||||
);
|
||||
goodsDetailVO.setSpecList(specList);
|
||||
|
||||
// 商品SKU列表
|
||||
List<PmsSku> skuList = iPmsSkuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, id));
|
||||
goodsDetailVO.setSkuList(skuList);
|
||||
return goodsDetailVO;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量删除商品(SPU)
|
||||
@ -154,11 +273,11 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
boolean result = true;
|
||||
for (Long goodsId : goodsIds) {
|
||||
// sku
|
||||
iPmsSkuService.remove(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, goodsId));
|
||||
skuService.remove(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, goodsId));
|
||||
// 规格
|
||||
iPmsSpuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getId, goodsId));
|
||||
spuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getId, goodsId));
|
||||
// 属性
|
||||
iPmsSpuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getSpuId, goodsId));
|
||||
spuAttributeValueService.remove(new LambdaQueryWrapper<PmsSpuAttributeValue>().eq(PmsSpuAttributeValue::getSpuId, goodsId));
|
||||
// spu
|
||||
result = this.removeById(goodsId);
|
||||
}
|
||||
@ -195,7 +314,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
// 删除SKU
|
||||
List<Long> formSkuIds = skuList.stream().map(PmsSku::getId).collect(Collectors.toList());
|
||||
|
||||
List<Long> dbSkuIds = iPmsSkuService.list(new LambdaQueryWrapper<PmsSku>()
|
||||
List<Long> dbSkuIds = skuService.list(new LambdaQueryWrapper<PmsSku>()
|
||||
.eq(PmsSku::getSpuId, goodsId)
|
||||
.select(PmsSku::getId))
|
||||
.stream().map(PmsSku::getId)
|
||||
@ -204,7 +323,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
List<Long> removeSkuIds = dbSkuIds.stream().filter(dbSkuId -> !formSkuIds.contains(dbSkuId)).collect(Collectors.toList());
|
||||
|
||||
if (CollectionUtil.isNotEmpty(removeSkuIds)) {
|
||||
iPmsSkuService.removeByIds(removeSkuIds);
|
||||
skuService.removeByIds(removeSkuIds);
|
||||
}
|
||||
|
||||
// 新增/修改SKU
|
||||
@ -219,7 +338,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
sku.setSpuId(goodsId);
|
||||
return sku;
|
||||
}).collect(Collectors.toList());
|
||||
return iPmsSkuService.saveOrUpdateBatch(pmsSkuList);
|
||||
return skuService.saveOrUpdateBatch(pmsSkuList);
|
||||
}
|
||||
|
||||
|
||||
@ -236,7 +355,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
.map(item -> Convert.toLong(item.getId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> dbAttrValIds = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
List<Long> dbAttrValIds = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getSpuId, goodsId)
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
|
||||
.select(PmsSpuAttributeValue::getId)
|
||||
@ -244,7 +363,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
|
||||
List<Long> removeAttrValIds = dbAttrValIds.stream().filter(id -> !formAttrValIds.contains(id)).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(removeAttrValIds)) {
|
||||
iPmsSpuAttributeValueService.removeByIds(removeAttrValIds);
|
||||
spuAttributeValueService.removeByIds(removeAttrValIds);
|
||||
}
|
||||
|
||||
// 新增或修改商品属性
|
||||
@ -256,7 +375,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
return pmsSpuAttributeValue;
|
||||
}).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(pmsSpuAttributeValueList)) {
|
||||
return iPmsSpuAttributeValueService.saveOrUpdateBatch(pmsSpuAttributeValueList);
|
||||
return spuAttributeValueService.saveOrUpdateBatch(pmsSpuAttributeValueList);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -276,7 +395,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
.map(item -> Convert.toLong(item.getId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Long> dbSpecValIds = iPmsSpuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
List<Long> dbSpecValIds = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getSpuId, goodsId)
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
|
||||
.select(PmsSpuAttributeValue::getId)
|
||||
@ -284,7 +403,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
|
||||
List<Long> removeAttrValIds = dbSpecValIds.stream().filter(id -> !formSpecValIds.contains(id)).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(removeAttrValIds)) {
|
||||
iPmsSpuAttributeValueService.removeByIds(removeAttrValIds);
|
||||
spuAttributeValueService.removeByIds(removeAttrValIds);
|
||||
}
|
||||
// 新增规格
|
||||
Map<String, Long> tempIdIdMap = new HashMap<>();
|
||||
@ -296,7 +415,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
BeanUtil.copyProperties(item, specification, "id");
|
||||
specification.setSpuId(goodsId);
|
||||
specification.setType(AttributeTypeEnum.SPECIFICATION.getValue());
|
||||
iPmsSpuAttributeValueService.save(specification);
|
||||
spuAttributeValueService.save(specification);
|
||||
tempIdIdMap.put(item.getId(), specification.getId());
|
||||
});
|
||||
}
|
||||
@ -311,7 +430,7 @@ public class PmsSpuServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> impleme
|
||||
return pmsSpuAttributeValue;
|
||||
}).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(pmsSpuAttributeValueList)) {
|
||||
iPmsSpuAttributeValueService.updateBatchById(pmsSpuAttributeValueList);
|
||||
spuAttributeValueService.updateBatchById(pmsSpuAttributeValueList);
|
||||
}
|
||||
return tempIdIdMap;
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
package com.youlai.mall.pms.serviceapp;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.pojo.vo.app.GoodsDetailVO;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2021/8/8
|
||||
*/
|
||||
public interface IGoodsService extends IService<PmsSpu> {
|
||||
GoodsDetailVO getGoodsById(Long id);
|
||||
|
||||
GoodsDetailVO getGoodsBySkuId(Long skuId);
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package com.youlai.mall.pms.serviceapp.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.mall.pms.common.enums.AttributeTypeEnum;
|
||||
import com.youlai.mall.pms.mapper.PmsSpuMapper;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpu;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSpuAttributeValue;
|
||||
import com.youlai.mall.pms.pojo.vo.ProductHistoryVO;
|
||||
import com.youlai.mall.pms.pojo.vo.app.GoodsDetailVO;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import com.youlai.mall.pms.service.IPmsSpuAttributeValueService;
|
||||
import com.youlai.mall.pms.serviceapp.IGoodsService;
|
||||
import com.youlai.mall.ums.api.MemberFeignClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2021/8/8
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GoodsServiceImpl extends ServiceImpl<PmsSpuMapper, PmsSpu> implements IGoodsService {
|
||||
|
||||
final IPmsSpuAttributeValueService spuAttributeValueService;
|
||||
final IPmsSkuService skuService;
|
||||
final MemberFeignClient memberFeignClient;
|
||||
|
||||
@Override
|
||||
public GoodsDetailVO getGoodsById(Long goodsId) {
|
||||
|
||||
GoodsDetailVO goodsDetailVO = new GoodsDetailVO();
|
||||
PmsSpu pmsSpu = this.baseMapper.selectById(goodsId);
|
||||
Assert.isTrue(pmsSpu != null, "商品不存在");
|
||||
// 商品基本信息
|
||||
GoodsDetailVO.GoodsInfo goodsInfo = new GoodsDetailVO.GoodsInfo();
|
||||
BeanUtil.copyProperties(pmsSpu, goodsInfo, "album");
|
||||
|
||||
List<String> album = new ArrayList<>();
|
||||
|
||||
if (StrUtil.isNotBlank(pmsSpu.getPicUrl())) {
|
||||
album.add(pmsSpu.getPicUrl());
|
||||
}
|
||||
if (pmsSpu.getAlbum() != null && pmsSpu.getAlbum().length > 0) {
|
||||
album.addAll(Arrays.asList(pmsSpu.getAlbum()));
|
||||
goodsInfo.setAlbum(album);
|
||||
}
|
||||
goodsDetailVO.setGoodsInfo(goodsInfo);
|
||||
|
||||
// 商品属性列表
|
||||
List<GoodsDetailVO.Attribute> attributeList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.ATTRIBUTE.getValue())
|
||||
.eq(PmsSpuAttributeValue::getSpuId, goodsId)
|
||||
.select(PmsSpuAttributeValue::getId, PmsSpuAttributeValue::getName, PmsSpuAttributeValue::getValue)
|
||||
).stream().map(item -> {
|
||||
GoodsDetailVO.Attribute attribute = new GoodsDetailVO.Attribute();
|
||||
BeanUtil.copyProperties(item, attribute);
|
||||
return attribute;
|
||||
}).collect(Collectors.toList());
|
||||
goodsDetailVO.setAttributeList(attributeList);
|
||||
|
||||
|
||||
// 商品规格列表
|
||||
List<PmsSpuAttributeValue> specSourceList = spuAttributeValueService.list(new LambdaQueryWrapper<PmsSpuAttributeValue>()
|
||||
.eq(PmsSpuAttributeValue::getType, AttributeTypeEnum.SPECIFICATION.getValue())
|
||||
.eq(PmsSpuAttributeValue::getSpuId, goodsId)
|
||||
.select(PmsSpuAttributeValue::getId, PmsSpuAttributeValue::getName, PmsSpuAttributeValue::getValue)
|
||||
);
|
||||
|
||||
List<GoodsDetailVO.Specification> specList = new ArrayList<>();
|
||||
// 规格Map [key:"颜色",value:[{id:1,value:"黑"},{id:2,value:"白"}]]
|
||||
Map<String, List<PmsSpuAttributeValue>> specValueMap = specSourceList.stream()
|
||||
.collect(Collectors.groupingBy(PmsSpuAttributeValue::getName));
|
||||
|
||||
for (Map.Entry<String, List<PmsSpuAttributeValue>> entry : specValueMap.entrySet()) {
|
||||
String specName = entry.getKey();
|
||||
List<PmsSpuAttributeValue> specValueSourceList = entry.getValue();
|
||||
|
||||
// 规格映射处理
|
||||
GoodsDetailVO.Specification spec = new GoodsDetailVO.Specification();
|
||||
spec.setName(specName);
|
||||
if (CollectionUtil.isNotEmpty(specValueSourceList)) {
|
||||
List<GoodsDetailVO.Specification.Value> specValueList = specValueSourceList.stream().map(item -> {
|
||||
GoodsDetailVO.Specification.Value specValue = new GoodsDetailVO.Specification.Value();
|
||||
specValue.setId(item.getId());
|
||||
specValue.setValue(item.getValue());
|
||||
return specValue;
|
||||
}).collect(Collectors.toList());
|
||||
spec.setValues(specValueList);
|
||||
specList.add(spec);
|
||||
}
|
||||
}
|
||||
goodsDetailVO.setSpecList(specList);
|
||||
// 商品SKU列表
|
||||
List<PmsSku> skuSourceList = skuService.list(new LambdaQueryWrapper<PmsSku>().eq(PmsSku::getSpuId, goodsId));
|
||||
if (CollectionUtil.isNotEmpty(skuSourceList)) {
|
||||
List<GoodsDetailVO.Sku> skuList = skuSourceList.stream().map(item -> {
|
||||
GoodsDetailVO.Sku sku = new GoodsDetailVO.Sku();
|
||||
BeanUtil.copyProperties(item, sku);
|
||||
return sku;
|
||||
}).collect(Collectors.toList());
|
||||
goodsDetailVO.setSkuList(skuList);
|
||||
}
|
||||
// 添加用户浏览历史记录
|
||||
ProductHistoryVO vo = new ProductHistoryVO();
|
||||
vo.setId(goodsInfo.getId());
|
||||
vo.setName(goodsInfo.getName());
|
||||
vo.setPicUrl(goodsInfo.getAlbum() != null ? goodsInfo.getAlbum().get(0) : null);
|
||||
memberFeignClient.addProductViewHistory(vo);
|
||||
return goodsDetailVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoodsDetailVO getGoodsBySkuId(Long skuId) {
|
||||
PmsSku skuInfo = skuService.getById(skuId);
|
||||
if (null == skuInfo) {
|
||||
throw new BizException("商品不存在");
|
||||
}
|
||||
Long spuId = skuInfo.getSpuId();
|
||||
return getGoodsById(spuId);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.youlai.mall.pms.tcc.idempotent;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
/**
|
||||
* @Author DaniR
|
||||
* @Description TCC幂等工具类
|
||||
* @Date 2021/7/15 20:38
|
||||
**/
|
||||
public class IdempotentUtil {
|
||||
|
||||
private static Table<Class<?>,String,Long> map= HashBasedTable.create();
|
||||
|
||||
public static void addMarker(Class<?> clazz,String xid,Long marker){
|
||||
map.put(clazz,xid,marker);
|
||||
}
|
||||
|
||||
public static Long getMarker(Class<?> clazz,String xid){
|
||||
return map.get(clazz,xid);
|
||||
}
|
||||
|
||||
public static void removeMarker(Class<?> clazz,String xid){
|
||||
map.remove(clazz,xid);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package com.youlai.mall.pms.tcc.service;
|
||||
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||
import io.seata.rm.tcc.api.BusinessActionContextParameter;
|
||||
import io.seata.rm.tcc.api.LocalTCC;
|
||||
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@LocalTCC
|
||||
public interface SeataTccSkuService {
|
||||
|
||||
@TwoPhaseBusinessAction(name = "prepareSkuLockList", commitMethod = "commitSkuLockList", rollbackMethod = "rollbackSkuLockList")
|
||||
boolean prepareSkuLockList(BusinessActionContext businessActionContext,
|
||||
@BusinessActionContextParameter(paramName = "skuLockList") List<LockStockDTO> skuLockList);
|
||||
|
||||
boolean commitSkuLockList(BusinessActionContext businessActionContext);
|
||||
|
||||
boolean rollbackSkuLockList(BusinessActionContext businessActionContext);
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package com.youlai.mall.pms.tcc.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.mall.pms.common.constant.PmsConstants;
|
||||
import com.youlai.mall.pms.pojo.dto.app.LockStockDTO;
|
||||
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||
import com.youlai.mall.pms.tcc.idempotent.IdempotentUtil;
|
||||
import com.youlai.mall.pms.tcc.service.SeataTccSkuService;
|
||||
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SeataTccSkuServiceImpl implements SeataTccSkuService {
|
||||
|
||||
@Autowired
|
||||
private IPmsSkuService iPmsSkuService;
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean prepareSkuLockList(BusinessActionContext businessActionContext, List<LockStockDTO> skuLockList) {
|
||||
|
||||
if (Objects.nonNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtil.isEmpty(skuLockList)) {
|
||||
throw new BizException("锁定的商品列表为空");
|
||||
}
|
||||
// 锁定商品
|
||||
skuLockList.forEach(item -> {
|
||||
RLock lock = redissonClient.getLock(PmsConstants.LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
|
||||
lock.lock();
|
||||
boolean result = iPmsSkuService.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.setSql("locked_stock = locked_stock + " + item.getCount())
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.apply("stock - locked_stock >= {0}", item.getCount())
|
||||
);
|
||||
item.setLocked(result);
|
||||
lock.unlock();
|
||||
});
|
||||
// 锁定失败的商品集合
|
||||
List<LockStockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(unlockSkuList)) {
|
||||
// 恢复已被锁定的库存
|
||||
List<LockStockDTO> lockSkuList = skuLockList.stream().filter(LockStockDTO::getLocked).collect(Collectors.toList());
|
||||
lockSkuList.forEach(item ->
|
||||
iPmsSkuService.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.setSql("locked_stock = locked_stock - " + item.getCount()))
|
||||
);
|
||||
// 提示订单哪些商品库存不足
|
||||
List<Long> ids = unlockSkuList.stream().map(LockStockDTO::getSkuId).collect(Collectors.toList());
|
||||
throw new BizException("商品" + ids.toString() + "库存不足");
|
||||
}
|
||||
IdempotentUtil.addMarker(getClass(), businessActionContext.getXid(), System.currentTimeMillis());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public boolean commitSkuLockList(BusinessActionContext businessActionContext) {
|
||||
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||
return true;
|
||||
}
|
||||
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public boolean rollbackSkuLockList(BusinessActionContext businessActionContext) {
|
||||
|
||||
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||
return true;
|
||||
}
|
||||
JSONArray jsonObjectList = (JSONArray) businessActionContext.getActionContext("skuLockList");
|
||||
|
||||
List<LockStockDTO> skuLockList = JSONUtil.toList(jsonObjectList,LockStockDTO.class);
|
||||
skuLockList.forEach(item ->
|
||||
iPmsSkuService.update(new LambdaUpdateWrapper<PmsSku>()
|
||||
.eq(PmsSku::getId, item.getSkuId())
|
||||
.setSql("locked_stock = locked_stock - " + item.getCount()))
|
||||
);
|
||||
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||
return true;
|
||||
}
|
||||
}
|
@ -14,7 +14,10 @@ spring:
|
||||
server-addr: http://localhost:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
# 本地启动
|
||||
## server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
# 极速启动
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
file-extension: yaml
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
|
@ -11,11 +11,11 @@ spring:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
# 公共配置
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
|
@ -4,31 +4,29 @@
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.youlai.mall.pms.mapper.PmsSkuMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.youlai.mall.pms.pojo.entity.PmsSku">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="spuId" column="spu_id" jdbcType="BIGINT"/>
|
||||
<result property="name" column="name" jdbcType="VARCHAR"/>
|
||||
<result property="sn" column="sn" jdbcType="VARCHAR"/>
|
||||
<result property="picUrl" column="pic_url" jdbcType="VARCHAR"/>
|
||||
<result property="specIds" column="specs" jdbcType="VARCHAR"/>
|
||||
<result property="price" column="price" jdbcType="BIGINT"/>
|
||||
<result property="stock" column="stock" jdbcType="INTEGER"/>
|
||||
<result property="lockedStock" column="locked_stock" jdbcType="INTEGER"/>
|
||||
<result property="gmtCreate" column="gmt_create" jdbcType="TIMESTAMP"/>
|
||||
<result property="gmtModified" column="gmt_modified" jdbcType="TIMESTAMP"/>
|
||||
</resultMap>
|
||||
<!-- 根据商品ID获取商品库存单元列表 -->
|
||||
<select id="listSkuBySpuId" resultType="com.youlai.mall.pms.pojo.entity.PmsSku">
|
||||
SELECT id ,
|
||||
NAME ,
|
||||
sku_sn,
|
||||
pic_url ,
|
||||
spec_ids,
|
||||
price ,
|
||||
stock_num
|
||||
FROM pms_sku
|
||||
WHERE spu_id = #{id}
|
||||
</select>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id
|
||||
,spu_id,name,
|
||||
sn,pic_url,specs,
|
||||
origin_price,price,stock,
|
||||
locked_stock,gmt_create,gmt_modified
|
||||
</sql>
|
||||
|
||||
|
||||
<select id="getSkuById" resultType="com.youlai.mall.pms.pojo.dto.app.SkuDTO">
|
||||
select t1.id, t1.sn, t1.name, t1.pic_url, t1.price, (t1.stock - t1.locked_stock) as stock, t2.name as goodsName
|
||||
<!-- 获取商品库存单元信息 -->
|
||||
<select id="getSkuInfo" resultType="com.youlai.mall.pms.pojo.dto.SkuInfoDTO">
|
||||
select
|
||||
t1.id skuId,
|
||||
t1.sku_sn,
|
||||
t1.name skuName,
|
||||
t1.pic_url,
|
||||
t1.price,
|
||||
t1.stock_num,
|
||||
t2.name as spuName
|
||||
from pms_sku t1
|
||||
left join pms_spu t2 on t1.spu_id = t2.id
|
||||
where t1.id = #{id}
|
||||
|
@ -3,6 +3,7 @@
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.youlai.mall.pms.mapper.PmsSpuMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.youlai.mall.pms.pojo.entity.PmsSpu">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="name" column="name" jdbcType="VARCHAR"/>
|
||||
@ -21,15 +22,15 @@
|
||||
<result property="gmtModified" column="gmt_modified" jdbcType="TIMESTAMP"/>
|
||||
<result property="categoryName" column="categoryName" jdbcType="VARCHAR"/>
|
||||
<result property="brandName" column="brandName" jdbcType="VARCHAR"/>
|
||||
<collection property="skuList" column="id" select="getSkuListBySpuId">
|
||||
<collection property="skuList" column="id" select="com.youlai.mall.pms.mapper.PmsSkuMapper.listSkuBySpuId">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="name" column="name" jdbcType="VARCHAR"/>
|
||||
<result property="sn" column="sn" jdbcType="VARCHAR"/>
|
||||
<result property="skuSn" column="sku_sn" jdbcType="VARCHAR"/>
|
||||
<result property="picUrl" column="pic_url" jdbcType="VARCHAR"/>
|
||||
<result property="specIds" column="spec_ids" jdbcType="VARCHAR"/>
|
||||
<result property="price" column="price" jdbcType="BIGINT"/>
|
||||
<result property="stock" column="stock" jdbcType="INTEGER"/>
|
||||
<result property="lockedStock" column="locked_stock" jdbcType="INTEGER"/>
|
||||
<result property="stockNum" column="stock_num" jdbcType="INTEGER"/>
|
||||
<result property="lockedStockNum" column="locked_stock_num" jdbcType="INTEGER"/>
|
||||
</collection>
|
||||
</resultMap>
|
||||
|
||||
@ -70,15 +71,28 @@
|
||||
</select>
|
||||
|
||||
|
||||
<select id="getSkuListBySpuId" resultType="com.youlai.mall.pms.pojo.entity.PmsSku">
|
||||
SELECT id ,
|
||||
<!--「移动端」商品分页列表-->
|
||||
<select id="listAppSpuWithPage" resultType="com.youlai.mall.pms.pojo.vo.GoodsPageVO">
|
||||
SELECT
|
||||
id,
|
||||
NAME,
|
||||
sn,
|
||||
pic_url,
|
||||
spec_ids,
|
||||
price,
|
||||
stock
|
||||
FROM pms_sku
|
||||
WHERE spu_id = #{id}
|
||||
sales
|
||||
FROM
|
||||
`pms_spu`
|
||||
<where>
|
||||
<if test='queryParams.keywords!=null and queryParams.keywords.trim() neq ""'>
|
||||
AND name like concat('%',#{queryParams.keywords},'%')
|
||||
</if>
|
||||
<if test='queryParams.categoryId!=null'>
|
||||
AND category_id like concat('%',#{queryParams.categoryId},'%')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY
|
||||
<if test='queryParams.sortField!=null and queryParams.sortField.trim() neq "" and queryParams.sortField !=null and queryParams.sort.trim() neq ""'>
|
||||
#{queryParams.sortField} #{queryParams.sort} ,
|
||||
</if>
|
||||
gmt_create desc
|
||||
</select>
|
||||
</mapper>
|
||||
|
@ -1,12 +1,13 @@
|
||||
package com.youlai.mall.sms.pojo.to;
|
||||
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 秒杀商品Redis存储
|
||||
*
|
||||
* @author huawei
|
||||
* @desc 秒杀商品Redis存储 TO
|
||||
* @email huawei_code@163.com
|
||||
* @date 2021/3/7
|
||||
*/
|
||||
@ -66,6 +67,6 @@ public class SeckillSkuRedisTO {
|
||||
/**
|
||||
* 秒杀商品详情
|
||||
*/
|
||||
private SkuDTO skuInfo;
|
||||
private SkuInfoDTO skuInfo;
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.youlai.mall.sms.pojo.vo;
|
||||
|
||||
import com.youlai.mall.pms.pojo.dto.app.SkuDTO;
|
||||
import com.youlai.mall.pms.pojo.dto.SkuInfoDTO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
@ -69,5 +69,5 @@ public class SmsSeckillSkuVO {
|
||||
/**
|
||||
* 秒杀商品详情
|
||||
*/
|
||||
private SkuDTO skuInfo;
|
||||
private SkuInfoDTO skuInfo;
|
||||
}
|
||||
|
@ -6,12 +6,13 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.youlai.common.base.BasePageQuery;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponRecord;
|
||||
import com.youlai.mall.sms.service.ICouponRecordService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -24,26 +25,25 @@ import org.springframework.web.bind.annotation.*;
|
||||
@Api(tags = "「系统端」优惠券领券记录")
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/coupon_record")
|
||||
@RequiredArgsConstructor
|
||||
public class CouponRecordController {
|
||||
|
||||
@Autowired
|
||||
private ICouponRecordService couponRecordService;
|
||||
private final ICouponRecordService couponRecordService;
|
||||
|
||||
@ApiOperation(value = "分页获取会员领券记录")
|
||||
@GetMapping("/page")
|
||||
public Result page(BasePageQuery pageQuery) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
LambdaQueryWrapper<SmsCouponRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(SmsCouponRecord::getUserId, userId).orderByDesc(SmsCouponRecord::getGmtCreate);
|
||||
Page<SmsCouponRecord> page = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize());
|
||||
IPage<SmsCouponRecord> result = couponRecordService.page(page, queryWrapper);
|
||||
IPage<SmsCouponRecord> result = couponRecordService.page(page, new LambdaQueryWrapper<SmsCouponRecord>()
|
||||
.eq(SmsCouponRecord::getUserId, MemberUtils.getMemberId())
|
||||
.orderByDesc(SmsCouponRecord::getGmtCreate));
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "获取优惠券记录详情")
|
||||
@GetMapping("/{id}/detail")
|
||||
public Result detail(@ApiParam(value = "优惠券记录ID") @PathVariable("id") String id) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
Long userId = MemberUtils.getMemberId();
|
||||
QueryWrapper<SmsCouponRecord> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("user_id", userId).eq("id", id);
|
||||
SmsCouponRecord result = couponRecordService.getOne(queryWrapper);
|
||||
@ -53,7 +53,6 @@ public class CouponRecordController {
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 用户领券功能
|
||||
* 1、查询优惠券是否真实存在
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.youlai.mall.sms.controller.app;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.sms.util.BeanMapperUtils;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCoupon;
|
||||
@ -40,7 +41,7 @@ public class AppCouponController {
|
||||
@ApiOperation("查看可领取优惠券模板列表")
|
||||
@GetMapping("/template")
|
||||
public Result<List<CouponTemplateVO>> findAvailableTemplate() {
|
||||
List<CouponTemplateVO> availableTemplate = couponService.findAvailableTemplate(JwtUtils.getUserId());
|
||||
List<CouponTemplateVO> availableTemplate = couponService.findAvailableTemplate(MemberUtils.getMemberId());
|
||||
return Result.success(availableTemplate);
|
||||
}
|
||||
|
||||
@ -48,7 +49,7 @@ public class AppCouponController {
|
||||
@GetMapping("/receive")
|
||||
public Result receive(@ApiParam(value = "优惠券模板ID")
|
||||
@RequestParam("templateId") String templateId) {
|
||||
couponService.receive(JwtUtils.getUserId(), templateId);
|
||||
couponService.receive(MemberUtils.getMemberId(), templateId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@ -56,7 +57,7 @@ public class AppCouponController {
|
||||
@GetMapping("/list")
|
||||
public Result<List<SmsCouponVO>> list(@ApiParam(value = "优惠券模板ID", defaultValue = "1")
|
||||
@RequestParam(value = "state", required = false) Integer state) {
|
||||
List<SmsCoupon> coupons = couponService.findCouponsByState(JwtUtils.getUserId(), state);
|
||||
List<SmsCoupon> coupons = couponService.findCouponsByState(MemberUtils.getMemberId(), state);
|
||||
return Result.success(BeanMapperUtils.mapList(coupons, SmsCouponVO.class));
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,13 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponRecordMapper;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCoupon;
|
||||
import com.youlai.mall.sms.pojo.domain.SmsCouponRecord;
|
||||
import com.youlai.mall.sms.service.ICouponRecordService;
|
||||
import com.youlai.mall.sms.service.ISmsCouponService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
@ -28,31 +30,27 @@ import static com.youlai.mall.sms.pojo.constant.AppConstants.COUPON_LOCK;
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CouponRecordServiceImpl extends ServiceImpl<SmsCouponRecordMapper, SmsCouponRecord> implements ICouponRecordService {
|
||||
|
||||
@Autowired
|
||||
private ISmsCouponService couponService;
|
||||
|
||||
@Autowired
|
||||
private RedissonClient redissonClient;
|
||||
private final ISmsCouponService couponService;
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
@Override
|
||||
public void add(String couponId) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
RLock lock = redissonClient.getLock(COUPON_LOCK + couponId);
|
||||
lock.lock();
|
||||
|
||||
try {
|
||||
SmsCoupon coupon = couponService.getById(couponId);
|
||||
this.couponCheck(coupon, userId);
|
||||
this.couponCheck(coupon, memberId);
|
||||
// 封装优惠券领取记录对象
|
||||
SmsCouponRecord couponRecord = new SmsCouponRecord();
|
||||
BeanUtils.copyProperties(coupon, couponRecord);
|
||||
couponRecord.setStartTime(new Date());
|
||||
// couponRecord.setEndTime(DateUtil.offsetDay(new Date(), coupon.getValidDays()));
|
||||
// couponRecord.setUseState(CouponStateEnum.NEW.name());
|
||||
couponRecord.setUserId(JwtUtils.getUserId());
|
||||
couponRecord.setUserName(JwtUtils.getUsername());
|
||||
couponRecord.setUserId(memberId);
|
||||
couponRecord.setUserName(MemberUtils.getUsername());
|
||||
couponRecord.setCouponId(coupon.getId());
|
||||
couponRecord.setId(null);
|
||||
|
||||
@ -63,7 +61,7 @@ public class CouponRecordServiceImpl extends ServiceImpl<SmsCouponRecordMapper,
|
||||
//库存扣减成功才保存
|
||||
this.save(couponRecord);
|
||||
} else {
|
||||
log.warn("发放优惠券失败,coupon={},loginUser={}", coupon, userId);
|
||||
log.warn("发放优惠券失败,coupon={},loginUser={}", coupon, memberId);
|
||||
throw new BizException("发放优惠券失败");
|
||||
}
|
||||
} finally {
|
||||
|
@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.web.exception.BizException;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.sms.util.BeanMapperUtils;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.mall.sms.mapper.SmsCouponMapper;
|
||||
@ -195,8 +196,8 @@ public class SmsCouponServiceImpl extends ServiceImpl<SmsCouponMapper, SmsCoupon
|
||||
SmsCoupon coupon = new SmsCoupon();
|
||||
coupon.setTemplateId(template.getId());
|
||||
coupon.setCouponCode(couponCode);
|
||||
coupon.setUserId(JwtUtils.getUserId());
|
||||
coupon.setUserName(JwtUtils.getUsername());
|
||||
coupon.setUserId(MemberUtils.getMemberId());
|
||||
coupon.setUserName(MemberUtils.getUsername());
|
||||
coupon.setState(CouponStateEnum.USABLE);
|
||||
CouponTemplateRule.Expiration expiration = template.getRule().getExpiration();
|
||||
if (expiration.getPeriod() == 1) {
|
||||
|
@ -14,7 +14,10 @@ spring:
|
||||
server-addr: http://localhost:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
# 本地启动
|
||||
## server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
# 极速启动
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
file-extension: yaml
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
|
@ -9,13 +9,15 @@ spring:
|
||||
matching-strategy: ant_path_matcher
|
||||
cloud:
|
||||
nacos:
|
||||
# 注册中心
|
||||
discovery:
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
@ -1,32 +1,28 @@
|
||||
package com.youlai.mall.ums.api;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会员地址 Feign 客户端
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
@FeignClient(name = "mall-ums", contextId = "address")
|
||||
public interface MemberAddressFeignClient {
|
||||
|
||||
/**
|
||||
* 获取地址详情
|
||||
*/
|
||||
@GetMapping("/app-api/v1/addresses/{id}")
|
||||
Result<UmsAddress> getById(@PathVariable("id") Long id);
|
||||
|
||||
|
||||
/**
|
||||
* 获取会员地址列表
|
||||
* 获取当前会员地址列表
|
||||
*
|
||||
* @param memberId
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/app-api/v1/addresses")
|
||||
Result<List<UmsAddress>> list(@RequestParam Long memberId);
|
||||
Result<List<MemberAddressDTO>> listCurrMemberAddresses();
|
||||
|
||||
}
|
||||
|
||||
|
@ -2,33 +2,30 @@ package com.youlai.mall.ums.api;
|
||||
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.mall.pms.pojo.vo.ProductHistoryVO;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberAuthDTO;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberDTO;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsMember;
|
||||
import com.youlai.mall.ums.dto.MemberAuthInfoDTO;
|
||||
import com.youlai.mall.ums.dto.MemberDTO;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@FeignClient(name = "mall-ums", contextId = "member")
|
||||
public interface MemberFeignClient {
|
||||
|
||||
/**
|
||||
* 新增会员
|
||||
*
|
||||
* @param member
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/app-api/v1/members")
|
||||
Result<Long> add(@RequestBody UmsMember member);
|
||||
|
||||
@PutMapping("/app-api/v1/members/{id}")
|
||||
<T> Result<T> update(@PathVariable Long id, @RequestBody UmsMember member);
|
||||
Result<Long> addMember(@RequestBody MemberDTO member);
|
||||
|
||||
/**
|
||||
* 获取会员信息
|
||||
* 获取会员的 openid
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/app-api/v1/members/{id}")
|
||||
Result<MemberDTO> getUserById(@PathVariable Long id);
|
||||
|
||||
/**
|
||||
* 获取会员信息
|
||||
*/
|
||||
@GetMapping("/app-api/v1/members/detail/{id}")
|
||||
Result<UmsMember> getUserEntityById(@PathVariable Long id);
|
||||
|
||||
@PostMapping("/app-api/v1/members/{memberId}/openid")
|
||||
Result<String> getMemberOpenId(@PathVariable Long memberId);
|
||||
|
||||
/**
|
||||
* 扣减会员余额
|
||||
@ -50,8 +47,7 @@ public interface MemberFeignClient {
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/app-api/v1/members/openid/{openid}")
|
||||
Result<MemberAuthDTO> loadUserByOpenId(@PathVariable String openid);
|
||||
|
||||
Result<MemberAuthInfoDTO> loadUserByOpenId(@PathVariable String openid);
|
||||
|
||||
/**
|
||||
* 根据手机号获取会员认证信息
|
||||
@ -60,8 +56,7 @@ public interface MemberFeignClient {
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/app-api/v1/members/mobile/{mobile}")
|
||||
Result<MemberAuthDTO> loadUserByMobile(@PathVariable String mobile);
|
||||
|
||||
Result<MemberAuthInfoDTO> loadUserByMobile(@PathVariable String mobile);
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
package com.youlai.mall.ums.dto;
|
||||
|
||||
import com.youlai.common.constraint.CheckCityValid;
|
||||
import com.youlai.common.constraint.CityType;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Positive;
|
||||
|
||||
/**
|
||||
* 会员地址传输层对象
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/12 15:57
|
||||
*/
|
||||
@Data
|
||||
public class MemberAddressDTO {
|
||||
|
||||
@NotNull(message = "{id.positive}")
|
||||
@Positive(message = "{id.positive}")
|
||||
private Long memberId;
|
||||
|
||||
private String consigneeName;
|
||||
|
||||
@Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "{phone.valid}")
|
||||
private String consigneeMobile;
|
||||
|
||||
@CheckCityValid(CityType.PROVINCE)
|
||||
private String province;
|
||||
|
||||
@CheckCityValid(CityType.CITY)
|
||||
private String city;
|
||||
|
||||
@CheckCityValid(CityType.AREA)
|
||||
private String area;
|
||||
|
||||
@Length(min = 1, max = 100, message = "{text.length.min},{text.length.max}")
|
||||
private String detailAddress;
|
||||
|
||||
private Integer defaulted;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.youlai.mall.ums.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 会员认证传输层对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class MemberAuthInfoDTO {
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 会员名(openId、mobile)
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 状态(1:正常;0:禁用)
|
||||
*/
|
||||
private Integer status;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.youlai.mall.ums.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
|
||||
/**
|
||||
* 会员传输层对象
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
@Data
|
||||
public class MemberDTO {
|
||||
|
||||
private Integer gender;
|
||||
|
||||
private String nickName;
|
||||
|
||||
private String mobile;
|
||||
|
||||
private LocalDate birthday;
|
||||
|
||||
private String avatarUrl;
|
||||
|
||||
private String openid;
|
||||
|
||||
private String sessionKey;
|
||||
|
||||
private String city;
|
||||
|
||||
private String country;
|
||||
|
||||
private String language;
|
||||
|
||||
private String province;
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.mall.ums.pojo.dto;
|
||||
package com.youlai.mall.ums.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
@ -1,4 +1,4 @@
|
||||
package com.youlai.mall.ums.pojo.dto;
|
||||
package com.youlai.mall.ums.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
@ -1,13 +0,0 @@
|
||||
package com.youlai.mall.ums.pojo.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class MemberAuthDTO {
|
||||
|
||||
private Long userId;
|
||||
private String username;
|
||||
private Integer status;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.youlai.mall.ums.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MemberDTO {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String nickName;
|
||||
|
||||
private String avatarUrl;
|
||||
|
||||
private String mobile;
|
||||
|
||||
private Long balance;
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package com.youlai.mall.ums.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import com.youlai.common.constraint.CheckCityValid;
|
||||
import com.youlai.common.constraint.CityType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UmsAddress extends BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
// @NotNull(message = "{id.positive}")
|
||||
// @Positive(message = "{id.positive}")
|
||||
private Long memberId;
|
||||
|
||||
@Length(min = 2, max = 8, message = "{text.length.min},{text.length.max}")
|
||||
private String name;
|
||||
|
||||
@Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "{phone.valid}")
|
||||
private String mobile;
|
||||
|
||||
@CheckCityValid(CityType.PROVINCE)
|
||||
private String province;
|
||||
|
||||
@CheckCityValid(CityType.CITY)
|
||||
private String city;
|
||||
|
||||
@CheckCityValid(CityType.AREA)
|
||||
private String area;
|
||||
|
||||
@Length(min = 1, max = 100, message = "{text.length.min},{text.length.max}")
|
||||
private String address;
|
||||
|
||||
@Pattern(regexp = "^[0-9]{6}$", message = "{zipcode.valid}")
|
||||
private String zipCode;
|
||||
|
||||
private Integer defaulted;
|
||||
}
|
@ -47,32 +47,6 @@
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 分布式事务 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
|
||||
<exclusions>
|
||||
<!--排除依赖 指定版本-->
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
|
@ -13,6 +13,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ -21,9 +22,9 @@ import java.util.List;
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/members")
|
||||
@RequiredArgsConstructor
|
||||
public class MemberController {
|
||||
public class UmsMemberController {
|
||||
|
||||
private final IUmsMemberService iUmsMemberService;
|
||||
private final IUmsMemberService memberService;
|
||||
|
||||
@ApiOperation(value = "会员分页列表")
|
||||
@GetMapping
|
||||
@ -32,7 +33,7 @@ public class MemberController {
|
||||
@ApiParam("每页数量") Long pageSize,
|
||||
@ApiParam("会员昵称") String nickName
|
||||
) {
|
||||
IPage<UmsMember> result = iUmsMemberService.list(new Page<>(pageNum, pageSize), nickName);
|
||||
IPage<UmsMember> result = memberService.list(new Page<>(pageNum, pageSize), nickName);
|
||||
return Result.success(result.getRecords(), result.getTotal());
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ public class MemberController {
|
||||
public Result<UmsMember> getMemberDetail(
|
||||
@ApiParam("会员ID") @PathVariable Long id
|
||||
) {
|
||||
UmsMember user = iUmsMemberService.getById(id);
|
||||
UmsMember user = memberService.getById(id);
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
@ -52,7 +53,7 @@ public class MemberController {
|
||||
@ApiParam("会员ID") @PathVariable Long id,
|
||||
@RequestBody UmsMember member
|
||||
) {
|
||||
boolean status = iUmsMemberService.updateById(member);
|
||||
boolean status = memberService.updateById(member);
|
||||
return Result.judge(status);
|
||||
}
|
||||
|
||||
@ -62,7 +63,7 @@ public class MemberController {
|
||||
@ApiParam("会员ID") @PathVariable Long id,
|
||||
@RequestBody UmsMember member
|
||||
) {
|
||||
boolean status = iUmsMemberService.update(new LambdaUpdateWrapper<UmsMember>()
|
||||
boolean status = memberService.update(new LambdaUpdateWrapper<UmsMember>()
|
||||
.eq(UmsMember::getId, id)
|
||||
.set(member.getStatus() != null, UmsMember::getStatus, member.getStatus())
|
||||
);
|
||||
@ -74,7 +75,7 @@ public class MemberController {
|
||||
public <T> Result<T> delete(
|
||||
@ApiParam("会员ID,多个以英文逗号(,)拼接") @PathVariable String ids
|
||||
) {
|
||||
boolean status = iUmsMemberService.update(new LambdaUpdateWrapper<UmsMember>()
|
||||
boolean status = memberService.update(new LambdaUpdateWrapper<UmsMember>()
|
||||
.in(UmsMember::getId, Arrays.asList(ids.split(",")))
|
||||
.set(UmsMember::getDeleted, GlobalConstants.STATUS_YES));
|
||||
return Result.judge(status);
|
@ -2,7 +2,8 @@ package com.youlai.mall.ums.controller.app;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
import com.youlai.mall.ums.service.IUmsAddressService;
|
||||
import io.swagger.annotations.Api;
|
||||
@ -21,22 +22,21 @@ import java.util.List;
|
||||
@RequiredArgsConstructor
|
||||
public class AddressController {
|
||||
|
||||
private final IUmsAddressService iUmsAddressService;
|
||||
private final IUmsAddressService addressService;
|
||||
|
||||
@ApiOperation(value = "获取会员地址列表")
|
||||
@ApiOperation(value = "获取当前会员地址列表")
|
||||
@GetMapping
|
||||
public Result<List<UmsAddress>> listAddresses() {
|
||||
List<UmsAddress> addressList = iUmsAddressService.list(new LambdaQueryWrapper<UmsAddress>()
|
||||
.eq(UmsAddress::getMemberId, JwtUtils.getUserId())
|
||||
.orderByDesc(UmsAddress::getDefaulted));
|
||||
public Result<List<MemberAddressDTO>> listCurrentMemberAddresses() {
|
||||
List<MemberAddressDTO> addressList = addressService.listCurrentMemberAddresses();
|
||||
return Result.success(addressList);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "获取地址详情")
|
||||
@GetMapping("/{addressId}")
|
||||
public Result<UmsAddress> getAddressDetail(
|
||||
@ApiParam("会员地址ID") @PathVariable Long addressId
|
||||
@ApiParam("地址ID") @PathVariable Long addressId
|
||||
) {
|
||||
UmsAddress umsAddress = iUmsAddressService.getById(addressId);
|
||||
UmsAddress umsAddress = addressService.getById(addressId);
|
||||
return Result.success(umsAddress);
|
||||
}
|
||||
|
||||
@ -45,18 +45,17 @@ public class AddressController {
|
||||
public Result addAddress(
|
||||
@RequestBody @Validated UmsAddress address
|
||||
) {
|
||||
boolean result = iUmsAddressService.addAddress(address);
|
||||
boolean result = addressService.addAddress(address);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "修改地址")
|
||||
@PutMapping("/{addressId}")
|
||||
public Result updateAddress(
|
||||
@ApiParam(value = "地址ID") @PathVariable Long addressId,
|
||||
@RequestBody @Validated UmsAddress address
|
||||
) {
|
||||
boolean result = iUmsAddressService.updateAddress(address);
|
||||
boolean result = addressService.updateAddress(address);
|
||||
return Result.judge(result);
|
||||
}
|
||||
|
||||
@ -65,7 +64,7 @@ public class AddressController {
|
||||
public Result deleteAddress(
|
||||
@ApiParam("地址ID,过个以英文逗号(,)分割") @PathVariable String ids
|
||||
) {
|
||||
boolean status = iUmsAddressService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
boolean status = addressService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.judge(status);
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,18 @@
|
||||
package com.youlai.mall.ums.controller.app;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.result.ResultCode;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.pms.pojo.vo.ProductHistoryVO;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberAuthDTO;
|
||||
import com.youlai.mall.ums.dto.MemberAuthInfoDTO;
|
||||
import com.youlai.mall.ums.dto.MemberDTO;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsMember;
|
||||
import com.youlai.mall.ums.pojo.dto.MemberDTO;
|
||||
import com.youlai.mall.ums.pojo.vo.MemberVO;
|
||||
import com.youlai.mall.ums.service.IUmsMemberService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@ -21,90 +20,46 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
@Api(tags = "「移动端」会员管理")
|
||||
@RestController
|
||||
@RequestMapping("/app-api/v1/members")
|
||||
@RestController("appMemberController")
|
||||
@RequiredArgsConstructor
|
||||
public class MemberController {
|
||||
|
||||
private final IUmsMemberService iUmsMemberService;
|
||||
private final IUmsMemberService memberService;
|
||||
|
||||
@ApiOperation(value = "获取会员信息")
|
||||
@ApiImplicitParam(name = "id", value = "会员ID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/{id}")
|
||||
public Result<MemberDTO> getById(@PathVariable Long id) {
|
||||
MemberDTO memberDTO = new MemberDTO();
|
||||
UmsMember user = iUmsMemberService.getOne(
|
||||
new LambdaQueryWrapper<UmsMember>()
|
||||
.select(UmsMember::getId, UmsMember::getNickName, UmsMember::getMobile, UmsMember::getBalance)
|
||||
.eq(UmsMember::getId, id)
|
||||
);
|
||||
if (user != null) {
|
||||
BeanUtil.copyProperties(user, memberDTO);
|
||||
}
|
||||
return Result.success(memberDTO);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "获取会员实体信息")
|
||||
@ApiImplicitParam(name = "id", value = "会员ID", required = true, paramType = "path", dataType = "Long")
|
||||
@GetMapping("/detail/{id}")
|
||||
public Result<UmsMember> getMemberEntityById(
|
||||
@PathVariable Long id
|
||||
@ApiOperation(value = "根据会员的openid")
|
||||
@GetMapping("/{memberId}/openid")
|
||||
public Result<String> getMemberById(
|
||||
@ApiParam("会员ID") @PathVariable Long memberId
|
||||
) {
|
||||
UmsMember user = iUmsMemberService.getById(id);
|
||||
if (user == null) {
|
||||
return Result.failed(ResultCode.USER_NOT_EXIST);
|
||||
UmsMember member = memberService.getOne(
|
||||
new LambdaQueryWrapper<UmsMember>()
|
||||
.eq(UmsMember::getId,memberId)
|
||||
.select(UmsMember::getOpenid)
|
||||
);
|
||||
String openid = member.getOpenid();
|
||||
return Result.success(openid);
|
||||
}
|
||||
return Result.success(user);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "新增会员")
|
||||
@ApiImplicitParam(name = "member", value = "实体JSON对象", required = true, paramType = "body", dataType = "UmsMember")
|
||||
@PostMapping
|
||||
public Result<Long> add(@RequestBody UmsMember member) {
|
||||
boolean status = iUmsMemberService.save(member);
|
||||
if (status) {
|
||||
return Result.success(member.getId());
|
||||
} else {
|
||||
return Result.failed();
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOperation(value = "修改会员")
|
||||
@PutMapping("/{id}")
|
||||
public <T> Result<T> add(@PathVariable Long id, @RequestBody UmsMember user) {
|
||||
boolean status = iUmsMemberService.updateById(user);
|
||||
return Result.judge(status);
|
||||
public Result<Long> addMember(@RequestBody MemberDTO member) {
|
||||
Long memberId = memberService.addMember(member);
|
||||
return Result.success(memberId);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "获取登录会员信息")
|
||||
@GetMapping("/me")
|
||||
public Result<MemberDTO> getMemberInfo() {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
UmsMember member = iUmsMemberService.getById(userId);
|
||||
if (member == null) {
|
||||
return Result.failed(ResultCode.USER_NOT_EXIST);
|
||||
}
|
||||
MemberDTO memberDTO = new MemberDTO();
|
||||
BeanUtil.copyProperties(member, memberDTO);
|
||||
return Result.success(memberDTO);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "修改会员积分")
|
||||
@PutMapping("/{id}/points")
|
||||
public <T> Result<T> updatePoint(@PathVariable Long id, @RequestParam Integer num) {
|
||||
UmsMember user = iUmsMemberService.getById(id);
|
||||
user.setPoint(user.getPoint() + num);
|
||||
boolean result = iUmsMemberService.updateById(user);
|
||||
return Result.judge(result);
|
||||
public Result<MemberVO> getCurrentMemberInfo() {
|
||||
MemberVO memberVO = memberService.getCurrentMemberInfo();
|
||||
return Result.success(memberVO);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "扣减会员余额")
|
||||
@PutMapping("/current/balances/_deduct")
|
||||
public <T> Result<T> deductBalance(@RequestParam Long balances) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
boolean result = iUmsMemberService.update(new LambdaUpdateWrapper<UmsMember>()
|
||||
Long userId = MemberUtils.getMemberId();
|
||||
boolean result = memberService.update(new LambdaUpdateWrapper<UmsMember>()
|
||||
.setSql("balance = balance - " + balances)
|
||||
.eq(UmsMember::getId, userId)
|
||||
);
|
||||
@ -114,8 +69,8 @@ public class MemberController {
|
||||
@ApiOperation(value = "添加浏览历史")
|
||||
@PostMapping("/view/history")
|
||||
public <T> Result<T> addProductViewHistory(@RequestBody ProductHistoryVO product) {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
iUmsMemberService.addProductViewHistory(product, userId);
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
memberService.addProductViewHistory(product, memberId);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@ -123,8 +78,8 @@ public class MemberController {
|
||||
@GetMapping("/view/history")
|
||||
public Result<Set<ProductHistoryVO>> getProductViewHistory() {
|
||||
try {
|
||||
Long userId = JwtUtils.getUserId();
|
||||
Set<ProductHistoryVO> historyList = iUmsMemberService.getProductViewHistory(userId);
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
Set<ProductHistoryVO> historyList = memberService.getProductViewHistory(memberId);
|
||||
return Result.success(historyList);
|
||||
} catch (Exception e) {
|
||||
return Result.success(Collections.emptySet());
|
||||
@ -132,33 +87,26 @@ public class MemberController {
|
||||
}
|
||||
|
||||
@ApiOperation(value = "根据 openid 获取会员认证信息")
|
||||
@ApiImplicitParam(name = "openid", value = "微信身份唯一标识", required = true, paramType = "path", dataType = "String")
|
||||
@GetMapping("/openid/{openid}")
|
||||
public Result<MemberAuthDTO> getByOpenid(@PathVariable String openid) {
|
||||
UmsMember member = iUmsMemberService.getOne(new LambdaQueryWrapper<UmsMember>()
|
||||
.eq(UmsMember::getOpenid, openid)
|
||||
.select(UmsMember::getId, UmsMember::getOpenid, UmsMember::getStatus)
|
||||
);
|
||||
if (member == null) {
|
||||
return Result.failed(ResultCode.USER_NOT_EXIST);
|
||||
}
|
||||
MemberAuthDTO memberAuth = new MemberAuthDTO(member.getId(), member.getOpenid(), member.getStatus());
|
||||
return Result.success(memberAuth);
|
||||
public Result<MemberAuthInfoDTO> getByOpenid(
|
||||
@ApiParam("微信身份标识") @PathVariable String openid
|
||||
) {
|
||||
MemberAuthInfoDTO memberAuthInfo = memberService.getByOpenid(openid);
|
||||
return Result.success(memberAuthInfo);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "根据手机号获取会员认证信息")
|
||||
@ApiImplicitParam(name = "mobile", value = "会员手机号码", required = true, paramType = "path", dataType = "String")
|
||||
/**
|
||||
* 根据手机号获取会员认证信息
|
||||
*
|
||||
* @param mobile
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/mobile/{mobile}")
|
||||
public Result<MemberAuthDTO> getByMobile(@PathVariable String mobile) {
|
||||
UmsMember member = iUmsMemberService.getOne(new LambdaQueryWrapper<UmsMember>()
|
||||
.eq(UmsMember::getMobile, mobile)
|
||||
.select(UmsMember::getId, UmsMember::getMobile, UmsMember::getStatus)
|
||||
);
|
||||
if (member == null) {
|
||||
return Result.failed(ResultCode.USER_NOT_EXIST);
|
||||
}
|
||||
MemberAuthDTO memberAuth = new MemberAuthDTO(member.getId(), member.getMobile(), member.getStatus());
|
||||
return Result.success(memberAuth);
|
||||
public Result<MemberAuthInfoDTO> getByMobile(
|
||||
@ApiParam("手机号码") @PathVariable String mobile
|
||||
) {
|
||||
MemberAuthInfoDTO memberAuthInfo = memberService.getByMobile(mobile);
|
||||
return Result.success(memberAuthInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import java.util.List;
|
||||
|
||||
|
||||
@Mapper
|
||||
public interface UmsUserMapper extends BaseMapper<UmsMember> {
|
||||
public interface UmsMemberMapper extends BaseMapper<UmsMember> {
|
||||
|
||||
@Select("<script>" +
|
||||
" SELECT * from ums_member " +
|
@ -0,0 +1,63 @@
|
||||
package com.youlai.mall.ums.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/12 16:12
|
||||
*/
|
||||
@Data
|
||||
public class UmsAddress extends BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 会员ID
|
||||
*/
|
||||
private Long memberId;
|
||||
|
||||
/**
|
||||
* 收货人姓名
|
||||
*/
|
||||
private String consigneeName;
|
||||
|
||||
/**
|
||||
* 收货人联系方式
|
||||
*/
|
||||
private String consigneeMobile;
|
||||
|
||||
/**
|
||||
* 省
|
||||
*/
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 市
|
||||
*/
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* 区
|
||||
*/
|
||||
private String area;
|
||||
|
||||
/**
|
||||
* 详细地址
|
||||
*/
|
||||
private String detailAddress;
|
||||
|
||||
/**
|
||||
* 邮编
|
||||
*/
|
||||
private String zipCode;
|
||||
|
||||
/**
|
||||
* 是否默认地址(1:是;0:否)
|
||||
*/
|
||||
private Integer defaulted;
|
||||
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
package com.youlai.mall.ums.pojo.entity;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.youlai.common.base.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/12 16:15
|
||||
*/
|
||||
@Data
|
||||
public class UmsMember extends BaseEntity {
|
||||
|
||||
@ -32,18 +34,6 @@ public class UmsMember extends BaseEntity {
|
||||
|
||||
private String sessionKey;
|
||||
|
||||
private Integer status;
|
||||
|
||||
private Integer point;
|
||||
|
||||
@TableLogic(delval = "1",value = "0")
|
||||
private Integer deleted;
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<UmsAddress> addressList;
|
||||
|
||||
private Long balance;
|
||||
|
||||
private String city;
|
||||
|
||||
private String country;
|
||||
@ -52,4 +42,16 @@ public class UmsMember extends BaseEntity {
|
||||
|
||||
private String province;
|
||||
|
||||
private Integer status;
|
||||
|
||||
private Long balance;
|
||||
|
||||
@TableLogic(delval = "1", value = "0")
|
||||
private Integer deleted;
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<UmsAddress> addressList;
|
||||
|
||||
private Integer point;
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.youlai.mall.ums.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 会员视图层对象
|
||||
*
|
||||
* @author <a href="mailto:xianrui0365@163.com">haoxr</a>
|
||||
* @date 2022/2/12 21:13
|
||||
*/
|
||||
@ApiModel("会员视图层对象")
|
||||
@Data
|
||||
public class MemberVO {
|
||||
|
||||
@ApiModelProperty("会员ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("会员昵称")
|
||||
private String nickName;
|
||||
|
||||
@ApiModelProperty("会员头像地址")
|
||||
private String avatarUrl;
|
||||
|
||||
@ApiModelProperty("会员手机号")
|
||||
private String mobile;
|
||||
|
||||
@ApiModelProperty("会员余额(单位:分)")
|
||||
private Long balance;
|
||||
|
||||
}
|
@ -2,8 +2,17 @@ package com.youlai.mall.ums.service;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 会员地址业务接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
public interface IUmsAddressService extends IService<UmsAddress> {
|
||||
|
||||
/**
|
||||
@ -22,5 +31,10 @@ public interface IUmsAddressService extends IService<UmsAddress> {
|
||||
*/
|
||||
boolean updateAddress(UmsAddress address);
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前登录会员的地址列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<MemberAddressDTO> listCurrentMemberAddresses();
|
||||
}
|
||||
|
@ -5,10 +5,19 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.youlai.mall.pms.pojo.vo.ProductHistoryVO;
|
||||
import com.youlai.mall.ums.dto.MemberAuthInfoDTO;
|
||||
import com.youlai.mall.ums.dto.MemberDTO;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsMember;
|
||||
import com.youlai.mall.ums.pojo.vo.MemberVO;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 会员业务接口
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
public interface IUmsMemberService extends IService<UmsMember> {
|
||||
|
||||
IPage<UmsMember> list(Page<UmsMember> page, String nickname);
|
||||
@ -16,4 +25,35 @@ public interface IUmsMemberService extends IService<UmsMember> {
|
||||
void addProductViewHistory(ProductHistoryVO product, Long userId);
|
||||
|
||||
Set<ProductHistoryVO> getProductViewHistory(Long userId);
|
||||
|
||||
/**
|
||||
* 根据 openid 获取会员认证信息
|
||||
*
|
||||
* @param openid
|
||||
* @return
|
||||
*/
|
||||
MemberAuthInfoDTO getByOpenid(String openid);
|
||||
|
||||
/**
|
||||
* 根据手机号获取会员认证信息
|
||||
*
|
||||
* @param mobile
|
||||
* @return
|
||||
*/
|
||||
MemberAuthInfoDTO getByMobile(String mobile);
|
||||
|
||||
/**
|
||||
* 新增会员
|
||||
*
|
||||
* @param member
|
||||
* @return
|
||||
*/
|
||||
Long addMember(MemberDTO member);
|
||||
|
||||
/**
|
||||
* 获取登录会员信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
MemberVO getCurrentMemberInfo();
|
||||
}
|
||||
|
@ -1,18 +1,32 @@
|
||||
package com.youlai.mall.ums.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.ums.dto.MemberAddressDTO;
|
||||
import com.youlai.mall.ums.mapper.UmsAddressMapper;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsAddress;
|
||||
import com.youlai.mall.ums.service.IUmsAddressService;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 会员地址业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
@Service
|
||||
public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddress> implements IUmsAddressService {
|
||||
|
||||
|
||||
/**
|
||||
* 添加地址
|
||||
*
|
||||
@ -21,7 +35,7 @@ public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddr
|
||||
*/
|
||||
@Override
|
||||
public boolean addAddress(UmsAddress address) {
|
||||
Long memberId = JwtUtils.getUserId();
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
address.setMemberId(memberId);
|
||||
if (GlobalConstants.STATUS_YES.equals(address.getDefaulted())) { // 修改其他默认地址为非默认
|
||||
this.update(new LambdaUpdateWrapper<UmsAddress>()
|
||||
@ -33,7 +47,6 @@ public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddr
|
||||
return this.save(address);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改地址
|
||||
*
|
||||
@ -42,11 +55,11 @@ public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddr
|
||||
*/
|
||||
@Override
|
||||
public boolean updateAddress(UmsAddress address) {
|
||||
Long loginUserId = JwtUtils.getUserId();
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
// 修改其他默认地址为非默认
|
||||
if (GlobalConstants.STATUS_YES.equals(address.getDefaulted())) {
|
||||
this.update(new LambdaUpdateWrapper<UmsAddress>()
|
||||
.eq(UmsAddress::getMemberId, loginUserId)
|
||||
.eq(UmsAddress::getMemberId, memberId)
|
||||
.eq(UmsAddress::getDefaulted, 1)
|
||||
.set(UmsAddress::getDefaulted, 0)
|
||||
);
|
||||
@ -54,4 +67,24 @@ public class UmsAddressServiceImpl extends ServiceImpl<UmsAddressMapper, UmsAddr
|
||||
return this.updateById(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录会员的地址列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<MemberAddressDTO> listCurrentMemberAddresses() {
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
List<UmsAddress> umsAddressList = this.list(new LambdaQueryWrapper<UmsAddress>()
|
||||
.eq(UmsAddress::getMemberId, memberId)
|
||||
.orderByDesc(UmsAddress::getDefaulted) // 默认地址排在首位
|
||||
);
|
||||
List<MemberAddressDTO> memberAddressList = Optional.ofNullable(umsAddressList).orElse(new ArrayList<>()).stream()
|
||||
.map(umsAddress -> {
|
||||
MemberAddressDTO memberAddressDTO = new MemberAddressDTO();
|
||||
BeanUtil.copyProperties(umsAddress, memberAddressDTO);
|
||||
return memberAddressDTO;
|
||||
}).collect(Collectors.toList());
|
||||
return memberAddressList;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,20 @@
|
||||
package com.youlai.mall.ums.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.common.web.util.MemberUtils;
|
||||
import com.youlai.mall.pms.pojo.vo.ProductHistoryVO;
|
||||
import com.youlai.mall.ums.constant.UmsConstants;
|
||||
import com.youlai.mall.ums.dto.MemberAuthInfoDTO;
|
||||
import com.youlai.mall.ums.dto.MemberDTO;
|
||||
import com.youlai.mall.ums.mapper.UmsMemberMapper;
|
||||
import com.youlai.mall.ums.pojo.entity.UmsMember;
|
||||
import com.youlai.mall.ums.mapper.UmsUserMapper;
|
||||
import com.youlai.mall.ums.pojo.vo.MemberVO;
|
||||
import com.youlai.mall.ums.service.IUmsMemberService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@ -15,9 +23,15 @@ import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 会员业务实现类
|
||||
*
|
||||
* @author haoxr
|
||||
* @date 2022/2/12
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UmsMemberServiceImpl extends ServiceImpl<UmsUserMapper, UmsMember> implements IUmsMemberService {
|
||||
public class UmsMemberServiceImpl extends ServiceImpl<UmsMemberMapper, UmsMember> implements IUmsMemberService {
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
|
||||
@ -44,4 +58,91 @@ public class UmsMemberServiceImpl extends ServiceImpl<UmsUserMapper, UmsMember>
|
||||
public Set<ProductHistoryVO> getProductViewHistory(Long userId) {
|
||||
return redisTemplate.opsForZSet().reverseRange(UmsConstants.USER_PRODUCT_HISTORY + userId, 0, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 openid 获取会员认证信息
|
||||
*
|
||||
* @param openid
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public MemberAuthInfoDTO getByOpenid(String openid) {
|
||||
UmsMember member = this.getOne(new LambdaQueryWrapper<UmsMember>()
|
||||
.eq(UmsMember::getOpenid, openid)
|
||||
.select(UmsMember::getId,
|
||||
UmsMember::getOpenid,
|
||||
UmsMember::getStatus
|
||||
)
|
||||
);
|
||||
Assert.isTrue(member != null, "会员不存在");
|
||||
|
||||
MemberAuthInfoDTO memberAuth = new MemberAuthInfoDTO()
|
||||
.setMemberId(member.getId())
|
||||
.setUsername(member.getOpenid())
|
||||
.setStatus(member.getStatus());
|
||||
return memberAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机号获取会员认证信息
|
||||
*
|
||||
* @param mobile
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public MemberAuthInfoDTO getByMobile(String mobile) {
|
||||
UmsMember member = this.getOne(new LambdaQueryWrapper<UmsMember>()
|
||||
.eq(UmsMember::getMobile, mobile)
|
||||
.select(UmsMember::getId,
|
||||
UmsMember::getOpenid,
|
||||
UmsMember::getStatus
|
||||
)
|
||||
);
|
||||
Assert.isTrue(member != null, "会员不存在");
|
||||
|
||||
MemberAuthInfoDTO memberAuth = new MemberAuthInfoDTO()
|
||||
.setMemberId(member.getId())
|
||||
.setUsername(member.getMobile())
|
||||
.setStatus(member.getStatus());
|
||||
return memberAuth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增会员
|
||||
*
|
||||
* @param memberDTO
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Long addMember(MemberDTO memberDTO) {
|
||||
UmsMember umsMember = new UmsMember();
|
||||
BeanUtil.copyProperties(memberDTO, umsMember);
|
||||
umsMember.setStatus(GlobalConstants.STATUS_YES);
|
||||
|
||||
boolean result = this.save(umsMember);
|
||||
Assert.isTrue(result, "新增会员失败");
|
||||
return umsMember.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录会员信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public MemberVO getCurrentMemberInfo() {
|
||||
Long memberId = MemberUtils.getMemberId();
|
||||
UmsMember umsMember = this.getOne(new LambdaQueryWrapper<UmsMember>()
|
||||
.eq(UmsMember::getId, memberId)
|
||||
.select(UmsMember::getId,
|
||||
UmsMember::getNickName,
|
||||
UmsMember::getAvatarUrl,
|
||||
UmsMember::getMobile,
|
||||
UmsMember::getBalance
|
||||
)
|
||||
);
|
||||
MemberVO memberVO = new MemberVO();
|
||||
BeanUtil.copyProperties(umsMember, memberVO);
|
||||
return memberVO;
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,8 @@ spring:
|
||||
server-addr: http://localhost:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
file-extension: yaml
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
||||
|
||||
# 日志目录
|
||||
logfile:
|
||||
dir: D://logssss/${spring.application.name}
|
||||
|
@ -11,11 +11,11 @@ spring:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
file-extension: yaml
|
||||
namespace: prod_namespace_id
|
||||
namespace: youlai-namespace-id
|
||||
shared-configs[0]:
|
||||
data-id: youlai-common.yaml
|
||||
refresh: true
|
10
pom.xml
10
pom.xml
@ -46,9 +46,9 @@
|
||||
<minio.version>7.1.0</minio.version>
|
||||
<weixin-java.version>4.1.5.B</weixin-java.version>
|
||||
<hibernate-validator.version>6.0.13.Final</hibernate-validator.version>
|
||||
<seata.version>1.4.1</seata.version>
|
||||
<seata.version>1.4.2</seata.version>
|
||||
<knife4j.version>2.0.9</knife4j.version>
|
||||
<redisson.version>3.15.1</redisson.version>
|
||||
<redisson.version>3.16.8</redisson.version>
|
||||
<logstash-logback-encoder.version>6.6</logstash-logback-encoder.version>
|
||||
<elasticsearch.version>7.10.1</elasticsearch.version>
|
||||
<ip2region.version>1.7.2</ip2region.version>
|
||||
@ -137,12 +137,6 @@
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-all</artifactId>
|
||||
<version>${seata.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-spring-boot-starter</artifactId>
|
||||
|
@ -14,6 +14,7 @@ import com.youlai.admin.service.ISysRoleService;
|
||||
import com.youlai.common.constant.GlobalConstants;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.UserUtils;
|
||||
import io.swagger.annotations.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
@ -41,7 +42,7 @@ public class RoleController {
|
||||
@ApiParam("每页数量") long pageSize,
|
||||
@ApiParam("角色名称") String name
|
||||
) {
|
||||
List<String> roles = JwtUtils.getRoles();
|
||||
List<String> roles = UserUtils.getRoles();
|
||||
boolean isRoot = roles.contains(GlobalConstants.ROOT_ROLE_CODE); // 判断是否是超级管理员
|
||||
LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<SysRole>()
|
||||
.like(StrUtil.isNotBlank(name), SysRole::getName, name)
|
||||
@ -56,7 +57,7 @@ public class RoleController {
|
||||
@ApiOperation(value = "角色列表")
|
||||
@GetMapping
|
||||
public Result getRoleList() {
|
||||
List<String> roles = JwtUtils.getRoles();
|
||||
List<String> roles = UserUtils.getRoles();
|
||||
boolean isRoot = roles.contains(GlobalConstants.ROOT_ROLE_CODE); // 判断是否是超级管理员
|
||||
List list = iSysRoleService.list(new LambdaQueryWrapper<SysRole>()
|
||||
.eq(SysRole::getStatus, GlobalConstants.STATUS_YES)
|
||||
@ -75,7 +76,6 @@ public class RoleController {
|
||||
return Result.success(role);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "新增角色")
|
||||
@PostMapping
|
||||
public Result saveRole(@RequestBody SysRole role) {
|
||||
@ -113,9 +113,10 @@ public class RoleController {
|
||||
}
|
||||
|
||||
@ApiOperation(value = "删除角色")
|
||||
@ApiImplicitParam(name = "ids", value = "以,分割拼接字符串", required = true, dataType = "String")
|
||||
@DeleteMapping("/{ids}")
|
||||
public Result deleteRole(@PathVariable String ids) {
|
||||
public Result deleteRoles(
|
||||
@ApiParam("删除角色,多个以英文逗号(,)分割") @PathVariable String ids
|
||||
) {
|
||||
boolean result = iSysRoleService.delete(Arrays.asList(ids.split(",")).stream()
|
||||
.map(id -> Long.parseLong(id)).collect(Collectors.toList()));
|
||||
if (result) {
|
||||
|
@ -12,7 +12,7 @@ import com.youlai.admin.pojo.vo.user.UserPageVO;
|
||||
import com.youlai.admin.service.ISysPermissionService;
|
||||
import com.youlai.admin.service.ISysUserService;
|
||||
import com.youlai.common.result.Result;
|
||||
import com.youlai.common.web.util.JwtUtils;
|
||||
import com.youlai.common.web.util.UserUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
@ -116,11 +116,11 @@ public class UserController {
|
||||
public Result<LoginUserVO> getCurrentUser() {
|
||||
LoginUserVO loginUserVO = new LoginUserVO();
|
||||
// 用户基本信息
|
||||
Long userId = JwtUtils.getUserId();
|
||||
Long userId = UserUtils.getUserId();
|
||||
SysUser user = iSysUserService.getById(userId);
|
||||
BeanUtil.copyProperties(user, loginUserVO);
|
||||
// 用户角色信息
|
||||
List<String> roles = JwtUtils.getRoles();
|
||||
List<String> roles = UserUtils.getRoles();
|
||||
loginUserVO.setRoles(roles);
|
||||
// 用户按钮权限信息
|
||||
List<String> perms = iSysPermissionService.listBtnPermByRoles(roles);
|
||||
|
@ -14,7 +14,7 @@ spring:
|
||||
server-addr: http://localhost:8848
|
||||
# 配置中心
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
server-addr: http://c.youlai.tech:8848
|
||||
file-extension: yaml
|
||||
# 公共配置
|
||||
shared-configs[0]:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user