# Conflicts:
#	yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceTest.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 2024-07-07 09:14:36 +08:00
commit 5d3bb791fd
13 changed files with 292 additions and 132 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -112,7 +112,7 @@
* 通用模块(必选):系统功能、基础设施
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
* 业务系统按需ERP 系统、CRM 系统、商城系统、微信公众号
* 业务系统按需ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
@ -248,6 +248,12 @@
演示地址:<https://doc.iocoder.cn/crm-preview/>
### AI 大模型
![功能图](/.image/common/ai-feature.png)
演示地址:<https://doc.iocoder.cn/ai-preview/>
## 🐨 技术栈
### 模块
@ -265,6 +271,7 @@
| `yudao-module-mall` | 商城系统的 Module 模块 |
| `yudao-module-erp` | ERP 系统的 Module 模块 |
| `yudao-module-crm` | CRM 系统的 Module 模块 |
| `yudao-module-ai` | AI 大模型的 Module 模块 |
| `yudao-module-mp` | 微信公众号的 Module 模块 |
| `yudao-module-report` | 大屏报表 Module 模块 |

View File

@ -34,20 +34,24 @@ public class CronUtils {
* @return 满足条件的执行时间
*/
public static List<LocalDateTime> getNextTimes(String cronExpression, int n) {
// 获得 CronExpression 对象
// 1. 获得 CronExpression 对象
CronExpression cron;
try {
cron = new CronExpression(cronExpression);
} catch (ParseException e) {
throw new IllegalArgumentException(e.getMessage());
}
// 从当前开始计算n 个满足条件的
// 2. 从当前开始计算n 个满足条件的
Date now = new Date();
List<LocalDateTime> nextTimes = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
Date nextTime = cron.getNextValidTimeAfter(now);
// 2.1 如果 nextTime null说明没有更多的有效时间退出循环
if (nextTime == null) {
break;
}
nextTimes.add(LocalDateTimeUtil.of(nextTime));
// 切换现在为下一个触发时间
// 2.2 切换现在为下一个触发时间
now = nextTime;
}
return nextTimes;

View File

@ -262,7 +262,9 @@ public class BpmModelServiceImpl implements BpmModelService {
}
private Model getModelByKey(String key) {
return repositoryService.createModelQuery().modelKey(key).singleResult();
return repositoryService.createModelQuery()
.modelTenantId(FlowableUtils.getTenantId())
.modelKey(key).singleResult();
}
@Override

View File

@ -79,7 +79,9 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
@Override
public ProcessDefinition getActiveProcessDefinition(String key) {
return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
return repositoryService.createProcessDefinitionQuery()
.processDefinitionTenantId(FlowableUtils.getTenantId())
.processDefinitionKey(key).active().singleResult();
}
@Override
@ -172,6 +174,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
@Override
public PageResult<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) {
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
query.processDefinitionTenantId(FlowableUtils.getTenantId());
if (StrUtil.isNotBlank(pageVO.getKey())) {
query.processDefinitionKey(pageVO.getKey());
}

View File

@ -30,7 +30,8 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
.likeIfPresent(ProductSpuDO::getName, reqVO.getName())
.eqIfPresent(ProductSpuDO::getCategoryId, reqVO.getCategoryId())
.betweenIfPresent(ProductSpuDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(ProductSpuDO::getSort);
.orderByDesc(ProductSpuDO::getSort)
.orderByDesc(ProductSpuDO::getId);
appendTabQuery(tabType, queryWrapper);
return selectPage(reqVO, queryWrapper);
}

View File

@ -1,33 +1,49 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderMapper;
import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderRefundStatusEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderConfig;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.cart.CartServiceImpl;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressServiceImpl;
import cn.iocoder.yudao.module.trade.service.message.TradeMessageServiceImpl;
import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
import cn.iocoder.yudao.module.trade.service.price.TradePriceServiceImpl;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.Duration;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@ -38,7 +54,9 @@ import static org.mockito.Mockito.when;
* @since 2022-09-07
*/
@Disabled // TODO 芋艿后续 fix 补充的单测
@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class})
@Import({TradeOrderUpdateServiceImpl.class, TradeOrderConfig.class, CartServiceImpl.class, TradePriceServiceImpl.class,
DeliveryExpressServiceImpl.class, TradeMessageServiceImpl.class
})
public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
@Resource
@ -55,6 +73,8 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
private ProductSpuApi productSpuApi;
@MockBean
private ProductSkuApi productSkuApi;
@MockBean
private ProductCommentApi productCommentApi;
// @MockBean
// private PriceApi priceApi;
@MockBean
@ -66,11 +86,22 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
@MockBean
private TradeOrderProperties tradeOrderProperties;
@MockBean
private TradeNoRedisDAO tradeNoRedisDAO;
@MockBean
private TradeOrderHandler tradeOrderHandler;
@MockBean
private TradePriceCalculator tradePriceCalculator;
@MockBean
private NotifyMessageSendApi notifyMessageSendApi;
@MockBean
private DeliveryExpressService deliveryExpressService;
@BeforeEach
public void setUp() {
when(tradeOrderProperties.getAppId()).thenReturn(888L);
when(tradeOrderProperties.getPayExpireTime()).thenReturn(Duration.ofDays(1));
when(tradeNoRedisDAO.generate(anyString())).thenReturn(IdUtil.randomUUID());
}
// @Test
@ -259,11 +290,18 @@ public class TradeOrderUpdateServiceTest extends BaseDbUnitTest {
TradeOrderDO order = randomPojo(TradeOrderDO.class, o -> {
o.setId(1L).setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus());
o.setLogisticsId(null).setLogisticsNo(null).setDeliveryTime(null);
o.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
o.setDeliveryType(DeliveryTypeEnum.EXPRESS.getType());
});
tradeOrderMapper.insert(order);
DeliveryExpressCreateReqVO expressCreateReqVO = new DeliveryExpressCreateReqVO();
expressCreateReqVO.setCode("code").setName("Name").setLogo("logo").setSort(0).setStatus(CommonStatusEnum.ENABLE.getStatus());
Long deliveryExpressId = deliveryExpressService.createDeliveryExpress(expressCreateReqVO);
// 准备参数
TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO().setId(1L)
.setLogisticsId(10L).setLogisticsNo("100");
.setLogisticsId(deliveryExpressId).setLogisticsNo("100");
// mock 方法支付单
// 调用

