Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-redis

 Conflicts:
	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/auth/AuthConvert.java
	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
This commit is contained in:
YunaiV 2023-07-28 22:31:18 +08:00
commit 95edd1d451
243 changed files with 3417 additions and 1230 deletions

View File

@ -1,23 +1,24 @@
# Docker Build & Up
目标: 快速部署体验系统,帮助了解系统之间的依赖关系。
依赖docker compose v2删除`name: yudao-system`,降低`version`版本为`3.3`以下,支持`docker-compose`。
## 功能文件列表
```text
.
├── Docker-HOWTO.md
├── docker-compose.yml
├── docker.env
├── Docker-HOWTO.md
├── docker-compose.yml
├── docker.env <-- 提供docker-compose环境变量配置
├── yudao-server
│ ├── Dockerfile
│ └── nginx.conf
│ └── Dockerfile
└── yudao-ui-admin
├── .dockerignore
└── Dockerfile
├── Dockerfile
└── nginx.conf <-- 提供基础配置gzip压缩api转发
```
## Maven build (Optional)
## 构建 jar 包
```shell
# 创建maven缓存volume
@ -30,29 +31,19 @@ docker run -it --rm --name yudao-maven \
maven mvn clean install package '-Dmaven.test.skip=true'
```
## Docker Compose Build
```shell
docker compose --env-file docker.env build
```
## Docker Compose Up
## 构建启动服务
```shell
docker compose --env-file docker.env up -d
```
第一次执行由于数据库未初始化因此yudao-server容器会运行失败。执行如下命令初始化数据库
首次运行会自动构建容器。可以通过`docker compose build [service]`来手动构建所有或某个docker镜像
```shell
docker compose exec -T mysql \
sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --default-character-set=utf8mb4 ruoyi-vue-pro' \
< ./sql/mysql/ruoyi-vue-pro.sql
```
`--env-file docker.env`为可选参数,只是展示了通过`.env`文件配置容器启动的环境变量,`docker-compose.yml`本身已经提供足够的默认参数来正常运行系统。
## Server:Port
## 服务器的宿主机端口映射
- admin: http://localhost:8080
- API: http://localhost:48080
- mysql: root/123456, port: 3308
- admin ui: http://localhost:8080
- api server: http://localhost:48080
- mysql: root/123456, port: 3306
- redis: port: 6379

View File

@ -102,7 +102,7 @@
系统内置多种多种业务功能,可以用于快速你的业务系统:
![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png)
![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png?imageView2/2/format/webp)
* 系统功能
* 基础设施

View File

@ -1,4 +1,4 @@
version: "3.8"
version: "3.4"
name: yudao-system
@ -9,25 +9,22 @@ services:
restart: unless-stopped
tty: true
ports:
- 13306:3306
- "3306:3306"
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE:-ruoyi-vue-pro}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}
volumes:
- mysql:/var/lib/mysql/
networks:
- yudao-network
- ./sql/mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro
redis:
container_name: yudao-redis
image: redis:6-alpine
restart: unless-stopped
ports:
- 16379:6379
- "6379:6379"
volumes:
- redis:/data
networks:
- yudao-network
server:
container_name: yudao-server
@ -36,7 +33,7 @@ services:
image: yudao-server
restart: unless-stopped
ports:
- 48080:48080
- "48080:48080"
environment:
# https://github.com/polovyivan/docker-pass-configs-to-container
SPRING_PROFILES_ACTIVE: local
@ -54,8 +51,6 @@ services:
--spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root}
--spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456}
--spring.redis.host=${REDIS_HOST:-yudao-redis}
networks:
- yudao-network
depends_on:
- mysql
- redis
@ -78,16 +73,10 @@ services:
image: yudao-admin
restart: unless-stopped
ports:
- 8080:80
networks:
- yudao-network
- "8080:80"
depends_on:
- server
networks:
yudao-network:
driver: bridge
volumes:
mysql:
driver: local

View File

@ -30,7 +30,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.7.3-snapshot</revision>
<revision>1.8.0-snapshot</revision>
<!-- Maven 相关 -->
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>

View File

@ -14,7 +14,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.7.3-snapshot</revision>
<revision>1.8.0-snapshot</revision>
<!-- 统一依赖管理 -->
<spring.boot.version>2.7.13</spring.boot.version>
<!-- Web 相关 -->
@ -71,7 +71,7 @@
<justauth.version>1.0.1</justauth.version>
<jimureport.version>1.5.8</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version>
<wx-java-mp.version>4.5.0</wx-java-mp.version>
<weixin-java.version>4.5.0</weixin-java.version>
</properties>
<dependencyManagement>
@ -216,10 +216,9 @@
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version>
</dependency>
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
<version>${mybatis-plus-join-boot-starter.version}</version>
</dependency>
@ -599,10 +598,25 @@
<version>${justauth.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>${wx-java-mp.version}</version>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<!-- 积木报表-->

View File

@ -27,8 +27,6 @@ public interface WebFilterOrderEnum {
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
int ACTIVITI_FILTER = -98; // 需要保证在 Spring Security 过滤后面
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
int DEMO_FILTER = Integer.MAX_VALUE;

View File

@ -37,15 +37,4 @@ public interface GlobalErrorCodeConstants {
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
/**
* 是否为服务端错误参考 HTTP 5XX 错误码段
*
* @param code 错误码
* @return 是否
*/
static boolean isServerErrorCode(Integer code) {
return code != null
&& code >= INTERNAL_SERVER_ERROR.getCode() && code <= INTERNAL_SERVER_ERROR.getCode() + 99;
}
}

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.common.pojo;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServerException;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.fasterxml.jackson.annotation.JsonIgnore;
@ -92,10 +91,6 @@ public class CommonResult<T> implements Serializable {
if (isSuccess()) {
return;
}
// 服务端异常
if (GlobalErrorCodeConstants.isServerErrorCode(code)) {
throw new ServerException(code, msg);
}
// 业务异常
throw new ServiceException(code, msg);
}

View File

@ -164,7 +164,7 @@ public class CollectionUtils {
return from.stream().filter(predicate).findFirst().orElse(null);
}
public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) {
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return null;
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* 时间工具类用于 {@link java.time.LocalDateTime}
@ -50,7 +51,7 @@ public class LocalDateTimeUtils {
* 判断当前时间是否在该时间范围内
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param endTime 结束时间
* @return 是否
*/
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {
@ -60,4 +61,24 @@ public class LocalDateTimeUtils {
return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
}
/**
* 检查时间重叠 不包含日期
*
* @param startTime1 需要校验的开始时间
* @param endTime1 需要校验的结束时间
* @param startTime2 校验所需的开始时间
* @param endTime2 校验所需的结束时间
* @return 是否重叠
*/
// TODO @puhui999LocalDateTimeUtil.isOverlap() 是不是可以满足呀
public static boolean checkTimeOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
// 判断时间是否重叠
// 开始时间在已配置时段的结束时间之前 结束时间在已配置时段的开始时间之后 []
return startTime1.isBefore(endTime2) && endTime1.isAfter(startTime2)
// 开始时间在已配置时段的开始时间之前 结束时间在已配置时段的开始时间之后 (] ()
|| startTime1.isBefore(startTime2) && endTime1.isAfter(startTime2)
// 开始时间在已配置时段的结束时间之前 结束时间在已配值时段的结束时间之后 [) ()
|| startTime1.isBefore(endTime2) && endTime1.isAfter(endTime2);
}
}

View File

@ -4,11 +4,11 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import org.junit.jupiter.api.BeforeEach;
@ -23,6 +23,7 @@ import static cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataP
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
@ -73,6 +74,8 @@ class DeptDataPermissionRuleTest extends BaseMockitoUnitTest {
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);
// mock 方法permissionApi 返回 null
when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(null);
// 调用
NullPointerException exception = assertThrows(NullPointerException.class,

View File

@ -63,9 +63,7 @@
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.5.0</version>
</dependency>
<!-- TODO 芋艿:清理 -->
<!-- Test 测试相关 -->
<dependency>

View File

@ -34,16 +34,12 @@
<!-- 三方云服务相关 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<!-- <artifactId>weixin-java-mp</artifactId>-->
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<!-- TODO 芋艿:清理 -->
</dependencies>
</project>

View File

@ -64,9 +64,8 @@
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
</dependency>
</dependencies>
</project>

View File

@ -6,10 +6,10 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Param;
import java.util.Collection;
@ -17,10 +17,8 @@ import java.util.List;
/**
* MyBatis Plus BaseMapper 的基础上拓展提供更多的能力
* <p>
* 为什么继承 MPJBaseMapper 接口支持 MyBatis Plus 多表 Join 的能力
*/
public interface BaseMapperX<T> extends MPJBaseMapper<T> {
public interface BaseMapperX<T> extends BaseMapper<T> {
default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
// MyBatis Plus 查询
@ -46,18 +44,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
}
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
SFunction<T, ?> field3, Object value3) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
.eq(field3, value3));
}
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
SFunction<T, ?> field3, Object value3, SFunction<T, ?> field4, Object value4) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
.eq(field3, value3).eq(field4, value4));
}
default Long selectCount() {
return selectCount(new QueryWrapper<T>());
}
@ -117,26 +103,15 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
update(update, new QueryWrapper<>());
}
/**
* 根据ID 批量更新适合大量数据更新
*
* @param entities 实体们
*/
default void updateBatch(Collection<T> entities) {
Db.updateBatchById(entities);
}
default void updateBatch(Collection<T> entities, int size) {
Db.updateBatchById(entities, size);
}
/**
* 批量修改插入, 会根据实体的主键是否为空更新还是修改默认为 1000
*
* @param entities 实体们
*/
default void saveOrUpdateBatch(Collection<T> entities){
Db.saveOrUpdateBatch(entities);
default void saveOrUpdateBatch(Collection<T> collection) {
Db.saveOrUpdateBatch(collection);
}
}

View File

@ -60,7 +60,7 @@ public class YudaoWebSecurityConfigurerAdapter {
/**
* 自定义的权限映射 Bean
*
* @see #configure(HttpSecurity)
* @see #filterChain(HttpSecurity)
*/
@Resource
private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;
@ -79,7 +79,7 @@ public class YudaoWebSecurityConfigurerAdapter {
/**
* 配置 URL 的安全配置
* <p>
*
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
@ -141,7 +141,6 @@ public class YudaoWebSecurityConfigurerAdapter {
// 添加 Token Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}

View File

@ -40,6 +40,7 @@ public class BaseDbAndRedisUnitTest {
// Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类用于启动 RedisServer
// RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类
})

View File

@ -21,6 +21,7 @@ public class YudaoJacksonAutoConfiguration {
@Bean
public BeanPostProcessor objectMapperBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof ObjectMapper)) {

View File

@ -52,7 +52,8 @@ public class YudaoSwaggerAutoConfiguration {
// 接口信息
.info(buildInfo(properties))
// 接口安全配置
.components(new Components().securitySchemes(securitySchemas));
.components(new Components().securitySchemes(securitySchemas))
.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));
securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
return openAPI;
}

View File

@ -47,7 +47,7 @@ public class BpmTaskController {
@Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
@RequestParam("processInstanceId") String processInstanceId) {
@RequestParam("processInstanceId") String processInstanceId) {
return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
}

View File

@ -17,12 +17,9 @@ import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import org.springframework.beans.BeanUtils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Bpm 任务 Convert
@ -34,37 +31,9 @@ public interface BpmTaskConvert {
BpmTaskConvert INSTANCE = Mappers.getMapper(BpmTaskConvert.class);
/**
* 复制对象
*
* @param source 要复制的对象
* @param target 目标 复制到此对象
* @param <T>
*
* @return
*/
public static <T> T copy(Object source, Class<T> target) {
if (source == null || target == null) {
return null;
}
try {
T newInstance = target.getDeclaredConstructor().newInstance();
BeanUtils.copyProperties(source, newInstance);
return newInstance;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
default <T, K> List<K> copyList(List<T> source, Class<K> target) {
if (null == source || source.isEmpty()) {
return Collections.emptyList();
}
return source.stream().map(e -> copy(e, target)).collect(Collectors.toList());
}
default List<BpmTaskTodoPageItemRespVO> convertList1(List<Task> tasks,
Map<String, ProcessInstance> processInstanceMap, Map<Long, AdminUserRespDTO> userMap) {
Map<String, ProcessInstance> processInstanceMap,
Map<Long, AdminUserRespDTO> userMap) {
return CollectionUtils.convertList(tasks, task -> {
BpmTaskTodoPageItemRespVO respVO = convert1(task);
ProcessInstance processInstance = processInstanceMap.get(task.getProcessInstanceId());
@ -104,11 +73,13 @@ public interface BpmTaskConvert {
BpmTaskDonePageItemRespVO convert2(HistoricTaskInstance bean);
@Mappings({@Mapping(source = "processInstance.id", target = "id"),
@Mapping(source = "processInstance.name", target = "name"),
@Mapping(source = "processInstance.startUserId", target = "startUserId"),
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
@Mapping(source = "startUser.nickname", target = "startUserNickname")})
@Mappings({
@Mapping(source = "processInstance.id", target = "id"),
@Mapping(source = "processInstance.name", target = "name"),
@Mapping(source = "processInstance.startUserId", target = "startUserId"),
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
@Mapping(source = "startUser.nickname", target = "startUserNickname")
})
BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks,

View File

@ -1,105 +0,0 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.task;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
/**
* 任务流程关联表
*
* @author kemengkai
* @create 2022-05-09 10:33
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BpmActivityDO {
/**
* 任务流程关联id
*/
private String id;
/**
* 审批结果
*/
private Integer rev;
/**
* 任务流程部署id
*/
private String procDefId;
/**
* 任务流程id
*/
private String processInstanceId;
/**
* 任务执行id
*/
private String executionId;
/**
* 任务key
*/
private String activityId;
/**
* 任务id
*/
private String taskId;
/**
* 调用流程id
*/
private String callProcInstId;
/**
* 任务名称
*/
private String activityName;
/**
* 任务类型
*/
private String activityType;
/**
* 任务审批人id
*/
private String assignee;
/**
* 任务开始时间
*/
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime startTime;
/**
* 任务结束时间
*/
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
private LocalDateTime endTime;
private Integer transactionOrder;
private LocalDateTime duration;
/**
* 删除结果
*/
private String deleteReason;
/**
* 租户id
*/
private String tenantId;
}

View File

@ -15,8 +15,8 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmTaskAssignRuleConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
@ -89,7 +89,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
@Override
public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
String taskDefinitionKey) {
String taskDefinitionKey) {
return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, taskDefinitionKey);
}
@ -128,14 +128,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
// 校验是否已经配置
BpmTaskAssignRuleDO existRule =
taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(reqVO.getModelId(), reqVO.getTaskDefinitionKey());
taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(reqVO.getModelId(), reqVO.getTaskDefinitionKey());
if (existRule != null) {
throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
}
// 存储
BpmTaskAssignRuleDO rule = BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO)
.setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型才允许新建
.setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型才允许新建
taskRuleMapper.insert(rule);
return rule.getId();
}
@ -169,14 +169,14 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
// 遍历匹配对应的规则
Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap =
CollectionUtils.convertMap(processInstanceRules, BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
CollectionUtils.convertMap(processInstanceRules, BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
if (processInstanceRule == null) {
return false;
}
if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType()) || !ObjectUtil.equal(
modelRule.getOptions(), processInstanceRule.getOptions())) {
modelRule.getOptions(), processInstanceRule.getOptions())) {
return false;
}
}
@ -192,7 +192,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
// 开始复制
List<BpmTaskAssignRuleDO> newRules = BpmTaskAssignRuleConvert.INSTANCE.convertList2(rules);
newRules.forEach(rule -> rule.setProcessDefinitionId(toProcessDefinitionId).setId(null).setCreateTime(null)
.setUpdateTime(null));
.setUpdateTime(null));
taskRuleMapper.insertBatch(newRules);
}
@ -215,7 +215,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
roleApi.validRoleList(options);
} else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
deptApi.validateDeptList(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
postApi.validPostList(options);
@ -225,7 +225,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
userGroupService.validUserGroups(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
dictDataApi.validateDictDataList(DictTypeConstants.TASK_ASSIGN_SCRIPT,
CollectionUtils.convertSet(options, String::valueOf));
CollectionUtils.convertSet(options, String::valueOf));
} else {
throw new IllegalArgumentException(format("未知的规则类型({})", type));
}
@ -298,7 +298,7 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
}
private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}

View File

@ -40,6 +40,8 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
// mock 方法(startUser)
AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L));
when(adminUserApi.getUser(eq(1L))).thenReturn(startUser);
// mock 方法(getStartUserDept)没有部门
when(deptApi.getDept(eq(10L))).thenReturn(null);
// 调用
Set<Long> result = script.calculateTaskCandidateUsers(execution);
@ -56,7 +58,9 @@ public class BpmTaskAssignLeaderX2ScriptTest extends BaseMockitoUnitTest {
when(adminUserApi.getUser(eq(1L))).thenReturn(startUser);
DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L)
.setLeaderUserId(20L));
// mock 方法getDept
when(deptApi.getDept(eq(10L))).thenReturn(startUserDept);
when(deptApi.getDept(eq(100L))).thenReturn(null);
// 调用
Set<Long> result = script.calculateTaskCandidateUsers(execution);

View File

