Merge remote-tracking branch 'origin/master' into feature/springdoc
# Conflicts: # README.md # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderItemRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderRespVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayNotifyOrderReqVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayRefundOrderReqVO.java # yudao-server/pom.xml # yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
This commit is contained in:
commit
7b98a3f3f3
23
README.md
23
README.md
|
@ -1,6 +1,4 @@
|
|||
**严肃声明:现在、未来都不会有商业版本,所有功能全部开源!**
|
||||
|
||||
**拒绝虚假开源,售卖商业版,程序员不骗程序员!!**
|
||||
**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!**
|
||||
|
||||
**「我喜欢写代码,乐此不疲」**
|
||||
**「我喜欢做开源,以此为乐」**
|
||||
|
@ -27,7 +25,7 @@
|
|||
|
||||
* 管理后台的 Vue3 版本采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) ,Vue2 版本采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
|
||||
* 管理后台的移动端采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
|
||||
* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson
|
||||
* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
|
||||
* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
|
||||
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
|
||||
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能
|
||||
|
@ -39,11 +37,11 @@
|
|||
* 集成报表设计器,支持数据报表、图形报表、打印设计等
|
||||
|
||||
| 项目名 | 说明 | 传送门 |
|
||||
|--------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
||||
|----------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ruoyi-vue-pro` | Spring Boot 多模块 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro)** [Github](https://github.com/YunaiV/ruoyi-vue-pro) |
|
||||
| `yudao-cloud` | Spring Cloud 微服务 | **[Gitee](https://gitee.com/zhijiantianya/yudao-cloud)** [Github](https://github.com/YunaiV/yudao-cloud) |
|
||||
| `Spring-Boot-Labs` | Spring Boot & Cloud 入门 | **[Gitee](https://gitee.com/zhijiantianya/SpringBoot-Labs)** [Github](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| `ruoyi-vue-pro-mini` | 精简版 移除工作流 支付等模块| **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini)** |
|
||||
| `ruoyi-vue-pro-mini` | 精简版:移除工作流、支付等模块 | **[Gitee](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/mini)** |
|
||||
|
||||
## 😎 开源协议
|
||||
|
||||
|
@ -98,6 +96,7 @@
|
|||
| | 通知公告 | 系统通知公告信息发布维护 |
|
||||
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
|
||||
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
||||
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
|
||||
|
||||
### 工作流程
|
||||
|
||||
|
@ -170,7 +169,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
## 🐨 技术栈
|
||||
|
||||
| 项目 | 说明 |
|
||||
|-------------------------|-----------------------|
|
||||
|------------------------------|--------------------|
|
||||
| `yudao-dependencies` | Maven 依赖版本管理 |
|
||||
| `yudao-framework` | Java 框架拓展 |
|
||||
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
|
||||
|
@ -181,14 +180,14 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
| `yudao-module-system` | 系统功能的 Module 模块 |
|
||||
| `yudao-module-member` | 会员中心的 Module 模块 |
|
||||
| `yudao-module-infra` | 基础设施的 Module 模块 |
|
||||
| `yudao-module-tool` | 研发工具的 Module 模块 |
|
||||
| `yudao-module-bpm` | 工作流程的 Module 模块 |
|
||||
| `yudao-module-pay` | 支付系统的 Module 模块 |
|
||||
| `yudao-module-visualization` | 大屏报表 Module 模块 |
|
||||
|
||||
### 后端
|
||||
|
||||
| 框架 | 说明 | 版本 | 学习指南 |
|
||||
|---------------------------------------------------------------------------------------------|-----------------------|-------------|----------------------------------------------------------------|
|
||||
|---------------------------------------------------------------------------------------------|------------------|-------------|----------------------------------------------------------------|
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.6 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
|
||||
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.15 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
|
@ -223,8 +222,8 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
| 框架 | 说明 | 版本 |
|
||||
|----------------------------------------------------------------------|:------------:|:------:|
|
||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.2.45 |
|
||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.0.2 |
|
||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.27 |
|
||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.0.3 |
|
||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.28 |
|
||||
| [TypeScript](https://www.typescriptlang.org/docs/) | TypeScript | 4.9.4 |
|
||||
| [pinia](https://pinia.vuejs.org/) | vuex5 | 2.0.28 |
|
||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.2.2 |
|
||||
|
@ -233,7 +232,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
### [管理后台 uni-app 跨端](./yudao-ui-admin-uniapp)
|
||||
|
||||
| 框架 | 说明 | 版本 |
|
||||
|----------------------------------------------------------------------|------------------|--------|
|
||||
|-------------------------------------------------|--------------------|--------|
|
||||
| [uni-app](hhttps://github.com/dcloudio/uni-app) | 跨平台框架 | 2.0.0 |
|
||||
| [uni-ui](https://github.com/dcloudio/uni-ui) | 基于 uni-app 的 UI 框架 | 1.4.20 |
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"adminTenentId": "1",
|
||||
|
||||
"appApi": "http://127.0.0.1:48080/app-api",
|
||||
"appToken": "test1",
|
||||
"appToken": "test247",
|
||||
"appTenentId": "1"
|
||||
},
|
||||
"gateway": {
|
||||
|
|
4
pom.xml
4
pom.xml
|
@ -14,12 +14,12 @@
|
|||
<module>yudao-server</module>
|
||||
<!-- 各种 module 拓展 -->
|
||||
<module>yudao-module-member</module>
|
||||
<module>yudao-module-bpm</module>
|
||||
<module>yudao-module-system</module>
|
||||
<module>yudao-module-infra</module>
|
||||
<module>yudao-module-pay</module>
|
||||
<module>yudao-module-mall</module>
|
||||
<!-- <module>yudao-module-bpm</module>-->
|
||||
<module>yudao-module-visualization</module>
|
||||
<!-- <module>yudao-module-mall</module>-->
|
||||
<!-- 示例项目 -->
|
||||
<module>yudao-example</module>
|
||||
</modules>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<!-- DB 相关 -->
|
||||
<druid.version>1.2.15</druid.version>
|
||||
<mybatis-plus.version>3.5.2</mybatis-plus.version>
|
||||
<mybatis-plus-generator.version>3.5.3</mybatis-plus-generator.version>
|
||||
<mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
|
||||
<dynamic-datasource.version>3.6.0</dynamic-datasource.version>
|
||||
<redisson.version>3.18.0</redisson.version>
|
||||
<!-- 服务保障相关 -->
|
||||
|
@ -56,6 +56,7 @@
|
|||
<tika-core.version>2.6.0</tika-core.version>
|
||||
<aj-captcha.version>1.3.0</aj-captcha.version>
|
||||
<netty-all.version>4.1.85.Final</netty-all.version>
|
||||
<ip2region.version>2.6.6</ip2region.version>
|
||||
<!-- 三方云服务相关 -->
|
||||
<okio.version>3.0.0</okio.version>
|
||||
<okhttp3.version>4.10.0</okhttp3.version>
|
||||
|
@ -130,6 +131,11 @@
|
|||
<artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-captcha</artifactId>
|
||||
|
@ -503,6 +509,12 @@
|
|||
<version>${netty-all.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
<version>${ip2region.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 三方云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<module>yudao-spring-boot-starter-biz-tenant</module>
|
||||
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
||||
<module>yudao-spring-boot-starter-biz-error-code</module>
|
||||
<module>yudao-spring-boot-starter-biz-ip</module>
|
||||
|
||||
<module>yudao-spring-boot-starter-flowable</module>
|
||||
<module>yudao-spring-boot-starter-captcha</module>
|
||||
|
|
|
@ -20,7 +20,6 @@ public enum CommonStatusEnum implements IntArrayValuable {
|
|||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
|
||||
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
|
|
|
@ -15,11 +15,12 @@ import java.util.Arrays;
|
|||
@Getter
|
||||
public enum TerminalEnum implements IntArrayValuable {
|
||||
|
||||
//TODO terminal 重复,请参考 '订单来源终端:[1:小程序 2:H5 3:iOS 4:安卓]'
|
||||
MINI_PROGRAM(1, "小程序"),
|
||||
H5(2, "H5"),
|
||||
IOS(3, "iOS"),
|
||||
ANDROID(3, "安卓"),;
|
||||
WECHAT_MINI_PROGRAM(10, "微信小程序"),
|
||||
WECHAT_WAP(11, "微信公众号"),
|
||||
H5(20, "H5 网页"),
|
||||
IOS(31, "苹果 App"),
|
||||
ANDROID(32, "安卓 App"),
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ public class DateUtils {
|
|||
|
||||
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static final String FORMAT_HOUR_MINUTE_SECOND = "HH:mm:ss";
|
||||
|
||||
/**
|
||||
* 将 LocalDateTime 转换成 Date
|
||||
*
|
||||
|
|
|
@ -12,6 +12,11 @@ import java.time.LocalDateTime;
|
|||
*/
|
||||
public class LocalDateTimeUtils {
|
||||
|
||||
/**
|
||||
* 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
|
||||
*/
|
||||
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
|
||||
|
||||
public static LocalDateTime addTime(Duration duration) {
|
||||
return LocalDateTime.now().plus(duration);
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
|
|||
// 调用
|
||||
Expression expression = rule.getExpression(tableName, tableAlias);
|
||||
// 断言
|
||||
assertEquals("u.dept_id IN (10, 20) OR u.id = 1", expression.toString());
|
||||
assertEquals("(u.dept_id IN (10, 20) OR u.id = 1)", expression.toString());
|
||||
assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>IP 拓展,支持如下功能:
|
||||
1. IP 功能:查询 IP 对应的城市信息
|
||||
基于 https://gitee.com/lionsoul/ip2region 实现
|
||||
2. 城市功能:查询城市编码对应的城市信息
|
||||
基于 https://github.com/modood/Administrative-divisions-of-China 实现
|
||||
</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<ip2region.version>2.6.6</ip2region.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- IP地址检索 -->
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,55 @@
|
|||
package cn.iocoder.yudao.framework.ip.core;
|
||||
|
||||
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 区域节点,包括国家、省份、城市、地区等信息
|
||||
*
|
||||
* 数据可见 resources/area.csv 文件
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Area {
|
||||
|
||||
/**
|
||||
* 编号 - 全球,即根目录
|
||||
*/
|
||||
public static final Integer ID_GLOBAL = 0;
|
||||
/**
|
||||
* 编号 - 中国
|
||||
*/
|
||||
public static final Integer ID_CHINA = 1;
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* 枚举 {@link AreaTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 父节点
|
||||
*/
|
||||
private Area parent;
|
||||
/**
|
||||
* 子节点
|
||||
*/
|
||||
private List<Area> children;
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package cn.iocoder.yudao.framework.ip.core.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 区域类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum AreaTypeEnum implements IntArrayValuable {
|
||||
|
||||
COUNTRY(1, "国家"),
|
||||
PROVINCE(2, "省份"),
|
||||
CITY(3, "城市"),
|
||||
DISTRICT(4, "地区"), // 县、镇、区等
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AreaTypeEnum::getType).toArray();
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
package cn.iocoder.yudao.framework.ip.core.utils;
|
||||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.csv.CsvRow;
|
||||
import cn.hutool.core.text.csv.CsvUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 区域工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class AreaUtils {
|
||||
|
||||
/**
|
||||
* 初始化 SEARCHER
|
||||
*/
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
private final static AreaUtils INSTANCE = new AreaUtils();
|
||||
|
||||
/**
|
||||
* Area 内存缓存,提升访问速度
|
||||
*/
|
||||
private static Map<Integer, Area> areas;
|
||||
|
||||
private AreaUtils() {
|
||||
long now = System.currentTimeMillis();
|
||||
areas = new HashMap<>();
|
||||
areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0,
|
||||
null, new ArrayList<>()));
|
||||
// 从 csv 中加载数据
|
||||
List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows();
|
||||
rows.remove(0); // 删除 header
|
||||
for (CsvRow row : rows) {
|
||||
// 创建 Area 对象
|
||||
Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)),
|
||||
null, new ArrayList<>());
|
||||
// 添加到 areas 中
|
||||
areas.put(area.getId(), area);
|
||||
}
|
||||
|
||||
// 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取
|
||||
for (CsvRow row : rows) {
|
||||
Area area = areas.get(Integer.valueOf(row.get(0))); // 自己
|
||||
Area parent = areas.get(Integer.valueOf(row.get(3))); // 父
|
||||
Assert.isTrue(area != parent, "{}:父子节点相同", area.getName());
|
||||
area.setParent(parent);
|
||||
parent.getChildren().add(area);
|
||||
}
|
||||
log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定编号对应的区域
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @return 区域
|
||||
*/
|
||||
public static Area getArea(Integer id) {
|
||||
return areas.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化区域
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @return 格式化后的区域
|
||||
*/
|
||||
public static String format(Integer id) {
|
||||
return format(id, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化区域
|
||||
*
|
||||
* 例如说:
|
||||
* 1. id = “静安区”时:上海 上海市 静安区
|
||||
* 2. id = “上海市”时:上海 上海市
|
||||
* 3. id = “上海”时:上海
|
||||
* 4. id = “美国”时:美国
|
||||
* 当区域在中国时,默认不显示中国
|
||||
*
|
||||
* @param id 区域编号
|
||||
* @param separator 分隔符
|
||||
* @return 格式化后的区域
|
||||
*/
|
||||
public static String format(Integer id, String separator) {
|
||||
// 获得区域
|
||||
Area area = areas.get(id);
|
||||
if (area == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 格式化
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < AreaTypeEnum.values().length; i++) { // 避免死循环
|
||||
sb.insert(0, area.getName());
|
||||
// “递归”父节点
|
||||
area = area.getParent();
|
||||
if (area == null
|
||||
|| ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况
|
||||
break;
|
||||
}
|
||||
sb.insert(0, separator);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package cn.iocoder.yudao.framework.ip.core.utils;
|
||||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* IP 工具类
|
||||
*
|
||||
* IP 数据源来自 ip2region.xdb 精简版,基于 <a href="https://gitee.com/zhijiantianya/ip2region"/> 项目
|
||||
*
|
||||
* @author wanglhup
|
||||
*/
|
||||
@Slf4j
|
||||
public class IPUtils {
|
||||
|
||||
/**
|
||||
* 初始化 SEARCHER
|
||||
*/
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
private final static IPUtils INSTANCE = new IPUtils();
|
||||
|
||||
/**
|
||||
* IP 查询器,启动加载到内存中
|
||||
*/
|
||||
private static Searcher SEARCHER;
|
||||
|
||||
/**
|
||||
* 私有化构造
|
||||
*/
|
||||
private IPUtils() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
byte[] bytes = ResourceUtil.readBytes("ip2region.xdb");
|
||||
SEARCHER = Searcher.newWithBuffer(bytes);
|
||||
log.info("启动加载 IPUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
||||
} catch (IOException e) {
|
||||
log.error("启动加载 IPUtils 失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区编号
|
||||
*
|
||||
* @param ip IP 地址,格式为 127.0.0.1
|
||||
* @return 地区id
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Integer getAreaId(String ip) {
|
||||
return Integer.parseInt(SEARCHER.search(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区编号
|
||||
*
|
||||
* @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回
|
||||
* @return 地区编号
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Integer getAreaId(long ip) {
|
||||
return Integer.parseInt(SEARCHER.search(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区
|
||||
*
|
||||
* @param ip IP 地址,格式为 127.0.0.1
|
||||
* @return 地区
|
||||
*/
|
||||
public static Area getArea(String ip) {
|
||||
return AreaUtils.getArea(getAreaId(ip));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 对应的地区
|
||||
*
|
||||
* @param ip IP 地址的时间戳,格式参考{@link Searcher#checkIP(String)} 的返回
|
||||
* @return 地区
|
||||
*/
|
||||
public static Area getArea(long ip) {
|
||||
return AreaUtils.getArea(getAreaId(ip));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* IP 拓展,支持如下功能:
|
||||
*
|
||||
* 1. IP 功能:查询 IP 对应的城市信息
|
||||
* 基于 https://gitee.com/lionsoul/ip2region 实现
|
||||
* 2. 城市功能:查询城市编码对应的城市信息
|
||||
* 基于 https://github.com/modood/Administrative-divisions-of-China 实现
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
package cn.iocoder.yudao.framework.ip;
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -0,0 +1,36 @@
|
|||
package cn.iocoder.yudao.framework.ip.core.utils;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link AreaUtils} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AreaUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testGetArea() {
|
||||
// 调用:北京
|
||||
Area area = AreaUtils.getArea(110100);
|
||||
// 断言
|
||||
assertEquals(area.getId(), 110100);
|
||||
assertEquals(area.getName(), "北京市");
|
||||
assertEquals(area.getType(), AreaTypeEnum.CITY.getType());
|
||||
assertEquals(area.getParent().getId(), 110000);
|
||||
assertEquals(area.getChildren().size(), 16);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormat() {
|
||||
assertEquals(AreaUtils.format(110105), "北京 北京市 朝阳区");
|
||||
assertEquals(AreaUtils.format(1), "中国");
|
||||
assertEquals(AreaUtils.format(2), "蒙古");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package cn.iocoder.yudao.framework.ip.core.utils;
|
||||
|
||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lionsoul.ip2region.xdb.Searcher;
|
||||
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link IPUtils} 的单元测试
|
||||
*
|
||||
* @author wanglhup
|
||||
*/
|
||||
public class IPUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testGetAreaId_string() {
|
||||
// 120.202.4.0|120.202.4.255|420600
|
||||
Integer areaId = IPUtils.getAreaId("120.202.4.50");
|
||||
assertEquals(420600, areaId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAreaId_long() throws Exception {
|
||||
// 120.203.123.0|120.203.133.255|360900
|
||||
long ip = Searcher.checkIP("120.203.123.250");
|
||||
Integer areaId = IPUtils.getAreaId(ip);
|
||||
assertEquals(360900, areaId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetArea_string() {
|
||||
// 120.202.4.0|120.202.4.255|420600
|
||||
Area area = IPUtils.getArea("120.202.4.50");
|
||||
assertEquals("襄阳市", area.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetArea_long() throws Exception {
|
||||
// 120.203.123.0|120.203.133.255|360900
|
||||
long ip = Searcher.checkIP("120.203.123.252");
|
||||
Area area = IPUtils.getArea(ip);
|
||||
assertEquals("宜春市", area.getName());
|
||||
}
|
||||
|
||||
}
|
|
@ -13,26 +13,23 @@ import javax.validation.constraints.NotEmpty;
|
|||
public class PayProperties {
|
||||
|
||||
/**
|
||||
* 支付回调地址
|
||||
* 回调地址
|
||||
*
|
||||
* 实际上,对应的 PayNotifyController 的 notifyCallback 方法的 URL
|
||||
*
|
||||
* 注意,支付渠道统一回调到 payNotifyUrl 地址,由支付模块统一处理;然后,自己的支付模块,在回调 PayAppDO.payNotifyUrl 地址
|
||||
*/
|
||||
@NotEmpty(message = "支付回调地址不能为空")
|
||||
@URL(message = "支付回调地址的格式必须是 URL")
|
||||
private String payNotifyUrl;
|
||||
/**
|
||||
* 退款回调地址
|
||||
* 注意点,同 {@link #payNotifyUrl} 属性
|
||||
*/
|
||||
@NotEmpty(message = "退款回调地址不能为空")
|
||||
@URL(message = "退款回调地址的格式必须是 URL")
|
||||
private String refundNotifyUrl;
|
||||
|
||||
@NotEmpty(message = "回调地址不能为空")
|
||||
@URL(message = "回调地址的格式必须是 URL")
|
||||
private String callbackUrl;
|
||||
|
||||
/**
|
||||
* 支付完成的返回地址
|
||||
* 回跳地址
|
||||
*
|
||||
* 实际上,对应的 PayNotifyController 的 returnCallback 方法的 URL
|
||||
*/
|
||||
@URL(message = "支付返回的地址的格式必须是 URL")
|
||||
@NotEmpty(message = "支付返回的地址不能为空")
|
||||
private String payReturnUrl;
|
||||
@URL(message = "回跳地址的格式必须是 URL")
|
||||
@NotEmpty(message = "回跳地址不能为空")
|
||||
private String returnUrl;
|
||||
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ public class PayNotifyDataDTO {
|
|||
*/
|
||||
private String body;
|
||||
|
||||
|
||||
/**
|
||||
* HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
||||
*/
|
||||
private Map<String,String> params;
|
||||
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ public class PayOrderUnifiedReqDTO {
|
|||
*/
|
||||
@NotNull(message = "支付金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
||||
private Long amount;
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 支付过期时间
|
||||
|
|
|
@ -63,7 +63,7 @@ public class PayRefundUnifiedReqDTO {
|
|||
*/
|
||||
@NotNull(message = "退款金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
||||
private Long amount;
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要
|
||||
|
|
|
@ -69,7 +69,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||
this.init();
|
||||
}
|
||||
|
||||
protected Double calculateAmount(Long amount) {
|
||||
protected Double calculateAmount(Integer amount) {
|
||||
return amount / 100.0;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ import com.github.binarywang.wxpay.service.WxPayService;
|
|||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -119,7 +121,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.setTotal(reqDTO
|
||||
.getAmount()
|
||||
.intValue())); // 单位分
|
||||
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式
|
||||
request.setTimeExpire(DateUtil.format(Date.from(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()).toInstant()), "yyyy-MM-dd'T'HH:mm:ssXXX")); // v3的时间格式
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
|
||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
|
@ -167,7 +169,8 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
return PayOrderNotifyRespDTO
|
||||
.builder()
|
||||
.orderExtensionNo(result.getOutTradeNo())
|
||||
.channelOrderNo(result.getTradeState())
|
||||
.channelOrderNo(result.getTransactionId())
|
||||
.channelUserId(result.getPayer().getOpenid())
|
||||
.successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody())
|
||||
.build();
|
||||
|
|
|
@ -24,6 +24,8 @@ import com.github.binarywang.wxpay.service.WxPayService;
|
|||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -98,7 +100,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
.outTradeNo(reqDTO.getMerchantOrderId())
|
||||
.body(reqDTO.getBody())
|
||||
.totalFee(reqDTO.getAmount().intValue()) // 单位分
|
||||
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.timeExpire(DateUtil.format(Date.from(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()).toInstant()), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.spbillCreateIp(reqDTO.getUserIp())
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
.productId(tradeType)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
@ -26,6 +26,8 @@ import com.github.binarywang.wxpay.service.WxPayService;
|
|||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -98,8 +100,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
||||
.outTradeNo(reqDTO.getMerchantOrderId())
|
||||
.body(reqDTO.getBody())
|
||||
.totalFee(reqDTO.getAmount().intValue()) // 单位分
|
||||
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.totalFee(reqDTO.getAmount()) // 单位分
|
||||
.timeExpire(formatDate(reqDTO.getExpireTime()))
|
||||
.spbillCreateIp(reqDTO.getUserIp())
|
||||
.openid(getOpenid(reqDTO))
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
|
@ -113,8 +115,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||
request.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
request.setDescription(reqDTO.getBody());
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
|
||||
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
|
||||
request.setTimeExpire(formatDate(reqDTO.getExpireTime()));
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
|
||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
|
@ -194,4 +196,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static String formatDate(LocalDateTime time) {
|
||||
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), "yyyy-MM-dd'T'HH:mm:ssXXX");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.framework.core.client.impl;
|
||||
package cn.iocoder.yudao.framework.pay.core.client.impl;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
|||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
|
||||
|
@ -14,6 +13,7 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.alipay.api.response.AlipayTradePrecreateResponse;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
|
@ -24,7 +24,8 @@ import java.io.FileNotFoundException;
|
|||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class PayClientFactoryImplTest {
|
||||
@Disabled
|
||||
public class PayClientFactoryImplIntegrationTest {
|
||||
|
||||
private final PayClientFactoryImpl payClientFactory = new PayClientFactoryImpl();
|
||||
|
||||
|
@ -91,7 +92,7 @@ public class PayClientFactoryImplTest {
|
|||
PayClient client = payClientFactory.getPayClient(channelId);
|
||||
// 发起支付
|
||||
PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
|
||||
reqDTO.setNotifyUrl("http://niubi.natapp1.cc/api/pay/order/notify/alipay-qr/1"); // TODO @tina: 这里改成你的 natapp 回调地址
|
||||
reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
|
||||
CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
|
||||
System.out.println(JsonUtils.toJsonString(result));
|
||||
System.out.println(result.getData().getQrCode());
|
||||
|
@ -121,7 +122,7 @@ public class PayClientFactoryImplTest {
|
|||
|
||||
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
|
||||
PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
|
||||
reqDTO.setAmount(123L);
|
||||
reqDTO.setAmount(123);
|
||||
reqDTO.setSubject("IPhone 13");
|
||||
reqDTO.setBody("biubiubiu");
|
||||
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
|
|
@ -73,7 +73,7 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
|
|||
Long shopOrderId = System.currentTimeMillis();
|
||||
PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
|
||||
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
|
||||
reqDTO.setAmount(1L);
|
||||
reqDTO.setAmount(1);
|
||||
reqDTO.setBody("内容:" + shopOrderId);
|
||||
reqDTO.setSubject("标题:"+shopOrderId);
|
||||
String notify="http://niubi.natapp1.cc/api/pay/order/notify";
|
||||
|
|
|
@ -35,8 +35,8 @@ public class AliyunSmsCodeMapping implements SmsCodeMapping {
|
|||
case "isv.OUT_OF_SERVICE": return SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
|
||||
case "isv.MOBILE_NUMBER_ILLEGAL": return SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID;
|
||||
case "isv.TEMPLATE_MISSING_PARAMETERS": return SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
|
||||
default: return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.tenant.core.aop;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
|
@ -11,6 +12,8 @@ import org.aspectj.lang.annotation.Aspect;
|
|||
* 例如说,一个定时任务,读取所有数据,进行处理。
|
||||
* 又例如说,读取所有数据,进行缓存。
|
||||
*
|
||||
* 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Aspect
|
||||
|
|
|
@ -2,6 +2,10 @@ package cn.iocoder.yudao.framework.tenant.core.util;
|
|||
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* 多租户 Util
|
||||
*
|
||||
|
@ -32,4 +36,32 @@ public class TenantUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略租户,执行对应的逻辑
|
||||
*
|
||||
* @param runnable 逻辑
|
||||
*/
|
||||
public static void executeIgnore(Runnable runnable) {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
// 执行逻辑
|
||||
runnable.run();
|
||||
} finally {
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多租户编号,添加到 header 中
|
||||
*
|
||||
* @param headers HTTP 请求 headers
|
||||
*/
|
||||
public static void addTenantHeader(Map<String, String> headers) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId != null) {
|
||||
headers.put(HEADER_TENANT_ID, tenantId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import io.minio.*;
|
|||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
|
||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
|
||||
|
||||
/**
|
||||
* 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
|
||||
*
|
||||
* <p>
|
||||
* S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -78,6 +79,11 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
|||
.replaceAll("-internal", "")// 去除内网 Endpoint 的后缀
|
||||
.replaceAll("https://", "");
|
||||
}
|
||||
// 腾讯云必须有 region,否则会报错
|
||||
if (config.getEndpoint().contains(ENDPOINT_TENCENT)) {
|
||||
return StrUtil.subAfter(config.getEndpoint(), ".cos.", false)
|
||||
.replaceAll("." + ENDPOINT_TENCENT, ""); // 去除 Endpoint
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ public class S3FileClientConfig implements FileClientConfig {
|
|||
|
||||
public static final String ENDPOINT_QINIU = "qiniucs.com";
|
||||
public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
|
||||
public static final String ENDPOINT_TENCENT = "myqcloud.com";
|
||||
|
||||
/**
|
||||
* 节点地址
|
||||
|
|
|
@ -8,8 +8,11 @@ import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
|
|||
import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;
|
||||
import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
|
||||
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
|
||||
import cn.iocoder.yudao.framework.mq.job.RedisPendingMessageResendJob;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisServerCommands;
|
||||
|
@ -24,7 +27,7 @@ import org.springframework.data.redis.listener.ChannelTopic;
|
|||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
|
||||
import org.springframework.data.redis.stream.StreamMessageListenerContainer;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
@ -35,6 +38,7 @@ import java.util.Properties;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
|
||||
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
|
||||
public class YudaoMQAutoConfiguration {
|
||||
|
||||
|
@ -69,9 +73,20 @@ public class YudaoMQAutoConfiguration {
|
|||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Redis Stream 重新消费的任务
|
||||
*/
|
||||
@Bean
|
||||
public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractStreamMessageListener<?>> listeners,
|
||||
RedisMQTemplate redisTemplate,
|
||||
@Value("${spring.application.name}") String groupName,
|
||||
RedissonClient redissonClient) {
|
||||
return new RedisPendingMessageResendJob(listeners, redisTemplate, groupName, redissonClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Redis Stream 集群消费的容器
|
||||
*
|
||||
* <p>
|
||||
* Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html
|
||||
*/
|
||||
@Bean(initMethod = "start", destroyMethod = "stop")
|
||||
|
@ -99,7 +114,8 @@ public class YudaoMQAutoConfiguration {
|
|||
// 创建 listener 对应的消费者分组
|
||||
try {
|
||||
redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
|
||||
} catch (Exception ignore) {}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
// 设置 listener 对应的 redisTemplate
|
||||
listener.setRedisMQTemplate(redisMQTemplate);
|
||||
// 创建 Consumer 对象
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
package cn.iocoder.yudao.framework.mq.job;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
|
||||
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.data.redis.connection.stream.Consumer;
|
||||
import org.springframework.data.redis.connection.stream.MapRecord;
|
||||
import org.springframework.data.redis.connection.stream.PendingMessagesSummary;
|
||||
import org.springframework.data.redis.connection.stream.ReadOffset;
|
||||
import org.springframework.data.redis.connection.stream.StreamOffset;
|
||||
import org.springframework.data.redis.connection.stream.StreamRecords;
|
||||
import org.springframework.data.redis.core.StreamOperations;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 这个任务用于处理,crash 之后的消费者未消费完的消息
|
||||
*/
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class RedisPendingMessageResendJob {
|
||||
|
||||
private static final String LOCK_KEY = "redis:pending:msg:lock";
|
||||
|
||||
private final List<AbstractStreamMessageListener<?>> listeners;
|
||||
private final RedisMQTemplate redisTemplate;
|
||||
private final String groupName;
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题
|
||||
*/
|
||||
@Scheduled(cron = "35 * * * * ?")
|
||||
public void messageResend() {
|
||||
RLock lock = redissonClient.getLock(LOCK_KEY);
|
||||
// 尝试加锁
|
||||
if (lock.tryLock()) {
|
||||
try {
|
||||
execute();
|
||||
} catch (Exception ex) {
|
||||
log.error("[messageResend][执行异常]", ex);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void execute() {
|
||||
StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
|
||||
listeners.forEach(listener -> {
|
||||
PendingMessagesSummary pendingMessagesSummary = ops.pending(listener.getStreamKey(), groupName);
|
||||
// 每个消费者的 pending 队列消息数量
|
||||
Map<String, Long> pendingMessagesPerConsumer = pendingMessagesSummary.getPendingMessagesPerConsumer();
|
||||
pendingMessagesPerConsumer.forEach((consumerName, pendingMessageCount) -> {
|
||||
log.info("[processPendingMessage][消费者({}) 消息数量({})]", consumerName, pendingMessageCount);
|
||||
|
||||
// 从消费者的 pending 队列中读取消息
|
||||
List<MapRecord<String, Object, Object>> records = ops.read(Consumer.from(groupName, consumerName), StreamOffset.create(listener.getStreamKey(), ReadOffset.from("0")));
|
||||
if (CollUtil.isEmpty(records)) {
|
||||
return;
|
||||
}
|
||||
for (MapRecord<String, Object, Object> record : records) {
|
||||
// 重新投递消息
|
||||
redisTemplate.getRedisTemplate().opsForStream().add(StreamRecords.newRecord()
|
||||
.ofObject(record.getValue()) // 设置内容
|
||||
.withStreamKey(listener.getStreamKey()));
|
||||
|
||||
// ack 消息消费完成
|
||||
redisTemplate.getRedisTemplate().opsForStream().acknowledge(groupName, record);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.mybatis.core.mapper;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
|
@ -75,9 +76,13 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
|
|||
return selectList(new LambdaQueryWrapper<T>().in(field, values));
|
||||
}
|
||||
|
||||
default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {
|
||||
return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 逐条插入,适合少量数据插入,或者对性能要求不高的场景
|
||||
*
|
||||
* <p>
|
||||
* 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
|
||||
* 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
|
||||
*
|
||||
|
|
|
@ -33,6 +33,10 @@ public class AssertUtils {
|
|||
public static void assertPojoEquals(Object expected, Object actual, String... ignoreFields) {
|
||||
Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
|
||||
Arrays.stream(expectedFields).forEach(expectedField -> {
|
||||
// 忽略 jacoco 自动生成的 $jacocoData 属性的情况
|
||||
if (expectedField.isSynthetic()) {
|
||||
return;
|
||||
}
|
||||
// 如果是忽略的属性,则不进行比对
|
||||
if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package cn.iocoder.yudao.framework.jackson.core.databind;
|
||||
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_HOUR_MINUTE_SECOND;
|
||||
|
||||
public class LocalTimeJson {
|
||||
|
||||
public static final LocalTimeSerializer SERIALIZER = new LocalTimeSerializer(DateTimeFormatter
|
||||
.ofPattern(FORMAT_HOUR_MINUTE_SECOND)
|
||||
.withZone(ZoneId.systemDefault()));
|
||||
|
||||
public static final LocalTimeDeserializer DESERIALIZABLE = new LocalTimeDeserializer(DateTimeFormatter
|
||||
.ofPattern(FORMAT_HOUR_MINUTE_SECOND)
|
||||
.withZone(ZoneId.systemDefault()));
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@ spring:
|
|||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package cn.iocoder.yudao.module.infra.dal.mysql.file;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.file.core.client.db.DBFileContentFrameworkDAO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public class FileContentDAOImpl implements DBFileContentFrameworkDAO {
|
||||
|
@ -27,9 +30,11 @@ public class FileContentDAOImpl implements DBFileContentFrameworkDAO {
|
|||
|
||||
@Override
|
||||
public byte[] selectContent(Long configId, String path) {
|
||||
FileContentDO fileContentDO = fileContentMapper.selectOne(
|
||||
buildQuery(configId, path).select(FileContentDO::getContent));
|
||||
return fileContentDO != null ? fileContentDO.getContent() : null;
|
||||
List<FileContentDO> list = fileContentMapper.selectList(
|
||||
buildQuery(configId, path).select(FileContentDO::getContent).orderByDesc(FileContentDO::getId));
|
||||
return Optional.ofNullable(CollUtil.getFirst(list))
|
||||
.map(FileContentDO::getContent)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<FileContentDO> buildQuery(Long configId, String path) {
|
||||
|
|
|
@ -18,6 +18,8 @@ import org.springframework.stereotype.Component;
|
|||
import java.util.*;
|
||||
|
||||
import static cn.hutool.core.text.CharSequenceUtil.*;
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
import static cn.hutool.core.util.RandomUtil.randomInt;
|
||||
|
||||
/**
|
||||
* 代码生成器的 Builder,负责:
|
||||
|
@ -128,6 +130,7 @@ public class CodegenBuilder {
|
|||
// 初始化 Column 列的默认字段
|
||||
processColumnOperation(column); // 处理 CRUD 相关的字段的默认值
|
||||
processColumnUI(column); // 处理 UI 相关的字段的默认值
|
||||
processColumnExample(column); // 处理字段的 swagger example 示例
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
@ -169,4 +172,42 @@ public class CodegenBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字段的 swagger example 示例
|
||||
*
|
||||
* @param column 字段
|
||||
*/
|
||||
private void processColumnExample(CodegenColumnDO column) {
|
||||
// id、price、count 等可能是整数的后缀
|
||||
if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "id", "price", "count")) {
|
||||
column.setExample(String.valueOf(randomInt(1, Short.MAX_VALUE)));
|
||||
return;
|
||||
}
|
||||
// name
|
||||
if (StrUtil.endWithIgnoreCase(column.getJavaField(), "name")) {
|
||||
column.setExample(randomEle(new String[]{"张三", "李四", "王五", "赵六", "芋艿"}));
|
||||
return;
|
||||
}
|
||||
// status
|
||||
if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), "status", "type")) {
|
||||
column.setExample(randomEle(new String[]{"1", "2"}));
|
||||
return;
|
||||
}
|
||||
// url
|
||||
if (StrUtil.endWithIgnoreCase(column.getColumnName(), "url")) {
|
||||
column.setExample("https://www.iocoder.cn");
|
||||
return;
|
||||
}
|
||||
// reason
|
||||
if (StrUtil.endWithIgnoreCase(column.getColumnName(), "reason")) {
|
||||
column.setExample(randomEle(new String[]{"不喜欢", "不对", "不好", "不香"}));
|
||||
return;
|
||||
}
|
||||
// description、memo、remark
|
||||
if (StrUtil.endWithAnyIgnoreCase(column.getColumnName(), "description", "memo", "remark")) {
|
||||
column.setExample(randomEle(new String[]{"你猜", "随便", "你说的对"}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.module.infra.service.file;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
|||
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -79,20 +77,36 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
@Resource
|
||||
@Lazy // 注入自己,所以延迟加载
|
||||
private FileConfigService self;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initFileClients() {
|
||||
// 获取文件配置,如果有更新
|
||||
List<FileConfigDO> configs = loadFileConfigIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(configs)) {
|
||||
return;
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
// 创建或更新支付 Client
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
List<FileConfigDO> configs = fileConfigMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存文件配置,数量为:{}]", configs.size());
|
||||
|
||||
// 第二步:构建缓存。创建或更新文件 Client
|
||||
configs.forEach(config -> {
|
||||
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
|
||||
// 如果是 master,进行设置
|
||||
|
@ -101,35 +115,8 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||
}
|
||||
});
|
||||
|
||||
// 写入缓存
|
||||
maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
|
||||
log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
self.initFileClients();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
|
||||
* 如果未发生变化,则返回空
|
||||
*
|
||||
* @param maxUpdateTime 当前文件配置的最大更新时间
|
||||
* @return 文件配置列表
|
||||
*/
|
||||
private List<FileConfigDO> loadFileConfigIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
|
||||
} else { // 判断数据库中是否有更新的文件配置
|
||||
if (fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有文件配置
|
||||
return fileConfigMapper.selectList();
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 列表 -->
|
||||
<vxe-grid ref="xGrid" v-bind="gridOptions" class="xtable-scrollbar">
|
||||
<XTable @register="registerTable">
|
||||
<template #toolbar_buttons>
|
||||
<!-- 操作:新增 -->
|
||||
<XButton
|
||||
|
@ -43,7 +43,7 @@
|
|||
@click="handleDelete(row.id)"
|
||||
/>
|
||||
</template>
|
||||
</vxe-grid>
|
||||
</XTable>
|
||||
</ContentWrap>
|
||||
<!-- 弹窗 -->
|
||||
<XModal id="${classNameVar}Model" :loading="modelLoading" v-model="modelVisible" :title="modelTitle">
|
||||
|
@ -79,8 +79,7 @@
|
|||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useMessage } from '@/hooks/web/useMessage'
|
||||
import { useVxeGrid } from '@/hooks/web/useVxeGrid'
|
||||
import { VxeGridInstance } from 'vxe-table'
|
||||
import { useXTable } from '@/hooks/web/useXTable'
|
||||
import { FormExpose } from '@/components/Form'
|
||||
// 业务相关的 import
|
||||
import { rules, allSchemas } from './${classNameVar}.data'
|
||||
|
@ -90,8 +89,7 @@ const { t } = useI18n() // 国际化
|
|||
const message = useMessage() // 消息弹窗
|
||||
|
||||
// 列表相关的变量
|
||||
const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
|
||||
const { gridOptions, getList, deleteData, exportList } = useVxeGrid<${simpleClassName}Api.${simpleClassName}VO>({
|
||||
const [registerTable, { reload, deleteData, exportList }] = useXTable({
|
||||
allSchemas: allSchemas,
|
||||
getListApi: ${simpleClassName}Api.get${simpleClassName}PageApi,
|
||||
deleteApi: ${simpleClassName}Api.delete${simpleClassName}Api,
|
||||
|
@ -123,7 +121,7 @@ const handleCreate = () => {
|
|||
|
||||
// 导出操作
|
||||
const handleExport = async () => {
|
||||
await exportList(xGrid, '${table.classComment}.xls')
|
||||
await exportList('${table.classComment}.xls')
|
||||
}
|
||||
|
||||
// 修改操作
|
||||
|
@ -145,7 +143,7 @@ const handleDetail = async (rowId: number) => {
|
|||
|
||||
// 删除操作
|
||||
const handleDelete = async (rowId: number) => {
|
||||
await deleteData(xGrid, rowId)
|
||||
await deleteData(rowId)
|
||||
}
|
||||
|
||||
// 提交按钮
|
||||
|
@ -169,7 +167,7 @@ const submitForm = async () => {
|
|||
} finally {
|
||||
actionLoading.value = false
|
||||
// 刷新列表
|
||||
await getList(xGrid)
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -79,7 +79,7 @@ public class FileServiceTest extends BaseDbUnitTest {
|
|||
FileClient client = mock(FileClient.class);
|
||||
when(fileConfigService.getMasterFileClient()).thenReturn(client);
|
||||
String url = randomString();
|
||||
when(client.upload(same(content), same(path), same("image/jpeg"))).thenReturn(url);
|
||||
when(client.upload(same(content), same(path), eq("image/jpeg"))).thenReturn(url);
|
||||
when(client.getId()).thenReturn(10L);
|
||||
String name = "单测文件名";
|
||||
// 调用
|
||||
|
|
|
@ -9,7 +9,7 @@ spring:
|
|||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
|
|
|
@ -15,18 +15,15 @@
|
|||
<name>${project.artifactId}</name>
|
||||
|
||||
<description>
|
||||
商城大模块,由 product 商品、promotion 营销、trade 交易 coupon等组成
|
||||
商城大模块,由 product 商品、promotion 营销、trade 交易等组成
|
||||
</description>
|
||||
<modules>
|
||||
<!-- <module>yudao-module-coupon-api</module>-->
|
||||
<!-- <module>yudao-module-coupon-biz</module>-->
|
||||
<module>yudao-module-promotion-api</module>
|
||||
<module>yudao-module-promotion-biz</module>
|
||||
<module>yudao-module-product-api</module>
|
||||
<module>yudao-module-product-biz</module>
|
||||
<module>yudao-module-trade-api</module>
|
||||
<module>yudao-module-trade-biz</module>
|
||||
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.module.product.api.property;
|
||||
|
||||
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品属性值 API 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface ProductPropertyValueApi {
|
||||
|
||||
/**
|
||||
* 根据编号数组,获得属性值列表
|
||||
*
|
||||
* @param ids 编号数组
|
||||
* @return 属性值明细列表
|
||||
*/
|
||||
List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids);
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package cn.iocoder.yudao.module.product.api.property.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 商品属性项的明细 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class ProductPropertyValueDetailRespDTO {
|
||||
|
||||
/**
|
||||
* 属性的编号
|
||||
*/
|
||||
private Long propertyId;
|
||||
|
||||
/**
|
||||
* 属性的名称
|
||||
*/
|
||||
private String propertyName;
|
||||
|
||||
/**
|
||||
* 属性值的编号
|
||||
*/
|
||||
private Long valueId;
|
||||
|
||||
/**
|
||||
* 属性值的名称
|
||||
*/
|
||||
private String valueName;
|
||||
|
||||
}
|
|
@ -18,17 +18,17 @@ public class ProductSkuRespDTO {
|
|||
* 商品 SKU 编号,自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 商品 SKU 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* SPU 编号
|
||||
*/
|
||||
private Long spuId;
|
||||
/**
|
||||
* SPU 名字
|
||||
*/
|
||||
private String spuName;
|
||||
|
||||
/**
|
||||
* 规格值数组,JSON 格式
|
||||
* 属性数组,JSON 格式
|
||||
*/
|
||||
private List<Property> properties;
|
||||
/**
|
||||
|
|
|
@ -15,28 +15,30 @@ public interface ErrorCodeConstants {
|
|||
ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, "父分类不能是二级分类");
|
||||
ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, "存在子分类,无法删除");
|
||||
ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, "商品分类({})已禁用,无法使用");
|
||||
ErrorCode CATEGORY_LEVEL_ERROR = new ErrorCode(1008001005, "商品分类不正确,原因:必须使用第三级的商品分类下");
|
||||
|
||||
// ========== 商品品牌相关编号 1008002000 ==========
|
||||
ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, "品牌不存在");
|
||||
ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, "品牌不存在");
|
||||
ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, "品牌名称已存在");
|
||||
|
||||
// ========== 商品规格名称 1008003000 ==========
|
||||
ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "规格名称不存在");
|
||||
ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "规格名称已存在");
|
||||
// ========== 商品属性项 1008003000 ==========
|
||||
ErrorCode PROPERTY_NOT_EXISTS = new ErrorCode(1008003000, "属性项不存在");
|
||||
ErrorCode PROPERTY_EXISTS = new ErrorCode(1008003001, "属性项的名称已存在");
|
||||
ErrorCode PROPERTY_DELETE_FAIL_VALUE_EXISTS = new ErrorCode(1008003002, "属性项下存在属性值,无法删除");
|
||||
|
||||
// ========== 规格值 1008004000 ==========
|
||||
ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "规格值不存在");
|
||||
ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "规格值已存在");
|
||||
// ========== 商品属性值 1008004000 ==========
|
||||
ErrorCode PROPERTY_VALUE_NOT_EXISTS = new ErrorCode(1008004000, "属性值不存在");
|
||||
ErrorCode PROPERTY_VALUE_EXISTS = new ErrorCode(1008004001, "属性值的名称已存在");
|
||||
|
||||
// ========== 商品 SPU 1008005000 ==========
|
||||
ErrorCode SPU_NOT_EXISTS = new ErrorCode(1008005000, "商品 SPU 不存在");
|
||||
ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1008005001, "商品分类不正确,原因:必须使用第三级的商品分类下");
|
||||
ErrorCode SPU_NOT_ENABLE = new ErrorCode(1008005002, "商品 SPU 不处于上架状态");
|
||||
|
||||
// ========== 商品 SKU 1008006000 ==========
|
||||
ErrorCode SKU_NOT_EXISTS = new ErrorCode(1008006000, "商品 SKU 不存在");
|
||||
ErrorCode SKU_PROPERTIES_DUPLICATED = new ErrorCode(1008006001, "商品 SKU 的属性组合存在重复");
|
||||
ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其规格数必须一致");
|
||||
ErrorCode SPU_ATTR_NUMBERS_MUST_BE_EQUALS = new ErrorCode(1008006002, "一个 SPU 下的每个 SKU,其属性项必须一致");
|
||||
ErrorCode SPU_SKU_NOT_DUPLICATE = new ErrorCode(1008006003, "一个 SPU 下的每个 SKU,必须不重复");
|
||||
ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
|
||||
|
||||
|
|
|
@ -21,11 +21,11 @@ public enum ProductSpuSpecTypeEnum implements IntArrayValuable {
|
|||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuSpecTypeEnum::getType).toArray();
|
||||
|
||||
/**
|
||||
* 规格
|
||||
* 规格类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 规格名
|
||||
* 规格名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
|
|
|
@ -35,4 +35,14 @@ public enum ProductSpuStatusEnum implements IntArrayValuable {
|
|||
return ARRAYS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否处于【上架】状态
|
||||
*
|
||||
* @param status 状态
|
||||
* @return 是否处于【上架】状态
|
||||
*/
|
||||
public static boolean isEnable(Integer status) {
|
||||
return ENABLE.getStatus().equals(status);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package cn.iocoder.yudao.module.product.api.property;
|
||||
|
||||
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
|
||||
import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
|
||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品属性值 API 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class ProductPropertyValueApiImpl implements ProductPropertyValueApi {
|
||||
|
||||
@Resource
|
||||
private ProductPropertyValueService productPropertyValueService;
|
||||
|
||||
@Override
|
||||
public List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids) {
|
||||
return ProductPropertyValueConvert.INSTANCE.convertList02(
|
||||
productPropertyValueService.getPropertyValueDetailList(ids));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.*;
|
||||
import cn.iocoder.yudao.module.product.convert.property.ProductPropertyConvert;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
|
||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
|
||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
@ -13,12 +18,13 @@ import org.springframework.web.bind.annotation.*;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - 规格名称")
|
||||
@Tag(name = "管理后台 - 商品属性项")
|
||||
@RestController
|
||||
@RequestMapping("/product/property")
|
||||
@Validated
|
||||
|
@ -26,16 +32,18 @@ public class ProductPropertyController {
|
|||
|
||||
@Resource
|
||||
private ProductPropertyService productPropertyService;
|
||||
@Resource
|
||||
private ProductPropertyValueService productPropertyValueService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建规格名称")
|
||||
@Operation(summary = "创建属性项")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:create')")
|
||||
public CommonResult<Long> createProperty(@Valid @RequestBody ProductPropertyCreateReqVO createReqVO) {
|
||||
return success(productPropertyService.createProperty(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新规格名称")
|
||||
@Operation(summary = "更新属性项")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:update')")
|
||||
public CommonResult<Boolean> updateProperty(@Valid @RequestBody ProductPropertyUpdateReqVO updateReqVO) {
|
||||
productPropertyService.updateProperty(updateReqVO);
|
||||
|
@ -43,7 +51,7 @@ public class ProductPropertyController {
|
|||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除规格名称")
|
||||
@Operation(summary = "删除属性项")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('product:property:delete')")
|
||||
public CommonResult<Boolean> deleteProperty(@RequestParam("id") Long id) {
|
||||
|
@ -52,32 +60,40 @@ public class ProductPropertyController {
|
|||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得规格名称")
|
||||
@Operation(summary = "获得属性项")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:query')")
|
||||
public CommonResult<ProductPropertyRespVO> getProperty(@RequestParam("id") Long id) {
|
||||
return success(productPropertyService.getProperty(id));
|
||||
return success(ProductPropertyConvert.INSTANCE.convert(productPropertyService.getProperty(id)));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得规格名称列表")
|
||||
@Operation(summary = "获得属性项列表")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:query')")
|
||||
public CommonResult<List<ProductPropertyRespVO>> getPropertyList(@Valid ProductPropertyListReqVO listReqVO) {
|
||||
return success(productPropertyService.getPropertyList(listReqVO));
|
||||
return success(ProductPropertyConvert.INSTANCE.convertList(productPropertyService.getPropertyList(listReqVO)));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得规格名称分页")
|
||||
@Operation(summary = "获得属性项分页")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:query')")
|
||||
public CommonResult<PageResult<ProductPropertyRespVO>> getPropertyPage(@Valid ProductPropertyPageReqVO pageVO) {
|
||||
return success(productPropertyService.getPropertyPage(pageVO));
|
||||
return success(ProductPropertyConvert.INSTANCE.convertPage(productPropertyService.getPropertyPage(pageVO)));
|
||||
}
|
||||
|
||||
@GetMapping("/listAndValue")
|
||||
@Operation(summary = "获得规格名称列表")
|
||||
@GetMapping("/get-value-list")
|
||||
@Operation(summary = "获得属性项列表")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:query')")
|
||||
public CommonResult<List<ProductPropertyAndValueRespVO>> getPropertyAndValueList(@Valid ProductPropertyListReqVO listReqVO) {
|
||||
return success(productPropertyService.getPropertyAndValueList(listReqVO));
|
||||
// 查询属性项
|
||||
List<ProductPropertyDO> keys = productPropertyService.getPropertyList(listReqVO);
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
// 查询属性值
|
||||
List<ProductPropertyValueDO> values = productPropertyValueService.getPropertyValueListByPropertyId(
|
||||
convertSet(keys, ProductPropertyDO::getId));
|
||||
return success(ProductPropertyConvert.INSTANCE.convertList(keys, values));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.product.controller.admin.property;
|
|||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.product.convert.propertyvalue.ProductPropertyValueConvert;
|
||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
@ -20,7 +20,7 @@ import javax.validation.Valid;
|
|||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 规格值名称")
|
||||
@Tag(name = "管理后台 - 商品属性值")
|
||||
@RestController
|
||||
@RequestMapping("/product/property/value")
|
||||
@Validated
|
||||
|
@ -30,14 +30,14 @@ public class ProductPropertyValueController {
|
|||
private ProductPropertyValueService productPropertyValueService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建规格名称")
|
||||
@Operation(summary = "创建属性值")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:create')")
|
||||
public CommonResult<Long> createProperty(@Valid @RequestBody ProductPropertyValueCreateReqVO createReqVO) {
|
||||
return success(productPropertyValueService.createPropertyValue(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新规格名称")
|
||||
@Operation(summary = "更新属性值")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:update')")
|
||||
public CommonResult<Boolean> updateProperty(@Valid @RequestBody ProductPropertyValueUpdateReqVO updateReqVO) {
|
||||
productPropertyValueService.updatePropertyValue(updateReqVO);
|
||||
|
@ -45,7 +45,7 @@ public class ProductPropertyValueController {
|
|||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除规格名称")
|
||||
@Operation(summary = "删除属性值")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('product:property:delete')")
|
||||
public CommonResult<Boolean> deleteProperty(@RequestParam("id") Long id) {
|
||||
|
@ -54,17 +54,17 @@ public class ProductPropertyValueController {
|
|||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得规格名称")
|
||||
@Operation(summary = "获得属性值")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:query')")
|
||||
public CommonResult<ProductPropertyValueRespVO> getProperty(@RequestParam("id") Long id) {
|
||||
return success(productPropertyValueService.getPropertyValue(id));
|
||||
return success(ProductPropertyValueConvert.INSTANCE.convert(productPropertyValueService.getPropertyValue(id)));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得规格名称分页")
|
||||
@Operation(summary = "获得属性值分页")
|
||||
@PreAuthorize("@ss.hasPermission('product:property:query')")
|
||||
public CommonResult<PageResult<ProductPropertyValueRespVO>> getPropertyValuePage(@Valid ProductPropertyValuePageReqVO pageVO) {
|
||||
return success(productPropertyValueService.getPropertyValueListPage(pageVO));
|
||||
return success(ProductPropertyValueConvert.INSTANCE.convertPage(productPropertyValueService.getPropertyValuePage(pageVO)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: ProductPropertyViewRespVO
|
||||
* @Author: franky
|
||||
* @CreateDate: 2022/7/5 21:29
|
||||
* @Version: 1.0.0
|
||||
*/
|
||||
@Schema(description = "管理后台 - 规格名称详情展示 Request VO")
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyViewRespVO {
|
||||
|
||||
@Schema(description = "规格名称id", example = "1")
|
||||
public Long propertyId;
|
||||
|
||||
@Schema(description = "规格名称", example = "内存")
|
||||
public String name;
|
||||
|
||||
@Schema(description = "规格属性值集合", example = "[{\"v1\":11,\"v2\":\"64G\"},{\"v1\":10,\"v2\":\"32G\"}]")
|
||||
public List<Tuple2> propertyValues;
|
||||
|
||||
@Data
|
||||
@Schema(description = "规格属性值元组")
|
||||
public static class Tuple2 {
|
||||
private final long id;
|
||||
private final String name;
|
||||
|
||||
public Tuple2(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,29 +1,35 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
|
||||
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 规格 + 规格值 Response VO")
|
||||
@Schema(description = "管理后台 - 商品属性项 + 属性值 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyAndValueRespVO extends ProductPropertyBaseVO {
|
||||
public class ProductPropertyAndValueRespVO {
|
||||
|
||||
@Schema(description = "规格的编号", required = true, example = "1024")
|
||||
@ApiModelProperty(value = "属性项的编号", required = true, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间", required = true)
|
||||
private LocalDateTime createTime;
|
||||
@ApiModelProperty(value = "属性项的名称", required = true, example = "颜色")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 规格值的集合
|
||||
* 属性值的集合
|
||||
*/
|
||||
private List<ProductPropertyValueRespVO> values;
|
||||
private List<Value> values;
|
||||
|
||||
@ApiModel("管理后台 - 属性值的简单 Response VO")
|
||||
@Data
|
||||
public static class Value {
|
||||
|
||||
@Schema(description = "属性值的编号", required = true, example = "2048")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "属性值的名称", required = true, example = "红色")
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 规格名称 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 商品属性项 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
@Data
|
||||
public class ProductPropertyBaseVO {
|
||||
|
||||
@Schema(description = "规格名称", required = true, example = "颜色")
|
||||
@NotBlank(message = "规格名称不能为空")
|
||||
@Schema(description = "名称", required = true, example = "颜色")
|
||||
@NotBlank(message = "名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "备注", example = "颜色")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "状态-参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
@Schema(description = "管理后台 - 规格名称创建 Request VO")
|
||||
@Schema(description = "管理后台 - 属性项创建 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -4,15 +4,12 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@Schema(description = "管理后台 - 规格名称 List Request VO")
|
||||
@Schema(description = "管理后台 - 属性项 List Request VO")
|
||||
@Data
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyListReqVO {
|
||||
|
||||
@Schema(description = "规格名称", example = "颜色")
|
||||
@Schema(description = "名称", example = "颜色")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
|
|
@ -11,16 +11,16 @@ import java.time.LocalDateTime;
|
|||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 规格名称分页 Request VO")
|
||||
@Schema(description = "管理后台 - 属性项 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "规格名称", example = "颜色")
|
||||
@Schema(description = "名称", example = "颜色")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
@Schema(description = "状态-参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
private Integer status;
|
||||
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
|
|
|
@ -7,13 +7,13 @@ import lombok.ToString;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 规格 + 规格值 Response VO")
|
||||
@Schema(description = "管理后台 - 属性项 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyRespVO extends ProductPropertyBaseVO {
|
||||
|
||||
@Schema(description = "规格的编号", required = true, example = "1024")
|
||||
@Schema(description = "编号", required = true, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间", required = true)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.property;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
@Schema(description = "管理后台 - 规格名称更新 Request VO")
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "管理后台 - 属性项更新 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
@ -6,24 +7,20 @@ import javax.validation.constraints.NotEmpty;
|
|||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 规格值 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 属性值 Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
@Data
|
||||
public class ProductPropertyValueBaseVO {
|
||||
|
||||
@Schema(description = "规格编号", required = true, example = "1024")
|
||||
@NotNull(message = "规格编号不能为空")
|
||||
@Schema(description = "属性项的编号", required = true, example = "1024")
|
||||
@NotNull(message = "属性项的编号不能为空")
|
||||
private Long propertyId;
|
||||
|
||||
@Schema(description = "规格值名字", required = true, example = "红色")
|
||||
@NotEmpty(message = "规格值名字不能为空")
|
||||
@Schema(description = "名称", required = true, example = "红色")
|
||||
@NotEmpty(message = "名称名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
@NotNull(message = "状态不能为空")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "备注", example = "颜色")
|
||||
private String remark;
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
@Schema(description = "管理后台 - 规格值创建 Request VO")
|
||||
@Schema(description = "管理后台 - 商品属性值创建 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@ApiModel("管理后台 - 商品属性值的明细 Response VO")
|
||||
@Data
|
||||
public class ProductPropertyValueDetailRespVO {
|
||||
|
||||
@ApiModelProperty(value = "属性的编号", required = true, example = "1")
|
||||
private Long propertyId;
|
||||
|
||||
@ApiModelProperty(value = "属性的名称", required = true, example = "颜色")
|
||||
private String propertyName;
|
||||
|
||||
@ApiModelProperty(value = "属性值的编号", required = true, example = "1024")
|
||||
private Long valueId;
|
||||
|
||||
@ApiModelProperty(value = "属性值的名称", required = true, example = "红色")
|
||||
private String valueName;
|
||||
|
||||
}
|
|
@ -5,26 +5,20 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 规格名称值分页 Request VO")
|
||||
@Schema(description = "管理后台 - 商品属性值分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyValuePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "规格id", example = "1024")
|
||||
@Schema(description = "属性项的编号", example = "1024")
|
||||
private String propertyId;
|
||||
|
||||
@Schema(description = "规格值", example = "红色")
|
||||
@Schema(description = "名称", example = "红色")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
@Schema(description = "状态 - 参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import lombok.ToString;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 规格值 Response VO")
|
||||
@Schema(description = "管理后台 - 商品属性值 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ProductPropertyValueRespVO extends ProductPropertyValueBaseVO {
|
||||
|
||||
@Schema(description = "主键", required = true, example = "10")
|
||||
@Schema(description = "编号", required = true, example = "10")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.property.vo.value;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import javax.validation.constraints.*;
|
||||
|
||||
@Schema(description = "管理后台 - 规格值更新 Request VO")
|
||||
@Schema(description = "管理后台 - 商品属性值更新 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -3,7 +3,9 @@ package cn.iocoder.yudao.module.product.controller.admin.sku.vo;
|
|||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -19,14 +21,14 @@ public class ProductSkuBaseVO {
|
|||
@NotEmpty(message = "商品 SKU 名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "销售价格,单位:分", required = true, example = "1024")
|
||||
@Schema(description = "销售价格,单位:分", required = true, example = "1024", notes = "单位:分")
|
||||
@NotNull(message = "销售价格,单位:分不能为空")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "市场价,单位:分", example = "1024")
|
||||
@Schema(description = "市场价", example = "1024", notes = "单位:分")
|
||||
private Integer marketPrice;
|
||||
|
||||
@Schema(description = "成本价,单位:分", example = "1024")
|
||||
@Schema(description = "成本价", example = "1024", notes = "单位:分")
|
||||
private Integer costPrice;
|
||||
|
||||
@Schema(description = "条形码", example = "haha")
|
||||
|
@ -36,7 +38,7 @@ public class ProductSkuBaseVO {
|
|||
@NotNull(message = "图片地址不能为空")
|
||||
private String picUrl;
|
||||
|
||||
@Schema(description = "SKU 状态,参见 CommonStatusEnum 枚举", required = true, example = "1")
|
||||
@Schema(description = "SKU 状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
|
||||
@NotNull(message = "SKU 状态不能为空")
|
||||
@InEnum(CommonStatusEnum.class)
|
||||
private Integer status;
|
||||
|
@ -48,14 +50,16 @@ public class ProductSkuBaseVO {
|
|||
@Schema(description = "预警预存", example = "1")
|
||||
private Integer warnStock;
|
||||
|
||||
@Schema(description = "商品重量,单位:kg 千克", example = "1")
|
||||
@Schema(description = "商品重量 - 单位:kg 千克", example = "1")
|
||||
private Double weight;
|
||||
|
||||
@Schema(description = "商品体积,单位:m^3 平米", example = "1024")
|
||||
@Schema(description = "商品体积 - 单位:m^3 平米", example = "1024")
|
||||
private Double volume;
|
||||
|
||||
@Schema(description = "规格值")
|
||||
@ApiModel("商品属性")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Property {
|
||||
|
||||
@Schema(description = "属性编号", required = true, example = "1")
|
||||
|
|
|
@ -13,11 +13,8 @@ import java.util.List;
|
|||
@ToString(callSuper = true)
|
||||
public class ProductSkuCreateOrUpdateReqVO extends ProductSkuBaseVO {
|
||||
|
||||
@Schema(description = "商品 SKU 编号", example = "1")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 规格值数组
|
||||
* 属性数组
|
||||
*/
|
||||
private List<Property> properties;
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public class ProductSkuRespVO extends ProductSkuBaseVO {
|
|||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 规格值数组
|
||||
* 属性数组
|
||||
*/
|
||||
private List<Property> properties;
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
### 获得商品 SPU 明细
|
||||
GET {{baseUrl}}/product/spu/get-detail?id=4
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
|
@ -3,8 +3,13 @@ package cn.iocoder.yudao.module.product.controller.admin.spu;
|
|||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
|
||||
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
|
||||
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
|
||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
||||
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
|
||||
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
|
||||
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
|
@ -15,10 +20,11 @@ import org.springframework.web.bind.annotation.*;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
|
||||
|
||||
@Tag(name = "管理后台 - 商品 SPU")
|
||||
@RestController
|
||||
|
@ -27,64 +33,61 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|||
public class ProductSpuController {
|
||||
|
||||
@Resource
|
||||
private ProductSpuService spuService;
|
||||
private ProductSpuService productSpuService;
|
||||
@Resource
|
||||
private ProductSkuService productSkuService;
|
||||
@Resource
|
||||
private ProductPropertyValueService productPropertyValueService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建商品 SPU")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:create')")
|
||||
public CommonResult<Long> createProductSpu(@Valid @RequestBody ProductSpuCreateReqVO createReqVO) {
|
||||
return success(spuService.createSpu(createReqVO));
|
||||
return success(productSpuService.createSpu(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新商品 SPU")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:update')")
|
||||
public CommonResult<Boolean> updateSpu(@Valid @RequestBody ProductSpuUpdateReqVO updateReqVO) {
|
||||
spuService.updateSpu(updateReqVO);
|
||||
productSpuService.updateSpu(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除商品 SPU")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:delete')")
|
||||
public CommonResult<Boolean> deleteSpu(@RequestParam("id") Long id) {
|
||||
spuService.deleteSpu(id);
|
||||
productSpuService.deleteSpu(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
// TODO 芋艿:修改接口
|
||||
@GetMapping("/get/detail")
|
||||
@Operation(summary = "获得商品 SPU")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得商品 SPU 明细")
|
||||
@Parameter(name = "id", value = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:query')")
|
||||
public CommonResult<ProductSpuDetailRespVO> getSpuDetail(@RequestParam("id") Long id) {
|
||||
return success(spuService.getSpuDetail(id));
|
||||
// 获得商品 SPU
|
||||
ProductSpuDO spu = productSpuService.getSpu(id);
|
||||
if (spu == null) {
|
||||
throw exception(SPU_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得商品 SPU")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:query')")
|
||||
public CommonResult<ProductSpuRespVO> getSpu(@RequestParam("id") Long id) {
|
||||
return success(spuService.getSpu(id));
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得商品 SPU 列表")
|
||||
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:query')")
|
||||
public CommonResult<List<ProductSpuRespVO>> getSpuList(@RequestParam("ids") Collection<Long> ids) {
|
||||
List<ProductSpuDO> list = spuService.getSpuList(ids);
|
||||
return success(ProductSpuConvert.INSTANCE.convertList(list));
|
||||
// 查询商品 SKU
|
||||
List<ProductSkuDO> skus = productSkuService.getSkuListBySpuIdAndStatus(spu.getId(), null);
|
||||
// 查询商品属性
|
||||
List<ProductPropertyValueDetailRespBO> propertyValues = productPropertyValueService
|
||||
.getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
|
||||
// 拼接
|
||||
return success(ProductSpuConvert.INSTANCE.convert03(spu, skus, propertyValues));
|
||||
}
|
||||
|
||||
@GetMapping("/get-simple-list")
|
||||
@Operation(summary = "获得商品 SPU 精简列表")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:query')")
|
||||
public CommonResult<List<ProductSpuSimpleRespVO>> getSpuSimpleList() {
|
||||
List<ProductSpuDO> list = spuService.getSpuList();
|
||||
List<ProductSpuDO> list = productSpuService.getSpuList();
|
||||
return success(ProductSpuConvert.INSTANCE.convertList02(list));
|
||||
}
|
||||
|
||||
|
@ -92,7 +95,7 @@ public class ProductSpuController {
|
|||
@Operation(summary = "获得商品 SPU 分页")
|
||||
@PreAuthorize("@ss.hasPermission('product:spu:query')")
|
||||
public CommonResult<PageResult<ProductSpuRespVO>> getSpuPage(@Valid ProductSpuPageReqVO pageVO) {
|
||||
return success(spuService.getSpuPage(pageVO));
|
||||
return success(ProductSpuConvert.INSTANCE.convertPage(productSpuService.getSpuPage(pageVO)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,81 +11,66 @@ import javax.validation.constraints.NotNull;
|
|||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品 SPU Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
* 商品 SPU Base VO,提供给添加、修改、详细的子 VO 使用
|
||||
* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
|
||||
*/
|
||||
@Data
|
||||
public class ProductSpuBaseVO {
|
||||
|
||||
@Schema(description = "商品名称", required = true, example = "芋道")
|
||||
@Schema(description = = "商品名称", required = true, example = "芋道")
|
||||
@NotEmpty(message = "商品名称不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "商品编码", example = "yudaoyuanma")
|
||||
@Schema(description = = "商品编码", example = "yudaoyuanma")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "促销语", example = "好吃!")
|
||||
@Schema(description = = "促销语", example = "好吃!")
|
||||
private String sellPoint;
|
||||
|
||||
@Schema(description = "商品详情", required = true, example = "我是商品描述")
|
||||
@Schema(description = = "商品详情", required = true, example = "我是商品描述")
|
||||
@NotNull(message = "商品详情不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "商品分类编号", required = true, example = "1")
|
||||
@Schema(description = = "商品分类编号", required = true, example = "1")
|
||||
@NotNull(message = "商品分类编号不能为空")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "商品品牌编号", example = "1")
|
||||
@Schema(description = = "商品品牌编号", example = "1")
|
||||
private Long brandId;
|
||||
|
||||
@Schema(description = "商品图片的数组", required = true)
|
||||
@Schema(description = = "商品图片的数组", required = true)
|
||||
@NotNull(message = "商品图片的数组不能为空")
|
||||
private List<String> picUrls;
|
||||
|
||||
@Schema(description = "商品视频", required = true)
|
||||
@Schema(description = = "商品视频", required = true)
|
||||
private String videoUrl;
|
||||
|
||||
@Schema(description = "排序字段", required = true, example = "1")
|
||||
@Schema(description = = "排序字段", required = true, example = "1")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "商品状态,参见 ProductSpuStatusEnum 枚举类", required = true, example = "1")
|
||||
@Schema(description = = "商品状态 参见 ProductSpuStatusEnum 枚举类", required = true, example = "1")
|
||||
@NotNull(message = "商品状态不能为空")
|
||||
@InEnum(ProductSpuStatusEnum.class)
|
||||
private Integer status;
|
||||
|
||||
// ========== SKU 相关字段 =========
|
||||
|
||||
@Schema(description = "规格类型,参见 ProductSpuSpecTypeEnum 枚举类", required = true, example = "1")
|
||||
@Schema(description = = "规格类型 参见 ProductSpuSpecTypeEnum 枚举类", required = true, example = "1")
|
||||
@NotNull(message = "规格类型不能为空")
|
||||
@InEnum(ProductSpuSpecTypeEnum.class)
|
||||
private Integer specType;
|
||||
|
||||
@Schema(description = "是否展示库存", required = true, example = "true")
|
||||
@Schema(description = = "是否展示库存", required = true, example = "true")
|
||||
@NotNull(message = "是否展示库存不能为空")
|
||||
private Boolean showStock;
|
||||
|
||||
@Schema(description = "库存", required = true, example = "true")
|
||||
private Integer totalStock;
|
||||
|
||||
@Schema(description = "市场价", example = "1024")
|
||||
@Schema(description = = "市场价", example = "1024")
|
||||
private Integer marketPrice;
|
||||
|
||||
@Schema(description = " 最小价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer minPrice;
|
||||
|
||||
@Schema(description = "最大价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer maxPrice;
|
||||
|
||||
// ========== 统计相关字段 =========
|
||||
|
||||
@Schema(description = "商品销量", example = "1024")
|
||||
private Integer salesCount;
|
||||
|
||||
@Schema(description = "虚拟销量", required = true, example = "1024")
|
||||
@Schema(description = = "虚拟销量", required = true, example = "1024")
|
||||
@NotNull(message = "虚拟销量不能为空")
|
||||
private Integer virtualSalesCount;
|
||||
|
||||
@Schema(description = "点击量", example = "1024")
|
||||
private Integer clickCount;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
package cn.iocoder.yudao.module.product.controller.admin.spu.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.ProductPropertyViewRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuBaseVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 商品 SPU 详细 Response VO,包括关联的 SKU 等信息")
|
||||
@Schema(description = "管理后台 - 商品 SPU 详细 Response VO 包括关联的 SKU 等信息")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class ProductSpuDetailRespVO extends ProductSpuBaseVO {
|
||||
public class ProductSpuDetailRespVO extends ProductSpuRespVO {
|
||||
|
||||
@Schema(description = "主键", required = true, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
// ========== SKU 相关字段 =========
|
||||
|
||||
/**
|
||||
* SKU 数组
|
||||
|
@ -34,31 +29,10 @@ public class ProductSpuDetailRespVO extends ProductSpuBaseVO {
|
|||
public static class Sku extends ProductSkuBaseVO {
|
||||
|
||||
/**
|
||||
* 规格的数组
|
||||
* 属性数组
|
||||
*/
|
||||
private List<ProductSpuDetailRespVO.Property> properties;
|
||||
private List<ProductPropertyValueDetailRespVO> properties;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "管理后台 - 商品规格的详细 Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public static class Property extends ProductSkuBaseVO.Property {
|
||||
|
||||
@Schema(description = "规格的名字", required = true, example = "颜色")
|
||||
private String propertyName;
|
||||
|
||||
@Schema(description = "规格值的名字", required = true, example = "蓝色")
|
||||
private String valueName;
|
||||
|
||||
}
|
||||
|
||||
@Schema(description = "分类 id 数组,一直递归到一级父节点", example = "4")
|
||||
private Long categoryId;
|
||||
|
||||
// TODO @芋艿:在瞅瞅~
|
||||
@Schema(description = "规格属性修改和详情展示组合", example = "[{\"propertyId\":2,\"name\":\"内存\",\"propertyValues\":[{\"v1\":11,\"v2\":\"64G\"},{\"v1\":10,\"v2\":\"32G\"}]},{\"propertyId\":3,\"name\":\"尺寸\",\"propertyValues\":[{\"v1\":16,\"v2\":\"6.1\"},{\"v1\":15,\"v2\":\"5.7\"}]}]")
|
||||
private List<ProductPropertyViewRespVO> productPropertyViews;
|
||||
|
||||
}
|
||||
|
|
|
@ -18,13 +18,13 @@ public class ProductSpuPageReqVO extends PageParam {
|
|||
@Schema(description = "商品编码", example = "yudaoyuanma")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "分类id", example = "1")
|
||||
@Schema(description = "分类编号", example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "商品品牌编号", example = "1")
|
||||
private Long brandId;
|
||||
|
||||
@Schema(description = "上下架状态,参见 ProductSpuStatusEnum 枚举值", example = "1")
|
||||
@Schema(description = "上下架状态 参见 ProductSpuStatusEnum 枚举值", example = "1")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "销量最小值", example = "1")
|
||||
|
|
|
@ -6,7 +6,6 @@ import lombok.EqualsAndHashCode;
|
|||
import lombok.ToString;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 商品 SPU Response VO")
|
||||
@Data
|
||||
|
@ -20,4 +19,22 @@ public class ProductSpuRespVO extends ProductSpuBaseVO {
|
|||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// ========== SKU 相关字段 =========
|
||||
|
||||
@ApiModelProperty(value = "库存", required = true, example = "true")
|
||||
private Integer totalStock;
|
||||
|
||||
@ApiModelProperty(value = " 最小价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer minPrice;
|
||||
|
||||
@ApiModelProperty(value = "最大价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer maxPrice;
|
||||
|
||||
@ApiModelProperty(value = "商品销量", example = "1024")
|
||||
private Integer salesCount;
|
||||
|
||||
// ========== 统计相关字段 =========
|
||||
|
||||
@ApiModelProperty(value = "点击量", example = "1024")
|
||||
private Integer clickCount;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 占位符,无时间作用,避免 package 缩进
|
||||
*/
|
||||
package cn.iocoder.yudao.module.product.controller.app.property;
|
|
@ -0,0 +1,4 @@
|
|||
/**
|
||||
* 占位符,无时间作用,避免 package 缩进
|
||||
*/
|
||||
package cn.iocoder.yudao.module.product.controller.app.property.vo.property;
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.property.vo.value;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
@ApiModel("用户 App - 商品属性值的明细 Response VO")
|
||||
@Data
|
||||
public class AppProductPropertyValueDetailRespVO {
|
||||
|
||||
@ApiModelProperty(value = "属性的编号", required = true, example = "1")
|
||||
private Long propertyId;
|
||||
|
||||
@ApiModelProperty(value = "属性的名称", required = true, example = "颜色")
|
||||
private String propertyName;
|
||||
|
||||
@ApiModelProperty(value = "属性值的编号", required = true, example = "1024")
|
||||
private Long valueId;
|
||||
|
||||
@ApiModelProperty(value = "属性值的名称", required = true, example = "红色")
|
||||
private String valueName;
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
### 获得订单交易的分页 TODO
|
||||
GET {{appApi}}/product/spu/page?pageNo=1&pageSize=10
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### 获得商品 SPU 明细
|
||||
GET {{appApi}}/product/spu/get-detail?id=4
|
||||
tenant-id: {{appTenentId}}
|
|
@ -1,11 +1,19 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
|
||||
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
|
||||
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
|
||||
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
|
||||
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
|
||||
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
|
||||
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
@ -17,8 +25,12 @@ import org.springframework.web.bind.annotation.RestController;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_ENABLE;
|
||||
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
|
||||
|
||||
@Tag(name = "用户 APP - 商品spu")
|
||||
@RestController
|
||||
|
@ -27,18 +39,40 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|||
public class AppProductSpuController {
|
||||
|
||||
@Resource
|
||||
private ProductSpuService spuService;
|
||||
private ProductSpuService productSpuService;
|
||||
@Resource
|
||||
private ProductSkuService productSkuService;
|
||||
@Resource
|
||||
private ProductPropertyValueService productPropertyValueService;
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得商品spu分页")
|
||||
public CommonResult<PageResult<AppSpuPageRespVO>> getSpuPage(@Valid AppSpuPageReqVO pageVO) {
|
||||
return success(spuService.getSpuPage(pageVO));
|
||||
@Operation(summary = "获得商品 SPU 分页")
|
||||
public CommonResult<PageResult<AppProductSpuPageItemRespVO>> getSpuPage(@Valid AppProductSpuPageReqVO pageVO) {
|
||||
PageResult<ProductSpuDO> pageResult = productSpuService.getSpuPage(pageVO, ProductSpuStatusEnum.ENABLE.getStatus());
|
||||
return success(ProductSpuConvert.INSTANCE.convertPage02(pageResult));
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
@Operation(summary = "获取商品 - 通过商品id")
|
||||
public CommonResult<AppSpuRespVO> getSpu(@RequestParam("spuId") Long spuId) {
|
||||
AppSpuRespVO appSpuRespVO = BeanUtil.toBean(spuService.getSpu(spuId), AppSpuRespVO.class);
|
||||
return success(appSpuRespVO);
|
||||
@GetMapping("/get-detail")
|
||||
@Operation(summary = "获得商品 SPU 明细")
|
||||
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
|
||||
public CommonResult<AppProductSpuDetailRespVO> getSpuDetail(@RequestParam("id") Long id) {
|
||||
// 获得商品 SPU
|
||||
ProductSpuDO spu = productSpuService.getSpu(id);
|
||||
if (spu == null) {
|
||||
throw exception(SPU_NOT_EXISTS);
|
||||
}
|
||||
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
|
||||
throw exception(SPU_NOT_ENABLE);
|
||||
}
|
||||
|
||||
// 查询商品 SKU
|
||||
List<ProductSkuDO> skus = productSkuService.getSkuListBySpuIdAndStatus(spu.getId(),
|
||||
CommonStatusEnum.ENABLE.getStatus());
|
||||
// 查询商品属性
|
||||
List<ProductPropertyValueDetailRespBO> propertyValues = productPropertyValueService
|
||||
.getPropertyValueDetailList(ProductSkuConvert.INSTANCE.convertPropertyValueIds(skus));
|
||||
// 拼接
|
||||
return success(ProductSpuConvert.INSTANCE.convert(spu, skus, propertyValues));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel("用户 App - 商品 SPU 明细 Response VO")
|
||||
@Data
|
||||
public class AppProductSpuDetailRespVO {
|
||||
|
||||
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
|
||||
private Long id;
|
||||
|
||||
// ========== 基本信息 =========
|
||||
|
||||
@ApiModelProperty(value = "商品名称", required = true, example = "芋道")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "促销语", example = "好吃!")
|
||||
private String sellPoint;
|
||||
|
||||
@ApiModelProperty(value = "商品详情", required = true, example = "我是商品描述")
|
||||
private String description;
|
||||
|
||||
@ApiModelProperty(value = "商品分类编号", required = true, example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@ApiModelProperty(value = "商品图片的数组", required = true)
|
||||
private List<String> picUrls;
|
||||
|
||||
@ApiModelProperty(value = "商品视频", required = true)
|
||||
private String videoUrl;
|
||||
|
||||
// ========== SKU 相关字段 =========
|
||||
|
||||
@ApiModelProperty(value = "规格类型", required = true, example = "1", notes = "参见 ProductSpuSpecTypeEnum 枚举类")
|
||||
private Integer specType;
|
||||
|
||||
@ApiModelProperty(value = "是否展示库存", required = true, example = "true")
|
||||
private Boolean showStock;
|
||||
|
||||
@ApiModelProperty(value = " 最小价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer minPrice;
|
||||
|
||||
@ApiModelProperty(value = "最大价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer maxPrice;
|
||||
|
||||
/**
|
||||
* SKU 数组
|
||||
*/
|
||||
private List<Sku> skus;
|
||||
|
||||
// ========== 统计相关字段 =========
|
||||
|
||||
@ApiModelProperty(value = "商品销量", required = true, example = "1024")
|
||||
private Integer salesCount;
|
||||
|
||||
@ApiModel("用户 App - 商品 SPU 明细的 SKU 信息")
|
||||
@Data
|
||||
public static class Sku {
|
||||
|
||||
@ApiModelProperty(value = "商品 SKU 编号", example = "1")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 商品属性数组
|
||||
*/
|
||||
private List<AppProductPropertyValueDetailRespVO> properties;
|
||||
|
||||
@ApiModelProperty(value = "销售价格,单位:分", required = true, example = "1024", notes = "单位:分")
|
||||
private Integer price;
|
||||
|
||||
@ApiModelProperty(value = "市场价", example = "1024", notes = "单位:分")
|
||||
private Integer marketPrice;
|
||||
|
||||
@ApiModelProperty(value = "图片地址", required = true, example = "https://www.iocoder.cn/xx.png")
|
||||
private String picUrl;
|
||||
|
||||
@ApiModelProperty(value = "库存", required = true, example = "1")
|
||||
private Integer stock;
|
||||
|
||||
@ApiModelProperty(value = "商品重量", example = "1", notes = "单位:kg 千克")
|
||||
private Double weight;
|
||||
|
||||
@ApiModelProperty(value = "商品体积", example = "1024", notes = "单位:m^3 平米")
|
||||
private Double volume;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel("用户 App - 商品 SPU 分页项 Response VO")
|
||||
@Data
|
||||
public class AppProductSpuPageItemRespVO {
|
||||
|
||||
@ApiModelProperty(value = "商品 SPU 编号", required = true, example = "1")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty(value = "商品名称", required = true, example = "芋道")
|
||||
@NotEmpty(message = "商品名称不能为空")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty(value = "分类编号", required = true)
|
||||
@NotNull(message = "分类编号不能为空")
|
||||
private Long categoryId;
|
||||
|
||||
@ApiModelProperty(value = "商品图片的数组", required = true)
|
||||
private List<String> picUrls;
|
||||
|
||||
@ApiModelProperty(value = " 最小价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer minPrice;
|
||||
|
||||
@ApiModelProperty(value = "最大价格,单位使用:分", required = true, example = "1024")
|
||||
private Integer maxPrice;
|
||||
|
||||
// ========== 统计相关字段 =========
|
||||
|
||||
@ApiModelProperty(value = "商品销量", example = "1024")
|
||||
private Integer salesCount;
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.AssertTrue;
|
||||
|
||||
@ApiModel("用户 App - 商品 SPU 分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class AppProductSpuPageReqVO extends PageParam {
|
||||
|
||||
public static final String SORT_FIELD_PRICE = "price";
|
||||
public static final String SORT_FIELD_SALES_COUNT = "salesCount";
|
||||
|
||||
@ApiModelProperty(value = "分类编号", example = "1")
|
||||
private Long categoryId;
|
||||
|
||||
@ApiModelProperty(value = "关键字", example = "好看")
|
||||
private String keyword;
|
||||
|
||||
@ApiModelProperty(value = "排序字段", example = "price", notes = "参见 AppSpuPageReqVO.SORT_FIELD_XXX 常量")
|
||||
private String sortField;
|
||||
|
||||
@ApiModelProperty(value = "排序方式", example = "true", notes = "true - 升序;false - 降序")
|
||||
private Boolean sortAsc;
|
||||
|
||||
@AssertTrue(message = "排序字段不合法")
|
||||
@JsonIgnore
|
||||
public boolean isSortFieldValid() {
|
||||
if (StrUtil.isEmpty(sortField)) {
|
||||
return true;
|
||||
}
|
||||
return StrUtil.equalsAny(sortField, SORT_FIELD_PRICE, SORT_FIELD_SALES_COUNT);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
@Schema(description = "App - 商品spu分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class AppSpuPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "分类id")
|
||||
private Long categoryId;
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "App - 商品spu分页 Request VO")
|
||||
@Data
|
||||
public class AppSpuPageRespVO {
|
||||
|
||||
@Schema(description = "主键", required = true, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "商品名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "卖点", required = true)
|
||||
@NotNull(message = "卖点不能为空")
|
||||
private String sellPoint;
|
||||
|
||||
@Schema(description = "描述", required = true)
|
||||
@NotNull(message = "描述不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "分类id", required = true)
|
||||
@NotNull(message = "分类id不能为空")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "商品主图地址,* 数组,以逗号分隔,最多上传15张", required = true)
|
||||
@NotNull(message = "商品主图地址,* 数组,以逗号分隔,最多上传15张不能为空")
|
||||
private List<String> picUrls;
|
||||
|
||||
@Schema(description = "排序字段", required = true)
|
||||
@NotNull(message = "排序字段不能为空")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "点赞初始人数")
|
||||
private Integer likeCount;
|
||||
|
||||
@Schema(description = "价格 单位使用:分")
|
||||
private Integer price;
|
||||
|
||||
@Schema(description = "库存数量")
|
||||
private Integer quantity;
|
||||
|
||||
@Schema(description = "上下架状态: 0 上架(开启) 1 下架(禁用)")
|
||||
private Integer status;
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.product.controller.app.spu.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuRespVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* @author LuoWenFeng
|
||||
*/
|
||||
@Schema(description = "App - 商品spu Response VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class AppSpuRespVO extends ProductSpuRespVO {
|
||||
|
||||
|
||||
}
|
|
@ -1,20 +1,21 @@
|
|||
package cn.iocoder.yudao.module.product.convert.property;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyAndValueRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyCreateReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 规格名称 Convert
|
||||
* 属性项 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -27,12 +28,21 @@ public interface ProductPropertyConvert {
|
|||
|
||||
ProductPropertyDO convert(ProductPropertyUpdateReqVO bean);
|
||||
|
||||
ProductPropertyAndValueRespVO convert(ProductPropertyRespVO bean);
|
||||
|
||||
ProductPropertyRespVO convert(ProductPropertyDO bean);
|
||||
|
||||
List<ProductPropertyRespVO> convertList(List<ProductPropertyDO> list);
|
||||
|
||||
PageResult<ProductPropertyRespVO> convertPage(PageResult<ProductPropertyDO> page);
|
||||
|
||||
default List<ProductPropertyAndValueRespVO> convertList(List<ProductPropertyDO> keys, List<ProductPropertyValueDO> values) {
|
||||
Map<Long, List<ProductPropertyValueDO>> valueMap = CollectionUtils.convertMultiMap(values, ProductPropertyValueDO::getPropertyId);
|
||||
return CollectionUtils.convertList(keys, key -> {
|
||||
ProductPropertyAndValueRespVO respVO = convert02(key);
|
||||
respVO.setValues(convertList02(valueMap.get(key.getId())));
|
||||
return respVO;
|
||||
});
|
||||
}
|
||||
ProductPropertyAndValueRespVO convert02(ProductPropertyDO bean);
|
||||
List<ProductPropertyAndValueRespVO.Value> convertList02(List<ProductPropertyValueDO> list);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
package cn.iocoder.yudao.module.product.convert.propertyvalue;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
|
||||
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
/**
|
||||
* 规格值 Convert
|
||||
* 属性值 Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -31,6 +38,18 @@ public interface ProductPropertyValueConvert {
|
|||
|
||||
PageResult<ProductPropertyValueRespVO> convertPage(PageResult<ProductPropertyValueDO> page);
|
||||
|
||||
List<ProductPropertyValueDO> convertList03(List<ProductPropertyValueCreateReqVO> list);
|
||||
default List<ProductPropertyValueDetailRespBO> convertList(List<ProductPropertyValueDO> values, List<ProductPropertyDO> keys) {
|
||||
Map<Long, ProductPropertyDO> keyMap = convertMap(keys, ProductPropertyDO::getId);
|
||||
return CollectionUtils.convertList(values, value -> {
|
||||
ProductPropertyValueDetailRespBO valueDetail = new ProductPropertyValueDetailRespBO()
|
||||
.setValueId(value.getId()).setValueName(value.getName());
|
||||
// 设置属性项
|
||||
MapUtils.findAndThen(keyMap, value.getPropertyId(),
|
||||
key -> valueDetail.setPropertyId(key.getId()).setPropertyName(key.getName()));
|
||||
return valueDetail;
|
||||
});
|
||||
}
|
||||
|
||||
List<ProductPropertyValueDetailRespDTO> convertList02(List<ProductPropertyValueDetailRespBO> list);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.module.product.convert.sku;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.sku.vo.ProductSkuCreateOrUpdateReqVO;
|
||||
|
@ -10,9 +12,8 @@ import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
|
|||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
|
@ -32,12 +33,16 @@ public interface ProductSkuConvert {
|
|||
|
||||
List<ProductSkuRespVO> convertList(List<ProductSkuDO> list);
|
||||
|
||||
List<ProductSkuDO> convertSkuDOList(List<ProductSkuCreateOrUpdateReqVO> list);
|
||||
List<ProductSkuDO> convertList06(List<ProductSkuCreateOrUpdateReqVO> list);
|
||||
|
||||
default List<ProductSkuDO> convertList06(List<ProductSkuCreateOrUpdateReqVO> list, Long spuId, String spuName) {
|
||||
List<ProductSkuDO> result = convertList06(list);
|
||||
result.forEach(item -> item.setSpuId(spuId).setSpuName(spuName));
|
||||
return result;
|
||||
}
|
||||
|
||||
ProductSkuRespDTO convert02(ProductSkuDO bean);
|
||||
|
||||
List<ProductSkuRespDTO> convertList02(List<ProductSkuDO> list);
|
||||
|
||||
List<ProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> list);
|
||||
|
||||
List<ProductSkuRespDTO> convertList04(List<ProductSkuDO> list);
|
||||
|
@ -66,4 +71,23 @@ public interface ProductSkuConvert {
|
|||
return spuIdAndStockMap;
|
||||
}
|
||||
|
||||
default Collection<Long> convertPropertyValueIds(List<ProductSkuDO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return list.stream().filter(item -> item.getProperties() != null)
|
||||
.flatMap(p -> p.getProperties().stream()) // 遍历多个 Property 属性
|
||||
.map(ProductSkuDO.Property::getValueId) // 将每个 Property 转换成对应的 propertyId,最后形成集合
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
default String buildPropertyKey(ProductSkuDO bean) {
|
||||
if (CollUtil.isEmpty(bean.getProperties())) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
List<ProductSkuDO.Property> properties = new ArrayList<>(bean.getProperties());
|
||||
properties.sort(Comparator.comparing(ProductSkuDO.Property::getValueId));
|
||||
return properties.stream().map(m -> String.valueOf(m.getValueId())).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
package cn.iocoder.yudao.module.product.convert.spu;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueDetailRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.*;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppSpuPageRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuDetailRespVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.app.spu.vo.AppProductSpuPageItemRespVO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
|
||||
import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.hutool.core.util.ObjectUtil.defaultIfNull;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
/**
|
||||
* 商品spu Convert
|
||||
* 商品 SPU Convert
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -25,18 +36,73 @@ public interface ProductSpuConvert {
|
|||
|
||||
ProductSpuDO convert(ProductSpuUpdateReqVO bean);
|
||||
|
||||
ProductSpuRespVO convert(ProductSpuDO bean);
|
||||
|
||||
List<ProductSpuRespVO> convertList(List<ProductSpuDO> list);
|
||||
List<ProductSpuDO> convertList(List<ProductSpuDO> list);
|
||||
|
||||
PageResult<ProductSpuRespVO> convertPage(PageResult<ProductSpuDO> page);
|
||||
|
||||
ProductSpuPageReqVO convert(AppSpuPageReqVO bean);
|
||||
|
||||
AppSpuPageRespVO convertAppResp(ProductSpuDO list);
|
||||
ProductSpuPageReqVO convert(AppProductSpuPageReqVO bean);
|
||||
|
||||
List<ProductSpuRespDTO> convertList2(List<ProductSpuDO> list);
|
||||
|
||||
List<ProductSpuSimpleRespVO> convertList02(List<ProductSpuDO> list);
|
||||
|
||||
default AppProductSpuDetailRespVO convert(ProductSpuDO spu, List<ProductSkuDO> skus,
|
||||
List<ProductPropertyValueDetailRespBO> propertyValues) {
|
||||
AppProductSpuDetailRespVO spuVO = convert02(spu)
|
||||
.setSalesCount(spu.getSalesCount() + defaultIfNull(spu.getVirtualSalesCount(), 0));
|
||||
spuVO.setSkus(convertList03(skus));
|
||||
// 处理商品属性
|
||||
Map<Long, ProductPropertyValueDetailRespBO> propertyValueMap = convertMap(propertyValues, ProductPropertyValueDetailRespBO::getValueId);
|
||||
for (int i = 0; i < skus.size(); i++) {
|
||||
List<ProductSkuDO.Property> properties = skus.get(i).getProperties();
|
||||
if (CollUtil.isEmpty(properties)) {
|
||||
continue;
|
||||
}
|
||||
AppProductSpuDetailRespVO.Sku sku = spuVO.getSkus().get(i);
|
||||
sku.setProperties(new ArrayList<>(properties.size()));
|
||||
// 遍历每个 properties,设置到 AppSpuDetailRespVO.Sku 中
|
||||
properties.forEach(property -> {
|
||||
ProductPropertyValueDetailRespBO propertyValue = propertyValueMap.get(property.getValueId());
|
||||
if (propertyValue == null) {
|
||||
return;
|
||||
}
|
||||
sku.getProperties().add(convert03(propertyValue));
|
||||
});
|
||||
}
|
||||
return spuVO;
|
||||
}
|
||||
AppProductSpuDetailRespVO convert02(ProductSpuDO spu);
|
||||
List<AppProductSpuDetailRespVO.Sku> convertList03(List<ProductSkuDO> skus);
|
||||
AppProductPropertyValueDetailRespVO convert03(ProductPropertyValueDetailRespBO propertyValue);
|
||||
|
||||
PageResult<AppProductSpuPageItemRespVO> convertPage02(PageResult<ProductSpuDO> page);
|
||||
|
||||
default ProductSpuDetailRespVO convert03(ProductSpuDO spu, List<ProductSkuDO> skus,
|
||||
List<ProductPropertyValueDetailRespBO> propertyValues) {
|
||||
ProductSpuDetailRespVO spuVO = convert03(spu);
|
||||
spuVO.setSkus(convertList04(skus));
|
||||
// 处理商品属性
|
||||
Map<Long, ProductPropertyValueDetailRespBO> propertyValueMap = convertMap(propertyValues, ProductPropertyValueDetailRespBO::getValueId);
|
||||
for (int i = 0; i < skus.size(); i++) {
|
||||
List<ProductSkuDO.Property> properties = skus.get(i).getProperties();
|
||||
if (CollUtil.isEmpty(properties)) {
|
||||
continue;
|
||||
}
|
||||
ProductSpuDetailRespVO.Sku sku = spuVO.getSkus().get(i);
|
||||
sku.setProperties(new ArrayList<>(properties.size()));
|
||||
// 遍历每个 properties,设置到 AppSpuDetailRespVO.Sku 中
|
||||
properties.forEach(property -> {
|
||||
ProductPropertyValueDetailRespBO propertyValue = propertyValueMap.get(property.getValueId());
|
||||
if (propertyValue == null) {
|
||||
return;
|
||||
}
|
||||
sku.getProperties().add(convert04(propertyValue));
|
||||
});
|
||||
}
|
||||
return spuVO;
|
||||
}
|
||||
ProductSpuDetailRespVO convert03(ProductSpuDO spu);
|
||||
List<ProductSpuDetailRespVO.Sku> convertList04(List<ProductSkuDO> skus);
|
||||
ProductPropertyValueDetailRespVO convert04(ProductPropertyValueDetailRespBO propertyValue);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.module.product.dal.dataobject.property;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
|
@ -8,7 +7,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
|||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 规格名称 DO
|
||||
* 商品属性项 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -28,20 +27,12 @@ public class ProductPropertyDO extends BaseDO {
|
|||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 规格名称
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
// TODO 芋艿:rule;规格属性 (发布商品时,和 SKU 关联);规格参数(搜索商品时,与 Category 关联搜索)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.module.product.dal.dataobject.property;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
|
@ -9,7 +8,7 @@ import lombok.*;
|
|||
|
||||
|
||||
/**
|
||||
* 规格值 DO
|
||||
* 商品属性值 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
|
@ -29,21 +28,15 @@ public class ProductPropertyValueDO extends BaseDO {
|
|||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 规格键编号
|
||||
* 属性项的编号
|
||||
*
|
||||
* 关联 {@link ProductPropertyDO#getId()}
|
||||
*/
|
||||
private Long propertyId;
|
||||
/**
|
||||
* 规格值名字
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 备注
|
||||
*
|
||||
|
|
|
@ -35,10 +35,6 @@ public class ProductSkuDO extends BaseDO {
|
|||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 商品 SKU 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* SPU 编号
|
||||
* <p>
|
||||
|
@ -46,7 +42,13 @@ public class ProductSkuDO extends BaseDO {
|
|||
*/
|
||||
private Long spuId;
|
||||
/**
|
||||
* 规格值数组,JSON 格式
|
||||
* SPU 名字
|
||||
*
|
||||
* 冗余 {@link ProductSkuDO#getSpuName()}
|
||||
*/
|
||||
private String spuName;
|
||||
/**
|
||||
* 属性数组,JSON 格式
|
||||
*/
|
||||
@TableField(typeHandler = PropertyTypeHandler.class)
|
||||
private List<Property> properties;
|
||||
|
|
|
@ -3,31 +3,30 @@ package cn.iocoder.yudao.module.product.dal.mysql.property;
|
|||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyListReqVO;
|
||||
import cn.iocoder.yudao.module.product.controller.admin.property.vo.property.ProductPropertyPageReqVO;
|
||||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 规格名称 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProductPropertyMapper extends BaseMapperX<ProductPropertyDO> {
|
||||
|
||||
default PageResult<ProductPropertyDO> selectPage(ProductPropertyPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ProductPropertyDO>()
|
||||
.likeIfPresent(ProductPropertyDO::getName, reqVO.getName())
|
||||
.eqIfPresent(ProductPropertyDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(ProductPropertyDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(ProductPropertyDO::getId));
|
||||
}
|
||||
|
||||
default ProductPropertyDO selectByName(String name) {
|
||||
return selectOne(new LambdaQueryWrapperX<ProductPropertyDO>()
|
||||
.eqIfPresent(ProductPropertyDO::getName, name));
|
||||
return selectOne(ProductPropertyDO::getName, name);
|
||||
}
|
||||
|
||||
default List<ProductPropertyDO> selectList(ProductPropertyListReqVO listReqVO) {
|
||||
return selectList(new LambdaQueryWrapperX<ProductPropertyDO>()
|
||||
.eqIfPresent(ProductPropertyDO::getName, listReqVO.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,17 +7,13 @@ import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.Produc
|
|||
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 规格值 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface ProductPropertyValueMapper extends BaseMapperX<ProductPropertyValueDO> {
|
||||
|
||||
default List<ProductPropertyValueDO> selectListByPropertyId(List<Long> propertyIds) {
|
||||
default List<ProductPropertyValueDO> selectListByPropertyId(Collection<Long> propertyIds) {
|
||||
return selectList(new LambdaQueryWrapperX<ProductPropertyValueDO>()
|
||||
.inIfPresent(ProductPropertyValueDO::getPropertyId, propertyIds));
|
||||
}
|
||||
|
@ -28,17 +24,20 @@ public interface ProductPropertyValueMapper extends BaseMapperX<ProductPropertyV
|
|||
.eq(ProductPropertyValueDO::getName, name));
|
||||
}
|
||||
|
||||
default void deletePropertyValueByPropertyId(Long propertyId) {
|
||||
delete(new LambdaQueryWrapperX<ProductPropertyValueDO>().eq(ProductPropertyValueDO::getPropertyId, propertyId)
|
||||
.eq(ProductPropertyValueDO::getDeleted, false));
|
||||
default void deleteByPropertyId(Long propertyId) {
|
||||
delete(new LambdaQueryWrapperX<ProductPropertyValueDO>()
|
||||
.eq(ProductPropertyValueDO::getPropertyId, propertyId));
|
||||
}
|
||||
|
||||
default PageResult<ProductPropertyValueDO> selectPage(ProductPropertyValuePageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<ProductPropertyValueDO>()
|
||||
.eqIfPresent(ProductPropertyValueDO::getPropertyId, reqVO.getPropertyId())
|
||||
.likeIfPresent(ProductPropertyValueDO::getName, reqVO.getName())
|
||||
.eqIfPresent(ProductPropertyValueDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(ProductPropertyValueDO::getId));
|
||||
}
|
||||
|
||||
default Integer selectCountByPropertyId(Long propertyId) {
|
||||
return selectCount(ProductPropertyValueDO::getPropertyId, propertyId).intValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue