Merge branch 'develop' of https://gitee.com/youlaitech/youlai-mall into develop

This commit is contained in:
代家彪 2022-02-21 11:07:38 +08:00
commit 4efad306bf
151 changed files with 2734 additions and 2023 deletions

224
README.md
View File

@ -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,143 +54,155 @@
- 移动端采用终极跨平台解决方案 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 微信授权模式 |
| ------------------------------------------------------------ |----------------------------------------------------------- |
### 🗁 预览截图
| 「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 验证码模式** |
| **「管理前端」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` 一致。
#### 2⃣ 本地启动
4. **启动测试**
访问接口文档地址测试 [http://localhost:9999/doc.html](http://localhost:9999/doc.html)
#### 二. 本地启动
1. **中间件安装(🔴必装 ⚪可选)**
- 🔴MySQL 安装
- 🔴Redis 安装
- ⚪RabbitMQ
- ⚪Seata 安装
- ⚪Sentinel 安装
> 为了避免数据和线上环境冲突MySQL 和 Redis 必装,不安装可默认使用有来线上环境
- 🔴MySQL 安装
- 🔴Redis 安装
- ⚪RabbitMQ
- ⚪Seata 安装
- ⚪Sentinel 安装
2. **数据库创建和数据初始化**
- **系统数据库**
- **系统数据库**
进入 `doc/sql` 目录 根据 MySQL 版本选择对应的脚本;
进入 `doc/sql` 目录 根据 MySQL 版本选择对应的脚本;
先执行 `database.sql` 完成数据库的创建;
先执行 `database.sql` 完成数据库的创建;
再执行 `youlai.sql` 、`mall_*.sql` 完成数据表的创建和数据初始化。
再执行 `youlai.sql` 、`mall_*.sql` 完成数据表的创建和数据初始化。
- **Nacos数据库**
创建名为 nacos 的数据库;
执行 `middleware/nacos/conf/nacos-mysql.sql` 脚本完成 Nacos 数据库初始化。
- **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**
2. **启动Nacos**
IDEA 打开命令行终端 Terminal输入 `cd middleware/nacos/bin` 切换到 Nacos 的 bin 目录,执行 `startup -m standalone` 启动 Nacos 服务。
3. **导入Nacos配置**
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` 文件
4. **修改Nacos配置**
进入控制台,点击左侧菜单 `配置管理``配置列表` 进入列表页面,点击 `导入配置` 选择项目中的 `doc/nacos/DEFAULT_GROUP.zip` 文件 。
进入共享配置 `youlai-common.yaml` ,修改 MySQL、Redis、RabbitMQ等中间件信息为您自己本地环境默认为有来云环境。
4. **修改Nacos配置**
在 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) |
|---|---|---|
| ![](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) |
| ----------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |

View File

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

View File

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

View File

@ -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));
}
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
return new ThreadPoolExecutor(50, 500, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10000), new NamedThreadFactory("订单线程"));
}
}

View File

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

View File

@ -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) {

View File

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

View File

@ -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值队列中未被消费的消息一次性确认

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) ;
/**
* 订单支付

View File

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

View File

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

View File

@ -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);
rabbitTemplate.convertAndSend("order.exchange", "order.create", orderToken);
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();
// 添加订单明细
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);
}
return 0L;
}).reduce(0L, Long::sum);
if (currentTotalPrice.compareTo(submitDTO.getTotalPrice()) != 0) {
throw new BizException("页面已过期,请重新刷新页面再提交");
}
Assert.isTrue(result, "订单提交失败");
// 校验库存是否足够和锁库存
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);
@ -307,7 +221,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
try {
//尝试获取锁获取不到会立马返回 false
flag = lock.tryLock(0L, 10L, TimeUnit.SECONDS);
if(!flag){
if (!flag) {
throw new BizException("订单不可重复支付");
}
if (!OrderStatusEnum.PENDING_PAYMENT.getCode().equals(order.getStatus())) {
@ -324,28 +238,33 @@ 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);
throw new BizException("锁订单异常");
} catch (Exception e){
} catch (Exception e) {
//异常继续往上抛
throw e;
}finally {
} finally {
//释放锁
if(flag){
if (flag) {
lock.unlock();
}
}
}
/**
* 余额支付
*
* @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);
// 在前面加上wxoweixin 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
* @return
* @param orderTotalAmount 订单总金额
* @param orderItems 订单商品明细
* @return true订单总价和商品总价一致false订单总价和商品总价不一致
*/
@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 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
*/
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);
}
}

View File

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

View File

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

View File

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

View File

@ -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]:

View File

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

View File