@ -120,7 +120,7 @@ public class BpmTaskAssignRuleServiceImplTest extends BaseDbUnitTest {
// mock 方法
List<AdminUserRespDTO> users = CollectionUtils.convertList(asSet(11L, 22L),
id -> new AdminUserRespDTO().setId(id));
when(adminUserApi.getUsersByPostIds(eq(rule.getOptions()))).thenReturn(users);
when(adminUserApi.getUserListByPostIds(eq(rule.getOptions()))).thenReturn(users);
mockGetUserMap(asSet(11L, 22L));
// 调用

View File

@ -23,10 +23,10 @@ import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgn
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
/**
* {@link BpmUserGroupServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
* {@link BpmUserGroupServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(BpmUserGroupServiceImpl.class)
public class BpmUserGroupServiceTest extends BaseDbUnitTest {

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.codegen.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 代码生成预览 Response VO,注意,每个文件都是一个该对象")
@Schema(description = "管理后台 - 代码生成预览 Response VO注意,每个文件都是一个该对象")
@Data
public class CodegenPreviewRespVO {

View File

@ -70,7 +70,7 @@ public class CodegenColumnBaseVO {
@NotNull(message = "是否为 List 查询操作的字段不能为空")
private Boolean listOperation;
@Schema(description = "List 查询操作的条件类型,参见 CodegenColumnListConditionEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "LIKE")
@Schema(description = "List 查询操作的条件类型参见 CodegenColumnListConditionEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "LIKE")
@NotNull(message = "List 查询操作的条件类型不能为空")
private String listOperationCondition;

View File

@ -12,7 +12,7 @@ import javax.validation.constraints.NotNull;
@Data
public class CodegenTableBaseVO {
@Schema(description = "生成场景,参见 CodegenSceneEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "生成场景参见 CodegenSceneEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "导入类型不能为空")
private Integer scene;

View File

@ -17,13 +17,13 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@ToString(callSuper = true)
public class CodegenTablePageReqVO extends PageParam {
@Schema(description = "表名称,模糊匹配", example = "yudao")
@Schema(description = "表名称模糊匹配", example = "yudao")
private String tableName;
@Schema(description = "表描述,模糊匹配", example = "芋道")
@Schema(description = "表描述模糊匹配", example = "芋道")
private String tableComment;
@Schema(description = "实体,模糊匹配", example = "Yudao")
@Schema(description = "实体模糊匹配", example = "Yudao")
private String className;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")

View File

@ -15,10 +15,10 @@ public class ConfigExportReqVO {
@Schema(description = "参数名称", example = "模糊匹配")
private String name;
@Schema(description = "参数键名,模糊匹配", example = "yunai.db.username")
@Schema(description = "参数键名模糊匹配", example = "yunai.db.username")
private String key;
@Schema(description = "参数类型,参见 SysConfigTypeEnum 枚举", example = "1")
@Schema(description = "参数类型参见 SysConfigTypeEnum 枚举", example = "1")
private Integer type;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")

View File

@ -17,13 +17,13 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@ToString(callSuper = true)
public class ConfigPageReqVO extends PageParam {
@Schema(description = "数据源名称,模糊匹配", example = "名称")
@Schema(description = "数据源名称模糊匹配", example = "名称")
private String name;
@Schema(description = "参数键名,模糊匹配", example = "yunai.db.username")
@Schema(description = "参数键名模糊匹配", example = "yunai.db.username")
private String key;
@Schema(description = "参数类型,参见 SysConfigTypeEnum 枚举", example = "1")
@Schema(description = "参数类型参见 SysConfigTypeEnum 枚举", example = "1")
private Integer type;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")

View File

@ -21,7 +21,7 @@ public class ConfigRespVO extends ConfigBaseVO {
@Size(max = 100, message = "参数键名长度不能超过100个字符")
private String key;
@Schema(description = "参数类型,参见 SysConfigTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "参数类型参见 SysConfigTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer type;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")

View File

@ -14,7 +14,7 @@ import java.util.Map;
@ToString(callSuper = true)
public class FileConfigCreateReqVO extends FileConfigBaseVO {
@Schema(description = "存储器,参见 FileStorageEnum 枚举类参见 FileStorageEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "存储器参见 FileStorageEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "存储器不能为空")
private Integer storage;

View File

@ -23,8 +23,8 @@ public class FileConfigPageReqVO extends PageParam {
@Schema(description = "存储器", example = "1")
private Integer storage;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -18,7 +18,7 @@ public class FileConfigRespVO extends FileConfigBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "存储器,参见 FileStorageEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "存储器参见 FileStorageEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "存储器不能为空")
private Integer storage;

View File

@ -17,14 +17,14 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@ToString(callSuper = true)
public class FilePageReqVO extends PageParam {
@Schema(description = "文件路径,模糊匹配", example = "yudao")
@Schema(description = "文件路径模糊匹配", example = "yudao")
private String path;
@Schema(description = "文件类型,模糊匹配", example = "application/octet-stream")
@Schema(description = "文件类型模糊匹配", example = "jpg")
private String type;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -12,13 +12,13 @@ import lombok.ToString;
@ToString(callSuper = true)
public class JobPageReqVO extends PageParam {
@Schema(description = "任务名称,模糊匹配", example = "测试任务")
@Schema(description = "任务名称模糊匹配", example = "测试任务")
private String name;
@Schema(description = "任务状态,参见 JobStatusEnum 枚举", example = "1")
@Schema(description = "任务状态参见 JobStatusEnum 枚举", example = "1")
private Integer status;
@Schema(description = "处理器的名字,模糊匹配", example = "sysUserSessionTimeoutJob")
@Schema(description = "处理器的名字模糊匹配", example = "sysUserSessionTimeoutJob")
private String handlerName;
}

View File

@ -43,7 +43,7 @@ public class JobLogBaseVO {
@Schema(description = "执行时长", example = "123")
private Integer duration;
@Schema(description = "任务状态,参见 JobLogStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "任务状态参见 JobLogStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "任务状态不能为空")
private Integer status;

View File

@ -8,14 +8,14 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 定时任务 Excel 导出 Request VO,参数和 JobLogPageReqVO 是一致的")
@Schema(description = "管理后台 - 定时任务 Excel 导出 Request VO参数和 JobLogPageReqVO 是一致的")
@Data
public class JobLogExportReqVO {
@Schema(description = "任务编号", example = "10")
private Long jobId;
@Schema(description = "处理器的名字,模糊匹配")
@Schema(description = "处理器的名字模糊匹配")
private String handlerName;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ -26,7 +26,7 @@ public class JobLogExportReqVO {
@Schema(description = "结束执行时间")
private LocalDateTime endTime;
@Schema(description = "任务状态,参见 JobLogStatusEnum 枚举")
@Schema(description = "任务状态参见 JobLogStatusEnum 枚举")
private Integer status;
}

View File

@ -20,7 +20,7 @@ public class JobLogPageReqVO extends PageParam {
@Schema(description = "任务编号", example = "10")
private Long jobId;
@Schema(description = "处理器的名字,模糊匹配")
@Schema(description = "处理器的名字模糊匹配")
private String handlerName;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ -31,7 +31,7 @@ public class JobLogPageReqVO extends PageParam {
@Schema(description = "结束执行时间")
private LocalDateTime endTime;
@Schema(description = "任务状态,参见 JobLogStatusEnum 枚举")
@Schema(description = "任务状态参见 JobLogStatusEnum 枚举")
private Integer status;
}

View File

@ -24,7 +24,7 @@ public class ApiAccessLogBaseVO {
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@Schema(description = "用户类型参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "用户类型不能为空")
private Integer userType;

View File

@ -8,7 +8,7 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - API 访问日志 Excel 导出 Request VO,参数和 ApiAccessLogPageReqVO 是一致的")
@Schema(description = "管理后台 - API 访问日志 Excel 导出 Request VO参数和 ApiAccessLogPageReqVO 是一致的")
@Data
public class ApiAccessLogExportReqVO {
@ -21,11 +21,11 @@ public class ApiAccessLogExportReqVO {
@Schema(description = "应用名", example = "dashboard")
private String applicationName;
@Schema(description = "请求地址,模糊匹配", example = "/xxx/yyy")
@Schema(description = "请求地址模糊匹配", example = "/xxx/yyy")
private String requestUrl;
@Schema(description = "开始时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "开始请求时间")
private LocalDateTime[] beginTime;
@Schema(description = "执行时长,大于等于,单位:毫秒", example = "100")

View File

@ -26,11 +26,11 @@ public class ApiAccessLogPageReqVO extends PageParam {
@Schema(description = "应用名", example = "dashboard")
private String applicationName;
@Schema(description = "请求地址,模糊匹配", example = "/xxx/yyy")
@Schema(description = "请求地址模糊匹配", example = "/xxx/yyy")
private String requestUrl;
@Schema(description = "开始时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "开始请求时间")
private LocalDateTime[] beginTime;
@Schema(description = "执行时长,大于等于,单位:毫秒", example = "100")

View File

@ -8,7 +8,7 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - API 错误日志 Excel 导出 Request VO,参数和 ApiErrorLogPageReqVO 是一致的")
@Schema(description = "管理后台 - API 错误日志 Excel 导出 Request VO参数和 ApiErrorLogPageReqVO 是一致的")
@Data
public class ApiErrorLogExportReqVO {

View File

@ -7,7 +7,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 字典类型 Excel 导出 Request VO,参数和 TestDemoPageReqVO 是一致的")
@Schema(description = "管理后台 - 字典类型 Excel 导出 Request VO参数和 TestDemoPageReqVO 是一致的")
@Data
public class TestDemoExportReqVO {

View File

@ -57,7 +57,7 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");
}
GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 Date 类型不使用 LocalDate
GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型不使用 LocalDate
ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, strategyConfig.build(),
null, globalConfig, null);
// 按照名字排序

View File

@ -79,7 +79,7 @@ public class TestDemoServiceImpl implements TestDemoService {
@Override
public PageResult<TestDemoDO> getTestDemoPage(TestDemoPageReqVO pageReqVO) {
testDemoMapper.selectList2();
// testDemoMapper.selectList2();
return testDemoMapper.selectPage(pageReqVO);
}

View File

@ -30,14 +30,13 @@ public interface ProductSkuApi {
*/
List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
// TODO puhui999入参用 Collection<Long> 更通用
/**
* 批量查询 SKU 数组
*
* @param spuIds SPU 编号列表
* @return SKU 数组
*/
List<ProductSkuRespDTO> getSkuListBySpuId(List<Long> spuIds);
List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds);
/**
* 更新 SKU 库存

View File

@ -45,10 +45,8 @@ public interface ErrorCodeConstants {
ErrorCode SKU_STOCK_NOT_ENOUGH = new ErrorCode(1008006004, "商品 SKU 库存不足");
// ========== 商品 评价 1008007000 ==========
ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1008007000, "商品 评价 不存在");
ErrorCode ORDER_SPU_COMMENT_EXISTS = new ErrorCode(1008007001, "订单 商品评价 已存在");
ErrorCode COMMENT_ERROR_OPT = new ErrorCode(1008007002, "商品评价非法操作");
ErrorCode COMMENT_ADDITIONAL_EXISTS = new ErrorCode(1008007003, "商品追加评价已存在");
ErrorCode COMMENT_NOT_EXISTS = new ErrorCode(1008007000, "商品评价不存在");
ErrorCode COMMENT_ORDER_EXISTS = new ErrorCode(1008007001, "订单的商品评价已存在");
// ========== 商品 收藏 1008008000 ==========
ErrorCode FAVORITE_EXISTS = new ErrorCode(1008008000, "该商品已经被收藏");

View File

@ -43,7 +43,7 @@ public class ProductSkuApiImpl implements ProductSkuApi {
}
@Override
public List<ProductSkuRespDTO> getSkuListBySpuId(List<Long> spuIds) {
public List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds) {
if (CollUtil.isEmpty(spuIds)) {
return Collections.emptyList();
}

View File

@ -26,23 +26,10 @@ public class ProductCommentBaseVO {
@NotNull(message = "评价人头像不能为空")
private String userAvatar;
// TODO @puhuispuIdspuName 是不是只有 ProductCommentRespVO 有呀
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑透气小短袖")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotNull(message = "商品 SPU 名称不能为空")
private String spuName;
@Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "商品 SKU 编号不能为空")
private Long skuId;
@Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "评分星级不能为空")
private Integer scores;
@Schema(description = "描述星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
@NotNull(message = "描述星级不能为空")
private Integer descriptionScores;

View File

@ -35,9 +35,8 @@ public class ProductCommentPageReqVO extends PageParam {
@InEnum(ProductCommentScoresEnum.class)
private Integer scores;
// TODO @puhui999replyStatus
@Schema(description = "商家是否回复", example = "true")
private Boolean replied;
private Boolean replyStatus;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)

View File

@ -5,6 +5,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 商品评价 Response VO")
@ -40,4 +41,15 @@ public class ProductCommentRespVO extends ProductCommentBaseVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "评分星级 1-5 分", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer scores;
@Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "清凉丝滑透气小短袖")
@NotNull(message = "商品 SPU 编号不能为空")
private Long spuId;
@Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
@NotNull(message = "商品 SPU 名称不能为空")
private String spuName;
}

View File

@ -21,6 +21,7 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -29,11 +30,6 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
/**
* 商品 SPU 相关接口
*
* @author HUIHUI
*/
@Tag(name = "管理后台 - 商品 SPU")
@RestController
@RequestMapping("/product/spu")
@ -100,6 +96,15 @@ public class ProductSpuController {
return success(ProductSpuConvert.INSTANCE.convertList02(list));
}
@GetMapping("/list")
@Operation(summary = "获得商品 SPU 详情列表")
@Parameter(name = "spuIds", description = "spu 编号列表", required = true, example = "[1,2,3]")
@PreAuthorize("@ss.hasPermission('product:spu:query')")
public CommonResult<List<ProductSpuDetailRespVO>> getSpuList(@RequestParam("spuIds") Collection<Long> spuIds) {
return success(ProductSpuConvert.INSTANCE.convertForSpuDetailRespListVO(
productSpuService.getSpuList(spuIds), productSkuService.getSkuListBySpuId(spuIds)));
}
@GetMapping("/page")
@Operation(summary = "获得商品 SPU 分页")
@PreAuthorize("@ss.hasPermission('product:spu:query')")