View File

@ -1,4 +1,5 @@
CREATE TABLE IF NOT EXISTS "trade_order" (
CREATE TABLE IF NOT EXISTS "trade_order"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"no" varchar NOT NULL,
"type" int NOT NULL,
@ -10,16 +11,19 @@ CREATE TABLE IF NOT EXISTS "trade_order" (
"product_count" int NOT NULL,
"cancel_type" int,
"remark" varchar,
"comment_status" boolean,
"brokerage_user_id" bigint,
"pay_status" bit NOT NULL,
"pay_time" datetime,
"finish_time" datetime,
"cancel_time" datetime,
"original_price" int NOT NULL,
"order_price" int NOT NULL,
"total_price" int NULL,
"order_price" int NULL,
"discount_price" int NOT NULL,
"delivery_price" int NOT NULL,
"adjust_price" int NOT NULL,
"pay_price" int NOT NULL,
"delivery_type" int NOT NULL,
"pay_order_id" bigint,
"pay_channel_code" varchar,
"delivery_template_id" bigint,
@ -32,11 +36,24 @@ CREATE TABLE IF NOT EXISTS "trade_order" (
"receiver_area_id" int NOT NULL,
"receiver_post_code" int,
"receiver_detail_address" varchar NOT NULL,
"after_sale_status" int NOT NULL,
"refund_price" int NOT NULL,
"pick_up_store_id" long NULL,
"pick_up_verify_code" varchar NULL,
"refund_status" int NULL,
"refund_price" int NULL,
"after_sale_status" int NULL,
"coupon_id" bigint NOT NULL,
"coupon_price" int NOT NULL,
"use_point" int NULL,
"point_price" int NOT NULL,
"give_point" int NULL,
"refund_point" int NULL,
"vip_price" int NULL,
"seckill_activity_id" long NULL,
"bargain_activity_id" long NULL,
"bargain_record_id" long NULL,
"combination_activity_id" long NULL,
"combination_head_id" long NULL,
"combination_record_id" long NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
@ -45,22 +62,30 @@ CREATE TABLE IF NOT EXISTS "trade_order" (
PRIMARY KEY ("id")
) COMMENT '交易订单表';
CREATE TABLE IF NOT EXISTS "trade_order_item" (
CREATE TABLE IF NOT EXISTS "trade_order_item"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"order_id" bigint NOT NULL,
"cart_id" int NULL,
"spu_id" bigint NOT NULL,
"spu_name" varchar NOT NULL,
"sku_id" bigint NOT NULL,
"properties" varchar,
"pic_url" varchar,
"count" int NOT NULL,
"original_price" int NOT NULL,
"original_unit_price" int NOT NULL,
"comment_status" boolean NULL,
"price" int NOT NULL,
"discount_price" int NOT NULL,
"delivery_price" int NULL,
"adjust_price" int NULL,
"pay_price" int NOT NULL,
"order_part_price" int NOT NULL,
"order_divide_price" int NOT NULL,
"coupon_price" int NULL,
"point_price" int NULL,
"use_point" int NULL,
"give_point" int NULL,
"vip_price" int NULL,
"after_sale_id" long NULL,
"after_sale_status" int NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
@ -70,7 +95,8 @@ CREATE TABLE IF NOT EXISTS "trade_order_item" (
PRIMARY KEY ("id")
) COMMENT '交易订单明细表';
CREATE TABLE IF NOT EXISTS "trade_after_sale" (
CREATE TABLE IF NOT EXISTS "trade_after_sale"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"no" varchar NOT NULL,
"status" int NOT NULL,
@ -108,7 +134,8 @@ CREATE TABLE IF NOT EXISTS "trade_after_sale" (
PRIMARY KEY ("id")
) COMMENT '交易售后表';
CREATE TABLE IF NOT EXISTS "trade_after_sale_log" (
CREATE TABLE IF NOT EXISTS "trade_after_sale_log"
(
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"user_id" bigint NOT NULL,
"user_type" int NOT NULL,
@ -189,3 +216,19 @@ CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw"
"tenant_id" bigint not null default '0',
PRIMARY KEY ("id")
) COMMENT '佣金提现';
CREATE TABLE IF NOT EXISTS "trade_delivery_express"
(
"id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"code" varchar NULL,
"name" varchar,
"logo" varchar NULL,
"sort" int NOT NULL,
"status" int NOT NULL,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '佣金提现';

View File

@ -88,6 +88,12 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp, getTerminal());
Assert.notNull(user, "获取用户失败,结果为空");
// 校验是否禁用
if (CommonStatusEnum.isDisable(user.getStatus())) {
createLoginLog(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, LoginResultEnum.USER_DISABLED);
throw exception(AUTH_LOGIN_USER_DISABLED);
}
// 如果 socialType 非空说明需要绑定社交用户
String openid = null;
if (reqVO.getSocialType() != null) {
@ -177,7 +183,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
// 校验是否禁用
if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
if (CommonStatusEnum.isDisable(user.getStatus())) {
createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);
throw exception(AUTH_LOGIN_USER_DISABLED);
}

View File

@ -109,7 +109,7 @@ public class AuthController {
// 1.3 获得菜单列表
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
List<MenuDO> menuList = menuService.getMenuList(menuIds);
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
menuList = menuService.filterDisableMenus(menuList);
// 2. 拼接结果返回
return success(AuthConvert.INSTANCE.convert(user, roles, menuList));

View File

@ -72,6 +72,7 @@ public class MenuController {
public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() {
List<MenuDO> list = menuService.getMenuListByTenant(
new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));
list = menuService.filterDisableMenus(list);
list.sort(Comparator.comparing(MenuDO::getSort));
return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));
}

View File

@ -52,6 +52,14 @@ public interface MenuService {
*/
List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO);
/**
* 过滤掉关闭的菜单及其子菜单
*
* @param list 菜单列表
* @return 过滤后的菜单列表
*/
List<MenuDO> filterDisableMenus(List<MenuDO> list);
/**
* 筛选菜单列表
*

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.system.service.permission;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
@ -11,6 +13,7 @@ import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@ -18,12 +21,11 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
@ -106,12 +108,57 @@ public class MenuServiceImpl implements MenuService {
@Override
public List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO) {
// 查询所有菜单并过滤掉关闭的节点
List<MenuDO> menus = getMenuList(reqVO);
// 开启多租户的情况下需要过滤掉未开通的菜单
tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));
return menus;
}
@Override
public List<MenuDO> filterDisableMenus(List<MenuDO> menuList) {
if (CollUtil.isEmpty(menuList)){
return Collections.emptyList();
}
Map<Long, MenuDO> menuMap = convertMap(menuList, MenuDO::getId);
// 遍历 menu 菜单查找不是禁用的菜单添加到 enabledMenus 结果
List<MenuDO> enabledMenus = new ArrayList<>();
Set<Long> disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单防止重复的搜索
for (MenuDO menu : menuList) {
if (isMenuDisabled(menu, menuMap, disabledMenuCache)) {
continue;
}
enabledMenus.add(menu);
}
return enabledMenus;
}
private boolean isMenuDisabled(MenuDO node, Map<Long, MenuDO> menuMap, Set<Long> disabledMenuCache) {
// 如果已经判定是禁用的节点直接结束
if (disabledMenuCache.contains(node.getId())) {
return true;
}
// 1. 遍历到 parentId 为根节点则无需判断
Long parentId = node.getParentId();
if (ObjUtil.equal(parentId, ID_ROOT)) {
if (CommonStatusEnum.isDisable(node.getStatus())) {
disabledMenuCache.add(node.getId());
return true;
}
return false;
}
// 2. 继续遍历 parent 节点
MenuDO parent = menuMap.get(parentId);
if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) {
disabledMenuCache.add(node.getId());
return true;
}
return false;
}
@Override
public List<MenuDO> getMenuList(MenuListReqVO reqVO) {
return menuMapper.selectList(reqVO);