@ -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 {
@ -16,7 +14,7 @@ public class RabbitMQTest {
private RabbitTemplate rabbitTemplate;
@Test
public void createOrderTest() {
public void createOrderTest() {
rabbitTemplate.convertAndSend("order.exchange", "order.create", "4acd475a-c6aa-4d9a-a3a5-40da7472cbee");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
public class LockStockDTO {
/**
* 订单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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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("锁定的商品列表为空");
}
//prepareSkuLockList(null, skuLockList);
// 锁定商品
skuLockList.forEach(item -> {
RLock lock = redissonClient.getLock(PmsConstants.LOCK_SKU_PREFIX + item.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())
);
if (result) {
item.setLocked(true);
} else {
item.setLocked(false);
}
lock.unlock();
});
@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));
// 锁定失败的商品集合
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 + "库存不足");
if (pmsSku != null) {
stockNum = pmsSku.getStockNum();
}
// 将锁定的商品保存至Redis中
String orderToken = skuLockList.get(0).getOrderToken();
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
return Result.success();
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), "锁定的商品为空");
// 锁定商品
lockedSkuList.forEach(lockedSku -> {
RLock lock = redissonClient.getLock(PmsConstants.LOCK_SKU_PREFIX + lockedSku.getSkuId()); // 获取商品的分布式锁
lock.lock();
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 (lockResult) {
lock.unlock();
} else {
throw new BizException("锁定商品" + lockedSku.getSkuId() + "失败");
}
});
// 将锁定商品库存信息保存至Redis
String orderToken = lockStockDTO.getOrderToken();
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(lockedSkuList));
return true;
}
/**
* 订单超时关单解锁库存
* 释放库存 - 订单超时未支付
*/
@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)) {
return true;
}
List<LockStockDTO> skuLockList = JSONUtil.toList(json, LockStockDTO.class);
skuLockList.forEach(item ->
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;
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) {
return pmsSku.getPrice() * checkOrderItem.getCount();
}
return 0L;
}).reduce(0L, Long::sum);
return orderTotalAmount.compareTo(skuTotalAmount) == 0;
}
// ->数据库
PmsSku pmsSku = this.getOne(new LambdaQueryWrapper<PmsSku>()
.eq(PmsSku::getId, id)
.select(PmsSku::getStock));
if (pmsSku != null) {
stock = pmsSku.getStock();
// ->缓存
redisTemplate.opsForValue().set(PmsConstants.LOCKED_STOCK_PREFIX + id, String.valueOf(stock));
}
return stock;
}
@Override
public SkuDTO getSkuById(Long id) {
return this.baseMapper.getSkuById(id);
return false;
}
/* private final SeataTccSkuService seataTccSkuService;
/**
* 获取商品库存信息
*
* @param skuId
* @return
*/
@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;
}*/
public SkuInfoDTO getSkuInfo(Long skuId) {
SkuInfoDTO skuInfo = this.baseMapper.getSkuInfo(skuId);
return skuInfo;
}
}

View File

@ -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 商品SPUID
* @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 商品SPUID
* @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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,12 +11,12 @@ 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
refresh: true

View File

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

View File

@ -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 ,
NAME ,
sn,
pic_url ,
spec_ids,
price ,
stock
FROM pms_sku
WHERE spu_id = #{id}
<!--「移动端」商品分页列表-->
<select id="listAppSpuWithPage" resultType="com.youlai.mall.pms.pojo.vo.GoodsPageVO">
SELECT
id,
NAME,
pic_url,
price,
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>

View File

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

View File

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

View File

@ -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查询优惠券是否真实存在

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -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;
@FeignClient(name = "mall-ums",contextId = "address")
/**
* 会员地址 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();
}

View File

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

View File

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

View File

@ -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;
/**
* 会员名(openIdmobile)
*/
private String username;
/**
* 状态(1:正常0禁用)
*/
private Integer status;
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package com.youlai.mall.ums.pojo.dto;
package com.youlai.mall.ums.dto;
import lombok.Data;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
return Result.success(user);
UmsMember member = memberService.getOne(
new LambdaQueryWrapper<UmsMember>()
.eq(UmsMember::getId,memberId)
.select(UmsMember::getOpenid)
);
String openid = member.getOpenid();
return Result.success(openid);
}
@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,42 +78,35 @@ 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());
}
}
@ApiOperation(value = "根据openid获取会员认证信息")
@ApiImplicitParam(name = "openid", value = "微信身份唯一标识", required = true, paramType = "path", dataType = "String")
@ApiOperation(value = "根据 openid 获取会员认证信息")
@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);
}
}

View File

@ -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 " +

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}
refresh: true

View File

@ -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
refresh: true

10
pom.xml
View File

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

View File

@ -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) {

View File

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

View File

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