View File

@ -9,11 +9,6 @@ import lombok.ToString;
import javax.validation.Valid;
import java.util.List;
/**
* 商品 SPU 创建 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)

View File

@ -8,12 +8,6 @@ import lombok.ToString;
import java.util.List;
/**
* 商品 SPU 详细 Response VO
* 包括关联的 SKU 等信息
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 详细 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)

View File

@ -10,12 +10,7 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 商品Spu导出 Request VO,参数和 ProductSpuPageReqVO 是一致的
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品Spu导出 Request VO,参数和 ProductSpuPageReqVO 是一致的")
@Schema(description = "管理后台 - 商品 SPU 导出 Request VO参数和 ProductSpuPageReqVO 是一致的")
@Data
@NoArgsConstructor
@AllArgsConstructor

View File

@ -11,11 +11,6 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 商品 SPU 分页 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)

View File

@ -7,11 +7,6 @@ import lombok.ToString;
import java.time.LocalDateTime;
/**
* 商品 SPU Response VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU Response VO")
@Data
@EqualsAndHashCode(callSuper = true)

View File

@ -4,11 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
/**
* 商品 SPU 精简 Response VO
* TODO 商品 SPU 精简 VO 暂时没有使用到用到的时候再按需添加\修改属性
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 精简 Response VO")
@Data
@ToString(callSuper = true)

View File

@ -12,11 +12,6 @@ import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 商品 SPU 更新 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU 更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)

View File

@ -7,11 +7,6 @@ import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 商品 SPU Status 更新 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 商品 SPU Status 更新 Request VO")
@Data
public class ProductSpuUpdateStatusReqVO{

View File

@ -1,18 +1,18 @@
package cn.iocoder.yudao.module.product.controller.app.comment;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO;
import cn.iocoder.yudao.module.product.controller.app.property.vo.value.AppProductPropertyValueDetailRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.service.comment.ProductCommentService;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
@ -26,11 +26,9 @@ import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -53,34 +51,24 @@ public class AppProductCommentController {
@Parameter(name = "spuId", description = "商品 SPU 编号", required = true, example = "1024"),
@Parameter(name = "count", description = "数量", required = true, example = "10")
})
public CommonResult<List<AppProductCommentRespVO>> getCommentList(@RequestParam("spuId") Long spuId,
@RequestParam(value = "count", defaultValue = "10") Integer count) {
public CommonResult<List<AppProductCommentRespVO>> getCommentList(
@RequestParam("spuId") Long spuId,
@RequestParam(value = "count", defaultValue = "10") Integer count) {
return success(productCommentService.getCommentList(spuId, count));
}
@GetMapping("/page")
@Operation(summary = "获得商品评价分页")
public CommonResult<PageResult<AppProductCommentRespVO>> getCommentPage(@Valid AppCommentPageReqVO pageVO) {
PageResult<AppProductCommentRespVO> page = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
// TODO @puhui CollUtils 有简化 convertmap list 的方法
Set<Long> skuIds = page.getList().stream().map(AppProductCommentRespVO::getSkuId).collect(Collectors.toSet());
// TODO @puhui999写到 convert 可以更简洁哈
PageResult<ProductCommentDO> commentDOPage = productCommentService.getCommentPage(pageVO, Boolean.TRUE);
Set<Long> skuIds = CollectionUtils.convertSet(commentDOPage.getList(), ProductCommentDO::getSkuId);
List<ProductSkuDO> skuList = productSkuService.getSkuList(skuIds);
Map<Long, ProductSkuDO> skuDOMap = new HashMap<>(skuIds.size());
Map<Long, ProductSkuDO> skuDOMap = Maps.newLinkedHashMapWithExpectedSize(skuIds.size());
if (CollUtil.isNotEmpty(skuList)) {
skuDOMap.putAll(skuList.stream().collect(Collectors.toMap(ProductSkuDO::getId, c -> c)));
skuDOMap.putAll(CollectionUtils.convertMap(skuList, ProductSkuDO::getId, c -> c));
}
// TODO @puihui999下面也可以放到 convert 里哈
page.getList().forEach(item -> {
// 判断用户是否选择匿名
if (ObjectUtil.equal(item.getAnonymous(), true)) {
item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS);
}
ProductSkuDO productSkuDO = skuDOMap.get(item.getSkuId());
if (productSkuDO != null) {
List<AppProductPropertyValueDetailRespVO> skuProperties = ProductCommentConvert.INSTANCE.convertList01(productSkuDO.getProperties());
item.setSkuProperties(skuProperties);
}
});
PageResult<AppProductCommentRespVO> page = ProductCommentConvert.INSTANCE.convertPage02(commentDOPage, skuDOMap);
return success(page);
}

View File

@ -10,11 +10,6 @@ import javax.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户 App - 商品评价详情 Response VO
*
* @author HUIHUI
*/
@Schema(description = "用户 App - 商品评价详情 Response VO")
@Data
@ToString(callSuper = true)

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.product.convert.comment;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentCreateReqVO;
@ -19,6 +21,7 @@ import org.mapstruct.factory.Mappers;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
/**
* 商品评价 Convert
@ -32,19 +35,33 @@ public interface ProductCommentConvert {
ProductCommentRespVO convert(ProductCommentDO bean);
// TODO @puhui999这里貌似字段对上就不用 mapping 可以测试下看看哈
@Mapping(target = "goodCount", source = "goodCount")
@Mapping(target = "mediocreCount", source = "mediocreCount")
@Mapping(target = "negativeCount", source = "negativeCount")
@Mapping(target = "scores", expression = "java(calculateOverallScore(goodCount, mediocreCount, negativeCount))")
AppCommentStatisticsRespVO convert(Long goodCount, Long mediocreCount, Long negativeCount);
@Named("calculateOverallScore")
default double calculateOverallScore(long goodCount, long mediocreCount, long negativeCount) {
return (goodCount * 5 + mediocreCount * 3 + negativeCount) / (double) (goodCount + mediocreCount + negativeCount);
}
List<ProductCommentRespVO> convertList(List<ProductCommentDO> list);
List<AppProductPropertyValueDetailRespVO> convertList01(List<ProductSkuDO.Property> properties);
PageResult<ProductCommentRespVO> convertPage(PageResult<ProductCommentDO> page);
PageResult<AppProductCommentRespVO> convertPage02(PageResult<ProductCommentDO> pageResult);
PageResult<AppProductCommentRespVO> convertPage01(PageResult<ProductCommentDO> pageResult);
default PageResult<AppProductCommentRespVO> convertPage02(PageResult<ProductCommentDO> pageResult,
Map<Long, ProductSkuDO> skuMap) {
PageResult<AppProductCommentRespVO> page = convertPage01(pageResult);
page.getList().forEach(item -> {
// 判断用户是否选择匿名
if (ObjectUtil.equal(item.getAnonymous(), true)) {
item.setUserNickname(ProductCommentDO.NICKNAME_ANONYMOUS);
}
MapUtils.findAndThen(skuMap, item.getSkuId(),
sku -> item.setSkuProperties(convertList01(sku.getProperties())));
});
return page;
}
List<AppProductPropertyValueDetailRespVO> convertList01(List<ProductSkuDO.Property> properties);
/**
* 计算综合评分
@ -63,14 +80,19 @@ public interface ProductCommentConvert {
ProductCommentDO convert(ProductCommentCreateReqDTO createReqDTO);
@Mapping(target = "scores", expression = "java(convertScores(createReqDTO.getDescriptionScores(), createReqDTO.getBenefitScores()))")
@Mapping(target = "scores",
expression = "java(convertScores(createReqDTO.getDescriptionScores(), createReqDTO.getBenefitScores()))")
default ProductCommentDO convert(ProductCommentCreateReqDTO createReqDTO, ProductSpuDO spuDO, MemberUserRespDTO user) {
ProductCommentDO commentDO = convert(createReqDTO);
commentDO.setUserId(user.getId());
commentDO.setUserNickname(user.getNickname());
commentDO.setUserAvatar(user.getAvatar());
commentDO.setSpuId(spuDO.getId());
commentDO.setSpuName(spuDO.getName());
if (user != null) {
commentDO.setUserId(user.getId());
commentDO.setUserNickname(user.getNickname());
commentDO.setUserAvatar(user.getAvatar());
}
if (spuDO != null) {
commentDO.setSpuId(spuDO.getId());
commentDO.setSpuName(spuDO.getName());
}
return commentDO;
}
@ -78,7 +100,8 @@ public interface ProductCommentConvert {
@Mapping(target = "orderId", constant = "0L")
@Mapping(target = "orderItemId", constant = "0L")
@Mapping(target = "anonymous", expression = "java(Boolean.FALSE)")
@Mapping(target = "scores", expression = "java(convertScores(createReq.getDescriptionScores(), createReq.getBenefitScores()))")
@Mapping(target = "scores",
expression = "java(convertScores(createReq.getDescriptionScores(), createReq.getBenefitScores()))")
ProductCommentDO convert(ProductCommentCreateReqVO createReq);
List<AppProductCommentRespVO> convertList02(List<ProductCommentDO> list);

View File

@ -18,8 +18,10 @@ 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.convertMultiMap;
/**
* 商品 SPU Convert
@ -107,4 +109,16 @@ public interface ProductSpuConvert {
return detailRespVO;
}
default List<ProductSpuDetailRespVO> convertForSpuDetailRespListVO(List<ProductSpuDO> spus, List<ProductSkuDO> skus) {
List<ProductSpuDetailRespVO> vos = new ArrayList<>(spus.size());
Map<Long, List<ProductSkuDO>> skuMultiMap = convertMultiMap(skus, ProductSkuDO::getSpuId);
// TODO @puhui999可以直接使用 CollUtils.convertList
spus.forEach(spu -> {
ProductSpuDetailRespVO detailRespVO = convert03(spu);
detailRespVO.setSkus(ProductSkuConvert.INSTANCE.convertList(skuMultiMap.get(spu.getId())));
vos.add(detailRespVO);
});
return vos;
}
}

View File

@ -26,6 +26,7 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
}
static void appendTabQuery(LambdaQueryWrapperX<ProductCommentDO> queryWrapper, Integer type) {
// TODO @puhui999是不是不用 apply 直接用 mybatis 的方法就好啦
// 构建好评查询语句好评计算 总评 >= 4
if (ObjectUtil.equal(type, AppCommentPageReqVO.GOOD_COMMENT)) {
queryWrapper.apply("scores >= 4");
@ -51,11 +52,11 @@ public interface ProductCommentMapper extends BaseMapperX<ProductCommentDO> {
return selectPage(reqVO, queryWrapper);
}
default ProductCommentDO selectByUserIdAndOrderItemIdAndSpuId(Long userId, Long orderItemId, Long spuId) {
default ProductCommentDO selectByUserIdAndOrderItemIdAndSpuId(Long userId, Long orderItemId, Long skuId) {
return selectOne(new LambdaQueryWrapperX<ProductCommentDO>()
.eq(ProductCommentDO::getUserId, userId)
.eq(ProductCommentDO::getOrderItemId, orderItemId)
.eq(ProductCommentDO::getSpuId, spuId));
.eq(ProductCommentDO::getSpuId, skuId));
}
default Long selectCountBySpuId(Long spuId, Boolean visible, Integer type) {

View File

@ -54,7 +54,7 @@ public interface ProductCommentService {
* @param visible 是否可见
* @return 商品评价分页
*/
PageResult<AppProductCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible);
/**
* 创建商品评论

View File

@ -81,7 +81,7 @@ public class ProductCommentServiceImpl implements ProductCommentService {
@Transactional(rollbackFor = Exception.class)
public void createComment(ProductCommentCreateReqVO createReqVO) {
// 校验评论
validateComment(createReqVO.getSpuId(), createReqVO.getUserId(), createReqVO.getOrderItemId());
validateComment(createReqVO.getSkuId(), createReqVO.getUserId(), createReqVO.getOrderItemId());
ProductCommentDO commentDO = ProductCommentConvert.INSTANCE.convert(createReqVO);
productCommentMapper.insert(commentDO);
@ -108,11 +108,11 @@ public class ProductCommentServiceImpl implements ProductCommentService {
return commentDO.getId();
}
private void validateComment(Long spuId, Long userId, Long orderItemId) {
private void validateComment(Long skuId, Long userId, Long orderItemId) {
// 判断当前订单的当前商品用户是否评价过
ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemIdAndSpuId(userId, orderItemId, spuId);
ProductCommentDO exist = productCommentMapper.selectByUserIdAndOrderItemIdAndSpuId(userId, orderItemId, skuId);
if (null != exist) {
throw exception(ORDER_SPU_COMMENT_EXISTS);
throw exception(COMMENT_ORDER_EXISTS);
}
}
@ -141,23 +141,17 @@ public class ProductCommentServiceImpl implements ProductCommentService {
productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.MEDIOCRE_COMMENT),
// 查询商品 id = spuId 的所有差评数量
productCommentMapper.selectCountBySpuId(spuId, visible, AppCommentPageReqVO.NEGATIVE_COMMENT)
).setScores(3.0); // TODO @puhui999这里要实现下
);
}
@Override
public List<AppProductCommentRespVO> getCommentList(Long spuId, Integer count) {
// 校验商品 spu 是否存在
// TODO @puhui 这里校验可以去掉哈
ProductSpuDO spuDO = validateSpu(spuId);
return ProductCommentConvert.INSTANCE.convertList02(productCommentMapper.selectCommentList(spuDO.getId(), count).getList());
return ProductCommentConvert.INSTANCE.convertList02(productCommentMapper.selectCommentList(spuId, count).getList());
}
// TODO @puhui 可以放到 controller convert
@Override
public PageResult<AppProductCommentRespVO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
// TODO @puhui 可以放到 controller convert
return ProductCommentConvert.INSTANCE.convertPage02(
productCommentMapper.selectPage(pageVO, visible));
public PageResult<ProductCommentDO> getCommentPage(AppCommentPageReqVO pageVO, Boolean visible) {
return productCommentMapper.selectPage(pageVO, visible);
}
@Override

View File

@ -90,7 +90,7 @@ public interface ProductSkuService {
* @param spuIds spu 编码集合
* @return 商品 sku 集合
*/
List<ProductSkuDO> getSkuListBySpuId(List<Long> spuIds);
List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds);
/**
* 通过 spuId 删除 sku 信息

View File

@ -148,7 +148,7 @@ public class ProductSkuServiceImpl implements ProductSkuService {
}
@Override
public List<ProductSkuDO> getSkuListBySpuId(List<Long> spuIds) {
public List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds) {
return productSkuMapper.selectListBySpuId(spuIds);
}

View File

@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommen
import cn.iocoder.yudao.module.product.controller.admin.comment.vo.ProductCommentUpdateVisibleReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentPageReqVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppCommentStatisticsRespVO;
import cn.iocoder.yudao.module.product.controller.app.comment.vo.AppProductCommentRespVO;
import cn.iocoder.yudao.module.product.convert.comment.ProductCommentConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.comment.ProductCommentDO;
import cn.iocoder.yudao.module.product.dal.mysql.comment.ProductCommentMapper;
@ -128,7 +127,7 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
productCommentPageReqVO.setSpuId(spuId);
productCommentPageReqVO.setSpuName("感冒药");
productCommentPageReqVO.setScores(ProductCommentScoresEnum.FOUR.getScores());
productCommentPageReqVO.setReplied(Boolean.TRUE);
productCommentPageReqVO.setReplyStatus(Boolean.TRUE);
PageResult<ProductCommentDO> commentPage = productCommentService.getCommentPage(productCommentPageReqVO);
PageResult<ProductCommentRespVO> result = ProductCommentConvert.INSTANCE.convertPage(productCommentMapper.selectPage(productCommentPageReqVO));
@ -138,15 +137,15 @@ public class ProductCommentServiceImplTest extends BaseDbUnitTest {
assertEquals(8, all.getTotal());
// 测试获取所有商品分页评论数据
PageResult<AppProductCommentRespVO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
PageResult<ProductCommentDO> result1 = productCommentService.getCommentPage(new AppCommentPageReqVO(), Boolean.TRUE);
assertEquals(7, result1.getTotal());
// 测试获取所有商品分页中评数据
PageResult<AppProductCommentRespVO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
PageResult<ProductCommentDO> result2 = productCommentService.getCommentPage(new AppCommentPageReqVO().setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
assertEquals(2, result2.getTotal());
// 测试获取指定 spuId 商品分页中评数据
PageResult<AppProductCommentRespVO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
PageResult<ProductCommentDO> result3 = productCommentService.getCommentPage(new AppCommentPageReqVO().setSpuId(spuId).setType(AppCommentPageReqVO.MEDIOCRE_COMMENT), Boolean.TRUE);
assertEquals(2, result3.getTotal());
// 测试分页 tab count

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import javax.validation.Valid;
// TODO @puhui999:CombinationRecordApi 分成活动记录哈
// TODO @芋艿后面也再撸撸这几个接口
/**
* 拼团活动 API 接口
*
* @author HUIHUI
*/
public interface CombinationApi {
/**
* 创建开团记录
*
* @param reqDTO 请求 DTO
*/
void createRecord(@Valid CombinationRecordReqDTO reqDTO);
/**
* 获取开团记录状态
*
* @param userId 用户编号
* @param orderId 订单编号
*/
boolean validateRecordStatusIsSuccess(Long userId, Long orderId);
/**
* 更新开团记录状态
*
* @param userId 用户编号
* @param orderId 订单编号
* @param status 状态值
*/
void updateRecordStatus(Long userId, Long orderId, Integer status);
/**
* 更新开团记录状态和开始时间
*
* @param userId 用户编号
* @param orderId 订单编号
* @param status 状态值
*/
void updateRecordStatusAndStartTime(Long userId, Long orderId, Integer status);
}

View File

@ -0,0 +1,78 @@
package cn.iocoder.yudao.module.promotion.api.combination.dto;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
// TODO @puhui999CombinationRecordCreateReqDTO这样更容易知道是创建噢
/**
* 拼团记录 Request DTO
*
* @author HUIHUI
*/
@Data
public class CombinationRecordReqDTO {
/**
* 拼团活动编号
*/
@NotNull(message = "拼团活动编号不能为空")
private Long activityId;
/**
* spu 编号
*/
@NotNull(message = "spu 编号不能为空")
private Long spuId;
/**
* sku 编号
*/
@NotNull(message = "sku 编号不能为空")
private Long skuId;
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 订单编号
*/
@NotNull(message = "订单编号不能为空")
private Long orderId;
/**
* 团长编号
*/
@NotNull(message = "团长编号不能为空")
private Long headId;
/**
* 商品名字
*/
@NotEmpty(message = "商品名字不能为空")
private String spuName;
/**
* 商品图片
*/
@NotEmpty(message = "商品图片不能为空")
private String picUrl;
/**
* 拼团商品单价
*/
@NotNull(message = "拼团商品单价不能为空")
private Integer combinationPrice;
/**
* 用户昵称
*/
@NotEmpty(message = "用户昵称不能为空")
private String nickname;
/**
* 用户头像
*/
@NotEmpty(message = "用户头像不能为空")
private String avatar;
/**
* 开团状态正在开团 拼团成功 拼团失败 TODO 等待支付
*/
@NotNull(message = "开团状态不能为空")
private Integer status;
}

View File

@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* Promotion 错误码枚举类
*
* <p>
* promotion 系统使用 1-013-000-000
*/
public interface ErrorCodeConstants {
@ -42,8 +42,7 @@ public interface ErrorCodeConstants {
ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1013006004, "满减送活动已关闭,不能重复关闭");
ErrorCode REWARD_ACTIVITY_CLOSE_FAIL_STATUS_END = new ErrorCode(1013006005, "满减送活动已结束,不能关闭");
// ========== Price 相关 1013007000 ============
ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1013007000, "支付价格计算异常,原因:价格小于等于 0");
// ========== TODO 空着 1013007000 ============
// ========== 秒杀活动 1013008000 ==========
ErrorCode SECKILL_ACTIVITY_NOT_EXISTS = new ErrorCode(1013008000, "秒杀活动不存在");
@ -58,5 +57,13 @@ public interface ErrorCodeConstants {
ErrorCode SECKILL_TIME_CONFLICTS = new ErrorCode(1013009001, "秒杀时段冲突");
ErrorCode SECKILL_TIME_EQUAL = new ErrorCode(1013009002, "秒杀时段开始时间和结束时间不能相等");
ErrorCode SECKILL_START_TIME_BEFORE_END_TIME = new ErrorCode(1013009003, "秒杀时段开始时间不能在结束时间之后");
ErrorCode SECKILL_TIME_DISABLE = new ErrorCode(1013009004, "秒杀时段已关闭");
// ========== 拼团活动 1013010000 ==========
ErrorCode COMBINATION_ACTIVITY_NOT_EXISTS = new ErrorCode(1013010000, "拼团活动不存在");
ErrorCode COMBINATION_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1013010001, "存在商品参加了其它拼团活动");
ErrorCode COMBINATION_ACTIVITY_STATUS_DISABLE = new ErrorCode(1013010002, "拼团活动已关闭不能修改");
ErrorCode COMBINATION_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1013010003, "拼团活动未关闭或未结束,不能删除");
ErrorCode COMBINATION_RECORD_NOT_EXISTS = new ErrorCode(1013010004, "拼团不存在");
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.promotion.enums.combination;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 拼团状态枚举
*
* @author HUIHUI
*/
@AllArgsConstructor
@Getter
public enum CombinationRecordStatusEnum implements IntArrayValuable {
WAITING(0, "未付款"),
IN_PROGRESS(1, "进行中"),
SUCCESS(2, "拼团成功"),
FAILED(3, "拼团失败");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CombinationRecordStatusEnum::getStatus).toArray();
/**
* 状态值
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.promotion.api.combination;
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordReqDTO;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* 拼团活动 API 实现类
*
* @author HUIHUI
*/
@Service
public class CombinationApiImpl implements CombinationApi {
@Resource
private CombinationActivityService activityService;
@Override
public void createRecord(CombinationRecordReqDTO reqDTO) {
activityService.createRecord(reqDTO);
}
@Override
public boolean validateRecordStatusIsSuccess(Long userId, Long orderId) {
return activityService.validateRecordStatusIsSuccess(userId, orderId);
}
@Override
public void updateRecordStatus(Long userId, Long orderId, Integer status) {
activityService.updateRecordStatusByUserIdAndOrderId(userId, orderId, status);
}
@Override
public void updateRecordStatusAndStartTime(Long userId, Long orderId, Integer status) {
activityService.updateRecordStatusAndStartTimeByUserIdAndOrderId(userId, orderId, status, LocalDateTime.now());
}
}

View File

@ -0,0 +1,114 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity.*;
import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationActivityDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.combinationactivity.CombinationProductDO;
import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static cn.hutool.core.collection.CollectionUtil.newArrayList;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
@Tag(name = "管理后台 - 拼团活动")
@RestController
@RequestMapping("/promotion/combination-activity")
@Validated
public class CombinationActivityController {
@Resource
private CombinationActivityService combinationActivityService;
@Resource
private ProductSpuApi spuApi;
@PostMapping("/create")
@Operation(summary = "创建拼团活动")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:create')")
public CommonResult<Long> createCombinationActivity(@Valid @RequestBody CombinationActivityCreateReqVO createReqVO) {
return success(combinationActivityService.createCombinationActivity(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新拼团活动")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:update')")
public CommonResult<Boolean> updateCombinationActivity(@Valid @RequestBody CombinationActivityUpdateReqVO updateReqVO) {
combinationActivityService.updateCombinationActivity(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除拼团活动")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:delete')")
public CommonResult<Boolean> deleteCombinationActivity(@RequestParam("id") Long id) {
combinationActivityService.deleteCombinationActivity(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得拼团活动")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
public CommonResult<CombinationActivityRespVO> getCombinationActivity(@RequestParam("id") Long id) {
CombinationActivityDO activity = combinationActivityService.getCombinationActivity(id);
List<CombinationProductDO> products = combinationActivityService.getProductsByActivityIds(newArrayList(id));
return success(CombinationActivityConvert.INSTANCE.convert(activity, products));
}
@GetMapping("/list")
@Operation(summary = "获得拼团活动列表")
@Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
public CommonResult<List<CombinationActivityRespVO>> getCombinationActivityList(@RequestParam("ids") Collection<Long> ids) {
List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(ids);
return success(CombinationActivityConvert.INSTANCE.convertList(list));
}
@GetMapping("/page")
@Operation(summary = "获得拼团活动分页")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
public CommonResult<PageResult<CombinationActivityRespVO>> getCombinationActivityPage(
@Valid CombinationActivityPageReqVO pageVO) {
PageResult<CombinationActivityDO> pageResult = combinationActivityService.getCombinationActivityPage(pageVO);
// TODO @puhui999可以不一定 aIds直接批量查询结果出来下面也是类似
Set<Long> aIds = CollectionUtils.convertSet(pageResult.getList(), CombinationActivityDO::getId);
List<CombinationProductDO> products = combinationActivityService.getProductsByActivityIds(aIds);
Set<Long> spuIds = CollectionUtils.convertSet(pageResult.getList(), CombinationActivityDO::getSpuId);
List<ProductSpuRespDTO> spus = spuApi.getSpuList(spuIds);
return success(CombinationActivityConvert.INSTANCE.convertPage(pageResult, products, spus));
}
@GetMapping("/export-excel")
@Operation(summary = "导出拼团活动 Excel")
@PreAuthorize("@ss.hasPermission('promotion:combination-activity:export')")
@OperateLog(type = EXPORT)
public void exportCombinationActivityExcel(@Valid CombinationActivityExportReqVO exportReqVO,
HttpServletResponse response) throws IOException {
List<CombinationActivityDO> list = combinationActivityService.getCombinationActivityList(exportReqVO);
// 导出 Excel
List<CombinationActivityExcelVO> datas = CombinationActivityConvert.INSTANCE.convertList02(list);
ExcelUtils.write(response, "拼团活动.xls", "数据", CombinationActivityExcelVO.class, datas);
}
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/**
* 拼团活动 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CombinationActivityBaseVO {
@Schema(description = "拼团名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "越拼越省钱")
@NotNull(message = "拼团名称不能为空")
private String name;
@Schema(description = "商品 SPU 编号,关联 ProductSpuDO 的 id", example = "[1,2,3]")
@NotNull(message = "拼团商品不能为空")
private Long spuId;
@Schema(description = "总限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "16218")
@NotNull(message = "总限购数量不能为空")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "28265")
@NotNull(message = "单次限购数量不能为空")
private Integer singleLimitCount;
// TODO @puhui999是不是弄成 2 个字段会好点哈开始结束
@Schema(description = "活动时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")
@NotNull(message = "活动时间不能为空")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityTime;
@Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED, example = "25222")
@NotNull(message = "开团人数不能为空")
private Integer userSize;
@Schema(description = "限制时长(小时)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "限制时长不能为空")
private Integer limitDuration;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductCreateReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityCreateReqVO extends CombinationActivityBaseVO {
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductCreateReqVO> products;
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
// TODO @puhui999如无必要导出都可以删除哈
/**
* 拼团活动 Excel VO
*
* @author HUIHUI
*/
@Data
public class CombinationActivityExcelVO {
@ExcelProperty("活动编号")
private Long id;
@ExcelProperty("拼团名称")
private String name;
@ExcelProperty("商品 SPU 编号关联 ProductSpuDO 的 id")
private Long spuId;
@ExcelProperty("总限购数量")
private Integer totalLimitCount;
@ExcelProperty("单次限购数量")
private Integer singleLimitCount;
@ExcelProperty("开始时间")
private LocalDateTime startTime;
@ExcelProperty("结束时间")
private LocalDateTime endTime;
@ExcelProperty("开团人数")
private Integer userSize;
@ExcelProperty("开团组数")
private Integer totalNum;
@ExcelProperty("成团组数")
private Integer successNum;
@ExcelProperty("参与人数")
private Integer orderUserCount;
@ExcelProperty("虚拟成团")
private Integer virtualGroup;
@ExcelProperty(value = "活动状态0开启 1关闭", converter = DictConvert.class)
@DictFormat("common_status") // TODO 代码优化建议设置到对应的 XXXDictTypeConstants 枚举类中
private Integer status;
@ExcelProperty("限制时长(小时)")
private Integer limitDuration;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @puhui999如无必要导出都可以删除哈
@Schema(description = "管理后台 - 拼团活动 Excel 导出 Request VO参数和 CombinationActivityPageReqVO 是一致的")
@Data
public class CombinationActivityExportReqVO {
@Schema(description = "拼团名称", example = "赵六")
private String name;
@Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "14016")
private Long spuId;
@Schema(description = "总限购数量", example = "16218")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", example = "28265")
private Integer singleLimitCount;
@Schema(description = "开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] startTime;
@Schema(description = "结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] endTime;
@Schema(description = "开团人数")
private Integer userSize;
@Schema(description = "开团组数")
private Integer totalNum;
@Schema(description = "成团组数")
private Integer successNum;
@Schema(description = "参与人数", example = "25222")
private Integer orderUserCount;
@Schema(description = "虚拟成团")
private Integer virtualGroup;
@Schema(description = "活动状态0开启 1关闭", example = "0")
private Integer status;
@Schema(description = "限制时长(小时)")
private Integer limitDuration;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
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;
import org.springframework.format.annotation.DateTimeFormat;
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")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityPageReqVO extends PageParam {
@Schema(description = "拼团名称", example = "赵六")
private String name;
@Schema(description = "商品 SPU 编号关联 ProductSpuDO 的 id", example = "14016")
private Long spuId;
@Schema(description = "总限购数量", example = "16218")
private Integer totalLimitCount;
@Schema(description = "单次限购数量", example = "28265")
private Integer singleLimitCount;
@Schema(description = "开始时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] startTime;
@Schema(description = "结束时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] endTime;
@Schema(description = "开团人数")
private Integer userSize;
@Schema(description = "开团组数")
private Integer totalNum;
@Schema(description = "成团组数")
private Integer successNum;
@Schema(description = "参与人数", example = "25222")
private Integer orderUserCount;
@Schema(description = "虚拟成团")
private Integer virtualGroup;
@Schema(description = "活动状态0开启 1关闭", example = "0")
private Integer status;
@Schema(description = "限制时长(小时)")
private Integer limitDuration;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityRespVO extends CombinationActivityBaseVO {
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
private String spuName;
@Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "开团人数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "开团人数不能为空")
private Integer userSize;
@Schema(description = "开团组数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "开团组数不能为空")
private Integer totalNum;
@Schema(description = "成团组数", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "成团组数不能为空")
private Integer successNum;
@Schema(description = "虚拟成团", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "虚拟成团不能为空")
private Integer virtualGroup;
@Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "活动状态不能为空")
private Integer status;
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductRespVO> products;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.activity;
import cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product.CombinationProductUpdateReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - 拼团活动更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationActivityUpdateReqVO extends CombinationActivityBaseVO {
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22901")
@NotNull(message = "活动编号不能为空")
private Long id;
@Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
private List<CombinationProductUpdateReqVO> products;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 拼团商品 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class CombinationProductBaseVO {
@Schema(description = "商品 spuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品 spuId 不能为空")
private Long spuId;
@Schema(description = "商品 skuId", requiredMode = Schema.RequiredMode.REQUIRED, example = "30563")
@NotNull(message = "商品 skuId 不能为空")
private Long skuId;
@Schema(description = "拼团价格,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "27682")
@NotNull(message = "拼团价格,单位分不能为空")
private Integer activePrice;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 拼团商品创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductCreateReqVO extends CombinationProductBaseVO {
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.time.LocalDateTime;
// TODO @puhui999可以考虑删除 excel 导出哈
/**
* 拼团商品 Excel VO
*
* @author HUIHUI
*/
@Data
public class CombinationProductExcelVO {
@ExcelProperty("编号")
private Long id;
@ExcelProperty("拼团活动编号")
private Long activityId;
@ExcelProperty("商品 SPU 编号")
private Long spuId;
@ExcelProperty("商品 SKU 编号")
private Long skuId;
@ExcelProperty("拼团商品状态")
private Integer activityStatus;
@ExcelProperty("活动开始时间点")
private LocalDateTime activityStartTime;
@ExcelProperty("活动结束时间点")
private LocalDateTime activityEndTime;
@ExcelProperty("拼团价格,单位分")
private Integer activePrice;
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @puhui999可以考虑删除 excel 导出哈
@Schema(description = "管理后台 - 拼团商品 Excel 导出 Request VO参数和 CombinationProductPageReqVO 是一致的")
@Data
public class CombinationProductExportReqVO {
@Schema(description = "拼团活动编号", example = "6829")
private Long activityId;
@Schema(description = "商品 SPU 编号", example = "18731")
private Long spuId;
@Schema(description = "商品 SKU 编号", example = "31675")
private Long skuId;
@Schema(description = "拼团商品状态", example = "2")
private Integer activityStatus;
@Schema(description = "活动开始时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityStartTime;
@Schema(description = "活动结束时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityEndTime;
@Schema(description = "拼团价格,单位分", example = "27682")
private Integer activePrice;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
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;
import org.springframework.format.annotation.DateTimeFormat;
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")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductPageReqVO extends PageParam {
@Schema(description = "拼团活动编号", example = "6829")
private Long activityId;
@Schema(description = "商品 SPU 编号", example = "18731")
private Long spuId;
@Schema(description = "商品 SKU 编号", example = "31675")
private Long skuId;
@Schema(description = "拼团商品状态", example = "2")
private Integer activityStatus;
@Schema(description = "活动开始时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityStartTime;
@Schema(description = "活动结束时间点")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] activityEndTime;
@Schema(description = "拼团价格,单位分", example = "27682")
private Integer activePrice;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 拼团商品 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductRespVO extends CombinationProductBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28322")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.promotion.controller.admin.combination.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 拼团商品更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CombinationProductUpdateReqVO extends CombinationProductBaseVO {
}

View File

@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.*;
import cn.iocoder.yudao.module.promotion.convert.seckill.seckillactivity.SeckillActivityConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.seckillactivity.SeckillActivityDO;
@ -18,6 +21,7 @@ import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -29,6 +33,8 @@ public class SeckillActivityController {
@Resource
private SeckillActivityService seckillActivityService;
@Resource
private ProductSpuApi spuApi;
@PostMapping("/create")
@Operation(summary = "创建秒杀活动")
@ -69,11 +75,8 @@ public class SeckillActivityController {
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<SeckillActivityDetailRespVO> getSeckillActivity(@RequestParam("id") Long id) {
SeckillActivityDO seckillActivity = seckillActivityService.getSeckillActivity(id);
if (seckillActivity == null) {
return success(null);
}
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity,seckillProducts));
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(id);
return success(SeckillActivityConvert.INSTANCE.convert(seckillActivity, seckillProducts));
}
@GetMapping("/list")
@ -90,7 +93,11 @@ public class SeckillActivityController {
@PreAuthorize("@ss.hasPermission('promotion:seckill-activity:query')")
public CommonResult<PageResult<SeckillActivityRespVO>> getSeckillActivityPage(@Valid SeckillActivityPageReqVO pageVO) {
PageResult<SeckillActivityDO> pageResult = seckillActivityService.getSeckillActivityPage(pageVO);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult));
Set<Long> aIds = CollectionUtils.convertSet(pageResult.getList(), SeckillActivityDO::getId);
List<SeckillProductDO> seckillProducts = seckillActivityService.getSeckillProductListByActivityId(aIds);
Set<Long> spuIds = CollectionUtils.convertSet(pageResult.getList(), SeckillActivityDO::getSpuId);
List<ProductSpuRespDTO> spuList = spuApi.getSpuList(spuIds);
return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, seckillProducts, spuList));
}
}

View File

@ -19,11 +19,6 @@ import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 秒杀时段相关接口
*
* @author HUIHUI
*/
@Tag(name = "管理后台 - 秒杀时段")
@RestController
@RequestMapping("/promotion/seckill-config")

View File

@ -20,10 +20,9 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@Data
public class SeckillActivityBaseVO {
// TODO @puhui999对应单 spuId
@Schema(description = "秒杀活动商品id", requiredMode = Schema.RequiredMode.REQUIRED, example = "[121,1212]")
@NotNull(message = "秒杀活动商品不能为空")
private List<Long> spuIds;
private Long spuId;
@Schema(description = "秒杀活动名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
@NotNull(message = "秒杀活动名称不能为空")
@ -56,8 +55,4 @@ public class SeckillActivityBaseVO {
@Schema(description = "单次限够数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "31683")
private Integer singleLimitCount;
// TODO @puhui999这个应该是计算出来的字段只返回create update 不用哈
@Schema(description = "秒杀总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer totalStock;
}

View File

@ -6,26 +6,46 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
/**
* 管理后台 - 秒杀活动 Response VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀活动 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SeckillActivityRespVO extends SeckillActivityBaseVO {
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "618大促")
private String spuName;
@Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.png")
private String picUrl;
@Schema(description = "秒杀活动id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "秒杀商品", requiredMode = Schema.RequiredMode.REQUIRED)
private List<SeckillProductRespVO> products; // TODO puhui: 考虑是否去除
private List<SeckillProductRespVO> products;
@Schema(description = "活动状态 开启0 禁用1", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "订单实付金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "22354")
private Integer totalPrice;
@Schema(description = "秒杀库存", example = "10")
private Integer stock;
@Schema(description = "秒杀总库存", example = "20")
private Integer totalStock;
@Schema(description = "新增订单数", example = "20")
private Integer orderCount;
@Schema(description = "付款人数", example = "20")
private Integer userCount;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -1,9 +1,13 @@
package cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;
import java.time.LocalTime;
import java.util.List;
/**
* 秒杀时段 Base VO提供给添加修改详细的子 VO 使用
@ -26,12 +30,24 @@ public class SeckillConfigBaseVO {
@NotNull(message = "结束时间点不能为空")
private String endTime;
@Schema(description = "秒杀", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
@NotNull(message = "秒杀图不能为空")
private String picUrl;
@Schema(description = "秒杀轮播", requiredMode = Schema.RequiredMode.REQUIRED, example = "[https://www.iocoder.cn/xx.png]")
@NotNull(message = "秒杀轮播图不能为空")
private List<String> sliderPicUrls;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "状态不能为空")
private Integer status;
@AssertTrue(message = "秒杀时段开始时间和结束时间不能相等")
@JsonIgnore
public boolean isValidStartTimeValid() {
return !LocalTime.parse(startTime).equals(LocalTime.parse(endTime));
}
@AssertTrue(message = "秒杀时段开始时间不能在结束时间之后")
@JsonIgnore
public boolean isValidEndTimeValid() {
return !LocalTime.parse(startTime).isAfter(LocalTime.parse(endTime));
}
}

View File

@ -6,12 +6,6 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
// TODO @puhuiVO 上不写注释已经有注解啦
/**
* 管理后台 - 秒杀时段分页 Request VO
*
* @author HUIHUI
*/
@Schema(description = "管理后台 - 秒杀时段分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)

Some files were not shown because too many files have changed in this diff Show More