Merge branch 'feature/mall_product' of https://gitee.com/CrazyWorld/ruoyi-vue-pro into feature/mall_product

# Conflicts:
#	yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
This commit is contained in:
YunaiV 2023-10-01 22:00:31 +08:00
commit 057952bdeb
89 changed files with 1651 additions and 570 deletions

11
sql/mysql/member.sql Normal file
View File

@ -0,0 +1,11 @@
-- 查询上级菜单排序
SELECT parent_id, sort
INTO @parentId, @sort
FROM system_menu
WHERE name = '用户等级修改'
LIMIT 1;
-- 新增 按钮权限
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('用户积分修改', 'member:user:update-point', 3, @sort + 1, @parentId, '', '', '', 0);
INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
VALUES ('用户余额修改', 'member:user:update-balance', 3, @sort + 2, @parentId, '', '', '', 0);

View File

@ -805,7 +805,7 @@ CREATE TABLE `member_level_record` (
`user_id` bigint NOT NULL DEFAULT 0 COMMENT '用户编号',
`level_id` bigint NOT NULL DEFAULT 0 COMMENT '等级编号',
`level` int NOT NULL DEFAULT 0 COMMENT '会员等级',
`discount_percent` tinyint NOT NULL DEFAULT 100 COMMENT '享受折扣',
`discount_percent` int NOT NULL DEFAULT 100 COMMENT '享受折扣',
`experience` int NOT NULL DEFAULT 0 COMMENT '升级经验',
`user_experience` int NOT NULL DEFAULT 0 COMMENT '会员此时的经验',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '备注',
@ -827,15 +827,15 @@ BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for member_point_config
-- Table structure for member_config
-- ----------------------------
DROP TABLE IF EXISTS `member_point_config`;
CREATE TABLE `member_point_config` (
DROP TABLE IF EXISTS `member_config`;
CREATE TABLE `member_config` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`trade_deduct_enable` bit(1) NOT NULL COMMENT '是否开启积分抵扣',
`trade_deduct_unit_price` int NOT NULL COMMENT '积分抵扣(单位)',
`trade_deduct_max_price` int NULL DEFAULT NULL COMMENT '积分抵扣最大值',
`trade_give_point` bigint NULL DEFAULT NULL COMMENT '1 元赠送多少分',
`point_trade_deduct_enable` bit(1) NOT NULL COMMENT '是否开启积分抵扣',
`point_trade_deduct_unit_price` int NOT NULL COMMENT '积分抵扣(单位)',
`point_trade_deduct_max_price` int NULL DEFAULT NULL COMMENT '积分抵扣最大值',
`point_trade_give_point` bigint NULL DEFAULT NULL COMMENT '1 元赠送多少分',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@ -843,13 +843,13 @@ CREATE TABLE `member_point_config` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员积分配置表';
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '会员配置表';
-- ----------------------------
-- Records of member_point_config
-- Records of member_config
-- ----------------------------
BEGIN;
INSERT INTO `member_point_config` (`id`, `trade_deduct_enable`, `trade_deduct_unit_price`, `trade_deduct_max_price`, `trade_give_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, b'1', 100, 2, 3, '1', '2023-08-20 09:54:42', '1', '2023-08-20 09:54:42', b'0', 1);
INSERT INTO `member_config` (`id`, `point_trade_deduct_enable`, `point_trade_deduct_unit_price`, `point_trade_deduct_max_price`, `point_trade_give_point`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, b'1', 100, 2, 3, '1', '2023-08-20 09:54:42', '1', '2023-08-20 09:54:42', b'0', 1);
COMMIT;
-- ----------------------------
@ -915,7 +915,8 @@ DROP TABLE IF EXISTS `member_sign_in_config`;
CREATE TABLE `member_sign_in_config` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
`day` int NOT NULL COMMENT '第几天',
`point` int NOT NULL COMMENT '奖励积分',
`point` int NOT NULL DEFAULT 0 COMMENT '奖励积分',
`experience` int NOT NULL DEFAULT 0 COMMENT '奖励经验',
`status` tinyint NOT NULL COMMENT '状态',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@ -930,18 +931,18 @@ CREATE TABLE `member_sign_in_config` (
-- Records of member_sign_in_config
-- ----------------------------
BEGIN;
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 10, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 20, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 7, 1001, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 6, 12121, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 2, 12, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 1, 10, 0, '1', '2023-08-20 19:20:42', '1', '2023-08-20 19:20:56', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 7, 22, 0, '1', '2023-08-20 19:20:48', '1', '2023-08-20 19:20:48', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 2, 3, 0, '1', '2023-08-21 20:22:44', '1', '2023-08-21 20:22:44', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 3, 4, 0, '1', '2023-08-21 20:22:48', '1', '2023-08-21 20:22:48', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 4, 5, 0, '1', '2023-08-21 20:22:51', '1', '2023-08-21 20:22:51', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 5, 6, 0, '1', '2023-08-21 20:22:56', '1', '2023-08-21 20:22:56', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 6, 7, 0, '1', '2023-08-21 20:22:59', '1', '2023-08-21 20:22:59', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 1, 10, 10, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (2, 2, 20, 20, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (3, 7, 1001, 1001, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (4, 6, 12121, 12121, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (5, 3, 12, 12, 0, '', '2023-08-20 01:38:56', '', '2023-08-20 01:38:56', b'0', 0);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (6, 1, 10, 10, 0, '1', '2023-08-20 19:20:42', '1', '2023-08-20 19:20:56', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (7, 7, 22, 22, 0, '1', '2023-08-20 19:20:48', '1', '2023-08-20 19:20:48', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (8, 2, 3, 3, 0, '1', '2023-08-21 20:22:44', '1', '2023-08-21 20:22:44', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (9, 3, 4, 4, 0, '1', '2023-08-21 20:22:48', '1', '2023-08-21 20:22:48', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (10, 4, 5, 5, 0, '1', '2023-08-21 20:22:51', '1', '2023-08-21 20:22:51', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (11, 5, 6, 6, 0, '1', '2023-08-21 20:22:56', '1', '2023-08-21 20:22:56', b'0', 1);
INSERT INTO `member_sign_in_config` (`id`, `day`, `point`, `experience`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (12, 6, 7, 7, 0, '1', '2023-08-21 20:22:59', '1', '2023-08-21 20:22:59', b'0', 1);
COMMIT;
-- ----------------------------
@ -952,7 +953,8 @@ CREATE TABLE `member_sign_in_record` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '签到自增id',
`user_id` int NULL DEFAULT NULL COMMENT '签到用户',
`day` int NULL DEFAULT NULL COMMENT '第几天签到',
`point` int NULL DEFAULT NULL COMMENT '签到的分数',
`point` int NOT NULL DEFAULT 0 COMMENT '签到的积分',
`experience` int NOT NULL DEFAULT 0 COMMENT '签到的经验',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@ -962,6 +964,8 @@ CREATE TABLE `member_sign_in_record` (
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '签到记录';
CREATE INDEX user_id_index ON member_sign_in_record (user_id);
-- ----------------------------
-- Records of member_sign_in_record
-- ----------------------------
@ -2013,20 +2017,19 @@ INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_i
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2184, '自提门店导出', 'trade:delivery:pick-up-store:export', 3, 5, 2179, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-05-25 10:53:29', '', '2023-05-25 10:53:29', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2209, '秒杀活动', '', 2, 3, 2030, 'seckill', 'ep:place', '', '', 0, b'1', b'1', b'1', '1', '2023-06-24 17:39:13', '1', '2023-06-24 18:55:15', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2262, '会员中心', '', 1, 55, 0, '/member', 'ep:bicycle', NULL, NULL, 0, b'1', b'1', b'1', '1', '2023-06-10 00:42:03', '1', '2023-08-20 09:23:56', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2275, '积分配置', '', 2, 0, 2299, 'config', 'fa:archive', 'member/point/config/index', 'PointConfig', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-08-20 12:01:20', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '积分设置查询', 'point:config:query', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '积分设置创建', 'point:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2275, '会员配置', '', 2, 0, 2262, 'config', 'fa:archive', 'member/config/index', 'MemberConfig', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-08-20 12:01:20', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2276, '会员配置查询', 'member:config:query', 3, 1, 2275, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '', '2023-06-10 02:07:44', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2277, '会员配置创建', 'member:config:save', 3, 2, 2275, '', '', '', '', 0, b'1', b'1', b'1', '', '2023-06-10 02:07:44', '1', '2023-06-27 20:32:31', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2281, '签到配置', '', 2, 2, 2300, 'config', 'ep:calendar', 'member/signin/config/index', 'SignInConfig', 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '1', '2023-08-20 19:25:51', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2282, '积分签到规则查询', 'point:sign-in-config:query', 3, 1, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2283, '积分签到规则创建', 'point:sign-in-config:create', 3, 2, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2284, '积分签到规则更新', 'point:sign-in-config:update', 3, 3, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2285, '积分签到规则删除', 'point:sign-in-config:delete', 3, 4, 2281, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 03:26:12', '', '2023-06-10 03:26:12', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2287, '积分记录', '', 2, 1, 2299, 'record', 'fa:asterisk', 'member/point/record/index', 'PointRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-08-20 12:01:42', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '用户积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2287, '会员积分', '', 2, 1, 2262, 'point', 'ep:pointer', 'member/point/record/index', 'PointRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '1', '2023-08-20 12:01:42', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2288, '会员积分记录查询', 'point:record:query', 3, 1, 2287, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:18:50', '', '2023-06-10 04:18:50', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2293, '签到记录', '', 2, 3, 2300, 'record', 'ep:chicken', 'member/signin/record/index', 'SignInRecord', 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '1', '2023-08-20 19:26:02', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2294, '用户签到积分查询', 'point:sign-in-record:query', 3, 1, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2297, '用户签到积分删除', 'point:sign-in-record:delete', 3, 4, 2293, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-06-10 04:48:22', '', '2023-06-10 04:48:22', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2299, '会员积分', '', 1, 10, 2262, 'point', 'ep:pointer', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:48:51', '1', '2023-08-20 09:23:35', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2300, '会员签到', '', 1, 11, 2262, 'signin', 'ep:alarm-clock', '', '', 0, b'1', b'1', b'1', '1', '2023-06-27 22:49:53', '1', '2023-08-20 09:23:48', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2301, '回调通知', '', 2, 4, 1117, 'notify', 'example', 'pay/notify/index', 'PayNotify', 0, b'1', b'1', b'1', '', '2023-07-20 04:41:32', '1', '2023-07-20 13:45:08', b'0');
INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2302, '支付通知查询', 'pay:notify:query', 3, 1, 2301, '', '', '', NULL, 0, b'1', b'1', b'1', '', '2023-07-20 04:41:32', '', '2023-07-20 04:41:32', b'0');

View File

@ -0,0 +1,3 @@
-- 增加字段
ALTER TABLE trade_order
ADD COLUMN brokerage_user_id bigint NULL COMMENT '推广人编号' AFTER comment_status;

View File

@ -6,6 +6,7 @@ import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
/**
* 时间工具类用于 {@link java.time.LocalDateTime}
@ -137,4 +138,29 @@ public class LocalDateTimeUtils {
}
/**
* 获取指定日期所在的月份的开始时间
* 例如2023-09-30 00:00:00,000
*
* @param date 日期
* @return 月份的开始时间
*/
public static LocalDateTime beginOfMonth(LocalDateTime date) {
return date
.with(TemporalAdjusters.firstDayOfMonth())
.with(LocalTime.MIN);
}
/**
* 获取指定日期所在的月份的最后时间
* 例如2023-09-30 23:59:59,999
*
* @param date 日期
* @return 月份的结束时间
*/
public static LocalDateTime endOfMonth(LocalDateTime date) {
return date
.with(TemporalAdjusters.lastDayOfMonth())
.with(LocalTime.MAX);
}
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.product.api.category;
import java.util.Collection;
/**
* 商品分类 API 接口
*
* @author owen
*/
public interface ProductCategoryApi {
/**
* 校验商品分类是否有效如下情况视为无效
* 1. 商品分类编号不存在
* 2. 商品分类被禁用
*
* @param ids 商品分类编号数组
*/
void validateCategoryList(Collection<Long> ids);
}

View File

@ -21,6 +21,14 @@ public interface ProductSpuApi {
*/
List<ProductSpuRespDTO> getSpuList(Collection<Long> ids);
/**
* 批量查询 SPU 数组并且校验是否 SPU 是否有效
*
* @param ids SPU 编号列表
* @return SPU 数组
*/
List<ProductSpuRespDTO> getSpuListAndValidate(Collection<Long> ids);
/**
* 获得 SPU
*
@ -28,4 +36,12 @@ public interface ProductSpuApi {
*/
ProductSpuRespDTO getSpu(Long id);
/**
* 校验商品是否有效如下情况视为无效
* 1. 商品编号不存在
* 2. 商品被禁用
*
* @param ids 商品编号数组
*/
void validateSpuList(Collection<Long> ids);
}

View File

@ -34,7 +34,7 @@ public interface ErrorCodeConstants {
// ========== 商品 SPU 1-008-005-000 ==========
ErrorCode SPU_NOT_EXISTS = new ErrorCode(1_008_005_000, "商品 SPU 不存在");
ErrorCode SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR = new ErrorCode(1_008_005_001, "商品分类不正确,原因:必须使用第二级的商品分类及以下");
ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU 不处于上架状态");
ErrorCode SPU_NOT_ENABLE = new ErrorCode(1_008_005_002, "商品 SPU【[]】不处于上架状态");
ErrorCode SPU_NOT_RECYCLE = new ErrorCode(1_008_005_003, "商品 SPU 不处于回收站状态");
// ========== 商品 SKU 1-008-006-000 ==========

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.product.api.category;
import cn.iocoder.yudao.module.product.service.category.ProductCategoryService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
/**
* 商品分类 API 接口实现类
*
* @author owen
*/
@Service
@Validated
public class ProductCategoryApiImpl implements ProductCategoryApi {
@Resource
private ProductCategoryService productCategoryService;
@Override
public void validateCategoryList(Collection<Long> ids) {
productCategoryService.validateCategoryList(ids);
}
}

View File

@ -3,9 +3,6 @@ package cn.iocoder.yudao.module.product.api.spu;
import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -36,9 +33,19 @@ public class ProductSpuApiImpl implements ProductSpuApi {
return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids));
}
@Override
public List<ProductSpuRespDTO> getSpuListAndValidate(Collection<Long> ids) {
return ProductSpuConvert.INSTANCE.convertList2(spuService.validateSpuList(ids));
}
@Override
public ProductSpuRespDTO getSpu(Long id) {
return ProductSpuConvert.INSTANCE.convert02(spuService.getSpu(id));
}
@Override
public void validateSpuList(Collection<Long> ids) {
spuService.validateSpuList(ids);
}
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCateg
import cn.iocoder.yudao.module.product.dal.dataobject.category.ProductCategoryDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
@ -75,4 +76,12 @@ public interface ProductCategoryService {
*/
List<ProductCategoryDO> getEnableCategoryList();
/**
* 校验商品分类是否有效如下情况视为无效
* 1. 商品分类编号不存在
* 2. 商品分类被禁用
*
* @param ids 商品分类编号数组
*/
void validateCategoryList(Collection<Long> ids);
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.product.service.category;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;
import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryListReqVO;
import cn.iocoder.yudao.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;
@ -13,7 +15,9 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -99,6 +103,26 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
}
}
@Override
public void validateCategoryList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return;
}
// 获得商品分类信息
List<ProductCategoryDO> categoryList = productCategoryMapper.selectBatchIds(ids);
Map<Long, ProductCategoryDO> categoryMap = CollectionUtils.convertMap(categoryList, ProductCategoryDO::getId);
// 校验
ids.forEach(id -> {
ProductCategoryDO category = categoryMap.get(id);
if (category == null) {
throw exception(CATEGORY_NOT_EXISTS);
}
if (!CommonStatusEnum.ENABLE.getStatus().equals(category.getStatus())) {
throw exception(CATEGORY_DISABLED, category.getName());
}
});
}
@Override
public ProductCategoryDO getCategory(Long id) {
return productCategoryMapper.selectById(id);

View File

@ -136,4 +136,14 @@ public interface ProductSpuService {
*/
Long getSpuCountByCategoryId(Long categoryId);
/**
* 校验商品是否有效如下情况视为无效
* 1. 商品编号不存在
* 2. 商品被禁用
*
* @param ids 商品编号数组
* @return 商品 SPU 列表
*/
List<ProductSpuDO> validateSpuList(Collection<Long> ids);
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.product.service.spu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
@ -139,6 +140,28 @@ public class ProductSpuServiceImpl implements ProductSpuService {
}
}
@Override
public List<ProductSpuDO> validateSpuList(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
// 获得商品信息
List<ProductSpuDO> spuList = productSpuMapper.selectBatchIds(ids);
Map<Long, ProductSpuDO> spuMap = CollectionUtils.convertMap(spuList, ProductSpuDO::getId);
// 校验
ids.forEach(id -> {
ProductSpuDO spu = spuMap.get(id);
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
throw exception(SPU_NOT_ENABLE, spu.getName());
}
});
return spuList;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSpu(Long id) {

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.promotion.controller.app.coupon;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@ -25,11 +24,9 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
@Tag(name = "用户 App - 优惠劵模板")
@ -46,7 +43,6 @@ public class AppCouponTemplateController {
@Resource
private ProductSpuApi productSpuApi;
// TODO 疯狂这里应该还有个 list 接口哈获得优惠劵模版列表用于首页商品页的优惠劵
@GetMapping("/list")
@Operation(summary = "获得优惠劵模版列表")
@Parameters({
@ -56,46 +52,28 @@ public class AppCouponTemplateController {
})
public CommonResult<List<AppCouponTemplateRespVO>> getCouponTemplateList(
@RequestParam(value = "spuId", required = false) Long spuId,
@RequestParam(value = "useType", required = false) Integer useType,
@RequestParam(value = "productScope", required = false) Integer productScope,
@RequestParam(value = "count", required = false, defaultValue = "10") Integer count) {
List<AppCouponTemplateRespVO> list = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 10; i++) {
AppCouponTemplateRespVO vo = new AppCouponTemplateRespVO();
vo.setId(i + 1L);
vo.setName("优惠劵" + (i + 1));
vo.setTakeLimitCount(random.nextInt(10) + 1);
vo.setUsePrice(random.nextInt(100) * 100);
vo.setValidityType(random.nextInt(2) + 1);
if (vo.getValidityType() == 1) {
vo.setValidStartTime(LocalDateTime.now().plusDays(random.nextInt(10)));
vo.setValidEndTime(LocalDateTime.now().plusDays(random.nextInt(20) + 10));
} else {
vo.setFixedStartTerm(random.nextInt(10));
vo.setFixedEndTerm(random.nextInt(10) + vo.getFixedStartTerm() + 1);
}
vo.setDiscountType(random.nextInt(2) + 1);
if (vo.getDiscountType() == 1) {
vo.setDiscountPercent(null);
vo.setDiscountPrice(random.nextInt(50) * 100);
vo.setDiscountLimitPrice(null);
} else {
vo.setDiscountPercent(random.nextInt(90) + 10);
vo.setDiscountPrice(null);
vo.setDiscountLimitPrice(random.nextInt(200) * 100);
}
// TODO @疯狂是否已领取要不在 TemplateService 搞个 static 方法让它基于 countMap 这种去计算这样好点
vo.setTakeStatus(random.nextBoolean());
list.add(vo);
}
return success(list);
// 1.1 处理查询条件商品范围编号
Long productScopeValue = getProductScopeValue(productScope, spuId);
// 1.2 处理查询条件领取方式 = 直接领取
List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
// 2. 查询
List<CouponTemplateDO> list = couponTemplateService.getCouponTemplateList(canTakeTypes, productScope,
productScopeValue, count);
// 3.1 领取数量
Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), list);
// 3.2 拼接返回
return success(CouponTemplateConvert.INSTANCE.convertAppList(list, canCanTakeMap));
}
@GetMapping("/page")
@Operation(summary = "获得优惠劵模版分页")
public CommonResult<PageResult<AppCouponTemplateRespVO>> getCouponTemplatePage(AppCouponTemplatePageReqVO pageReqVO) {
// 1.1 处理查询条件商品范围编号
Long productScopeValue = getProductScopeValue(pageReqVO);
Long productScopeValue = getProductScopeValue(pageReqVO.getProductScope(), pageReqVO.getSpuId());
// 1.2 处理查询条件领取方式 = 直接领取
List<Integer> canTakeTypes = Collections.singletonList(CouponTakeTypeEnum.USER.getValue());
@ -104,35 +82,30 @@ public class AppCouponTemplateController {
CouponTemplateConvert.INSTANCE.convert(pageReqVO, canTakeTypes, pageReqVO.getProductScope(), productScopeValue));
// 3.1 领取数量
Map<Long, Integer> couponTakeCountMap = new HashMap<>(0);
Long userId = getLoginUserId();
if (userId != null) {
List<Long> templateIds = convertList(pageResult.getList(), CouponTemplateDO::getId,
t -> ObjUtil.notEqual(t.getTakeLimitCount(), -1)); // 只查有设置每人限领个数
couponTakeCountMap = couponService.getTakeCountMapByTemplateIds(templateIds, userId);
}
Map<Long, Boolean> canCanTakeMap = couponService.getUserCanCanTakeMap(getLoginUserId(), pageResult.getList());
// 3.2 拼接返回
return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, couponTakeCountMap));
return success(CouponTemplateConvert.INSTANCE.convertAppPage(pageResult, canCanTakeMap));
}
/**
* 获得分页查询的商品范围
* 获得商品的使用范围编号
*
* @param pageReqVO 分页查询
* @return 商品范围
* @param productScope 商品范围
* @param spuId 商品 SPU 编号
* @return 商品范围编号
*/
private Long getProductScopeValue(AppCouponTemplatePageReqVO pageReqVO) {
// 通用券清除商品范围
if (pageReqVO.getProductScope() == null || ObjectUtils.equalsAny(pageReqVO.getProductScope(), PromotionProductScopeEnum.ALL.getScope(), null)) {
private Long getProductScopeValue(Integer productScope, Long spuId) {
// 通用券没有商品范围
if (productScope == null || ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) {
return null;
}
// 品类券查询商品的品类
if (Objects.equals(pageReqVO.getProductScope(), PromotionProductScopeEnum.CATEGORY.getScope()) && pageReqVO.getSpuId() != null) {
return Optional.ofNullable(productSpuApi.getSpu(pageReqVO.getSpuId()))
// 品类券查询商品的品类编号
if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) {
return Optional.ofNullable(productSpuApi.getSpu(spuId))
.map(ProductSpuRespDTO::getCategoryId).orElse(null);
}
// 商品卷直接返回
return pageReqVO.getSpuId();
return spuId;
}
}

View File

@ -55,7 +55,7 @@ public class AppCouponTemplateRespVO {
// ========== 用户相关字段 ==========
@Schema(description = "是否领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean takeStatus;
@Schema(description = "是否可以领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean canTake;
}

View File

@ -37,21 +37,25 @@ public interface CouponTemplateConvert {
PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult);
default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Integer> couponTakeCountMap) {
List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list);
default PageResult<AppCouponTemplateRespVO> convertAppPage(PageResult<CouponTemplateDO> pageResult, Map<Long, Boolean> userCanTakeMap) {
PageResult<AppCouponTemplateRespVO> result = convertAppPage(pageResult);
if (MapUtil.isEmpty(couponTakeCountMap)) {
return result;
}
for (AppCouponTemplateRespVO template : result.getList()) {
// 每人领取数量无限制
if (template.getTakeLimitCount() == -1) {
template.setTakeStatus(false);
continue;
}
// 检查已领取数量是否超过限领数量
template.setTakeStatus(MapUtil.getInt(couponTakeCountMap, template.getId(), 0) >= template.getTakeLimitCount());
}
copyTo(result.getList(), userCanTakeMap);
return result;
}
default List<AppCouponTemplateRespVO> convertAppList(List<CouponTemplateDO> list, Map<Long, Boolean> userCanTakeMap) {
List<AppCouponTemplateRespVO> result = convertAppList(list);
copyTo(result, userCanTakeMap);
return result;
}
default void copyTo(List<AppCouponTemplateRespVO> list, Map<Long, Boolean> userCanTakeMap) {
for (AppCouponTemplateRespVO template : list) {
// 检查已领取数量是否超过限领数量
template.setCanTake(MapUtil.getBool(userCanTakeMap, template.getId(), false));
}
}
}

View File

@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yulichang.toolkit.MPJWrappers;
import org.apache.ibatis.annotations.Mapper;
@ -16,9 +15,12 @@ import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 优惠劵 Mapper
*
@ -70,14 +72,16 @@ public interface CouponMapper extends BaseMapperX<CouponDO> {
);
}
// TODO @疯狂这个是不是搞个 Map 就可以呀
default List<CouponTakeCountBO> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
return BeanUtil.copyToList(selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
.select(CouponDO::getTemplateId)
.selectCount(CouponDO::getId, CouponTakeCountBO::getCount)
default Map<Long, Integer> selectCountByUserIdAndTemplateIdIn(Long userId, Collection<Long> templateIds) {
String templateIdAlias = "templateId";
String countAlias = "count";
List<Map<String, Object>> list = selectMaps(MPJWrappers.lambdaJoin(CouponDO.class)
.selectAs(CouponDO::getTemplateId, templateIdAlias)
.selectCount(CouponDO::getId, countAlias)
.eq(CouponDO::getUserId, userId)
.in(CouponDO::getTemplateId, templateIds)
.groupBy(CouponDO::getTemplateId)), CouponTakeCountBO.class);
.groupBy(CouponDO::getTemplateId));
return convertMap(list, map -> MapUtil.getLong(map, templateIdAlias), map -> MapUtil.getInt(map, countAlias));
}
default List<CouponDO> selectListByUserIdAndStatusAndUsePriceLeAndProductScope(

View File

@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Consumer;
/**
@ -23,16 +24,8 @@ import java.util.function.Consumer;
public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
default PageResult<CouponTemplateDO> selectPage(CouponTemplatePageReqVO reqVO) {
// 构建可领取的查询条件, 好啰嗦 ( -_-)
Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
if (CollUtil.isNotEmpty(reqVO.getCanTakeTypes())) {
canTakeConsumer = w ->
w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
.in(CouponTemplateDO::getTakeType, reqVO.getCanTakeTypes()) // 2. 领取方式一致
.and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
.or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
.apply(" take_count < total_count "); // 4. 剩余数量大于 0
}
// 构建可领取的查询条件
Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(reqVO.getCanTakeTypes());
// 执行分页查询
return selectPage(reqVO, new LambdaQueryWrapperX<CouponTemplateDO>()
.likeIfPresent(CouponTemplateDO::getName, reqVO.getName())
@ -48,4 +41,33 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
void updateTakeCount(@Param("id") Long id, @Param("incrCount") Integer incrCount);
default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
return selectList(CouponTemplateDO::getTakeType, takeType);
}
default List<CouponTemplateDO> selectList(List<Integer> canTakeTypes, Integer productScope, Long productScopeValue, Integer count) {
// 构建可领取的查询条件
Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = buildCanTakeQueryConsumer(canTakeTypes);
return selectList(new LambdaQueryWrapperX<CouponTemplateDO>()
.eqIfPresent(CouponTemplateDO::getProductScope, productScope)
.and(productScopeValue != null, w -> w.apply("FIND_IN_SET({0}, product_scope_values)",
productScopeValue))
.and(canTakeConsumer != null, canTakeConsumer)
.last(" LIMIT " + count)
.orderByDesc(CouponTemplateDO::getId));
}
static Consumer<LambdaQueryWrapper<CouponTemplateDO>> buildCanTakeQueryConsumer(List<Integer> canTakeTypes) {
Consumer<LambdaQueryWrapper<CouponTemplateDO>> canTakeConsumer = null;
if (CollUtil.isNotEmpty(canTakeTypes)) {
canTakeConsumer = w ->
w.eq(CouponTemplateDO::getStatus, CommonStatusEnum.ENABLE.getStatus()) // 1. 状态为可用的
.in(CouponTemplateDO::getTakeType, canTakeTypes) // 2. 领取方式一致
.and(ww -> ww.isNull(CouponTemplateDO::getValidEndTime) // 3. 未过期
.or().gt(CouponTemplateDO::getValidEndTime, LocalDateTime.now()))
.apply(" take_count < total_count "); // 4. 剩余数量大于 0
}
return canTakeConsumer;
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.promotion.mq.consumer.coupon;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;
import cn.iocoder.yudao.module.member.mq.message.user.RegisterCouponSendMessage;
import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 针对 {@link RegisterCouponSendMessage} 的消费者
*
* @author owen
*/
@Component
@Slf4j
public class RegisterCouponSendConsumer extends AbstractStreamMessageListener<RegisterCouponSendMessage> {
@Resource
private CouponService couponService;
@Override
public void onMessage(RegisterCouponSendMessage message) {
log.info("[onMessage][消息内容({})]", message);
couponService.takeCouponByRegister(message.getUserId());
}
}

View File

@ -1,17 +1,16 @@
package cn.iocoder.yudao.module.promotion.service.coupon;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* 优惠劵 Service 接口
*
@ -119,12 +118,9 @@ public interface CouponService {
/**
* 系统给用户发送新人券
*
* @param templateId 优惠券模板编号
* @param userId 用户编号列表
* @param userId 用户编号
*/
default void takeCouponByRegister(Long templateId, Long userId) {
takeCoupon(templateId, CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
}
void takeCouponByRegister(Long userId);
/**
* 获取会员领取指定优惠券的数量
@ -134,11 +130,8 @@ public interface CouponService {
* @return 领取优惠券的数量
*/
default Integer getTakeCount(Long templateId, Long userId) {
return CollUtil.emptyIfNull(getTakeCountListByTemplateIds(Collections.singleton(templateId), userId))
.stream()
.findFirst()
.map(CouponTakeCountBO::getCount)
.orElse(0);
Map<Long, Integer> map = getTakeCountMapByTemplateIds(Collections.singleton(templateId), userId);
return MapUtil.getInt(map, templateId, 0);
}
/**
@ -148,19 +141,7 @@ public interface CouponService {
* @param userId 用户编号
* @return 领取优惠券的数量
*/
default Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
return convertMap(getTakeCountListByTemplateIds(templateIds, userId),
CouponTakeCountBO::getTemplateId, CouponTakeCountBO::getCount);
}
/**
* 统计会员领取优惠券的数量
*
* @param templateIds 优惠券模板编号列表
* @param userId 用户编号
* @return 领取优惠券的数量
*/
List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId);
Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId);
/**
* 获取用户匹配的优惠券列表
@ -178,4 +159,13 @@ public interface CouponService {
*/
int expireCoupon();
/**
* 获取用户是否可以领取优惠券
*
* @param userId 用户编号
* @param templates 优惠券列表
* @return 是否可以领取
*/
Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates);
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -21,7 +20,6 @@ import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
import cn.iocoder.yudao.module.promotion.service.coupon.bo.CouponTakeCountBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -29,18 +27,14 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
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.*;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
import static java.util.Arrays.asList;
// TODO @疯狂注册时赠送用户优惠劵为了解耦可以考虑注册时发个 MQ 消息然后营销这里监听后消费
/**
* 优惠劵 Service 实现类
*
@ -184,9 +178,17 @@ public class CouponServiceImpl implements CouponService {
}
@Override
public List<CouponTakeCountBO> getTakeCountListByTemplateIds(Collection<Long> templateIds, Long userId) {
public void takeCouponByRegister(Long userId) {
List<CouponTemplateDO> templates = couponTemplateService.getCouponTemplateByTakeType(CouponTakeTypeEnum.REGISTER);
for (CouponTemplateDO template : templates) {
takeCoupon(template.getId(), CollUtil.newHashSet(userId), CouponTakeTypeEnum.REGISTER);
}
}
@Override
public Map<Long, Integer> getTakeCountMapByTemplateIds(Collection<Long> templateIds, Long userId) {
if (CollUtil.isEmpty(templateIds)) {
return ListUtil.empty();
return Collections.emptyMap();
}
return couponMapper.selectCountByUserIdAndTemplateIdIn(userId, templateIds);
}
@ -222,6 +224,29 @@ public class CouponServiceImpl implements CouponService {
return count;
}
@Override
public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
// 未登录时都显示可以领取
if (userId == null) {
return userCanTakeMap;
}
// 过滤领取数量无限制的
Set<Long> templateIds = convertSet(templates, CouponTemplateDO::getId, template -> template.getTakeLimitCount() != -1);
// 检查用户领取的数量是否超过限制
if (CollUtil.isNotEmpty(templateIds)) {
Map<Long, Integer> couponTakeCountMap = this.getTakeCountMapByTemplateIds(templateIds, userId);
for (CouponTemplateDO template : templates) {
Integer takeCount = couponTakeCountMap.get(template.getId());
userCanTakeMap.put(template.getId(), takeCount == null || takeCount < template.getTakeLimitCount());
}
}
return userCanTakeMap;
}
/**
* 过期单个优惠劵
*

View File

@ -5,8 +5,10 @@ import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.Cou
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import javax.validation.Valid;
import java.util.List;
/**
* 优惠劵模板 Service 接口
@ -69,4 +71,24 @@ public interface CouponTemplateService {
*/
void updateCouponTemplateTakeCount(Long id, int incrCount);
/**
* 获得指定领取方式的优惠券模板
*
* @param takeType 领取方式
* @return 优惠券模板列表
*/
List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType);
/**
* 获得优惠券模板列表
*
* @param canTakeTypes 可领取的类型列表
* @param productScope 商品使用范围类型
* @param productScopeValue 商品使用范围编号
* @param count 查询数量
* @return 优惠券模板列表
*/
List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
Long productScopeValue, Integer count);
}

View File

@ -2,16 +2,22 @@ package cn.iocoder.yudao.module.promotion.service.coupon;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.product.api.category.ProductCategoryApi;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateCreateReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplatePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.template.CouponTemplateUpdateReqVO;
import cn.iocoder.yudao.module.promotion.convert.coupon.CouponTemplateConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponTemplateMapper;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
@ -29,9 +35,15 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
@Resource
private CouponTemplateMapper couponTemplateMapper;
// TODO @疯狂新增/修改时需要校验对应的商品分类是否存在
@Resource
private ProductCategoryApi productCategoryApi;
@Resource
private ProductSpuApi productSpuApi;
@Override
public Long createCouponTemplate(CouponTemplateCreateReqVO createReqVO) {
// 校验商品范围
validateProductScope(createReqVO.getProductScope(), createReqVO.getProductScopeValues());
// 插入
CouponTemplateDO couponTemplate = CouponTemplateConvert.INSTANCE.convert(createReqVO)
.setStatus(CommonStatusEnum.ENABLE.getStatus());
@ -48,6 +60,8 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
if (updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
}
// 校验商品范围
validateProductScope(updateReqVO.getProductScope(), updateReqVO.getProductScopeValues());
// 更新
CouponTemplateDO updateObj = CouponTemplateConvert.INSTANCE.convert(updateReqVO);
@ -78,6 +92,14 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
return couponTemplate;
}
private void validateProductScope(Integer productScope, List<Long> productScopeValues) {
if (Objects.equals(PromotionProductScopeEnum.SPU.getScope(), productScope)) {
productSpuApi.validateSpuList(productScopeValues);
} else if (Objects.equals(PromotionProductScopeEnum.CATEGORY.getScope(), productScope)) {
productCategoryApi.validateCategoryList(productScopeValues);
}
}
@Override
public CouponTemplateDO getCouponTemplate(Long id) {
return couponTemplateMapper.selectById(id);
@ -93,4 +115,15 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
couponTemplateMapper.updateTakeCount(id, incrCount);
}
@Override
public List<CouponTemplateDO> getCouponTemplateByTakeType(CouponTakeTypeEnum takeType) {
return couponTemplateMapper.selectListByTakeType(takeType.getValue());
}
@Override
public List<CouponTemplateDO> getCouponTemplateList(List<Integer> canTakeTypes, Integer productScope,
Long productScopeValue, Integer count) {
return couponTemplateMapper.selectList(canTakeTypes, productScope, productScopeValue, count);
}
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.promotion.service.coupon.bo;
import lombok.Data;
/**
* 优惠券领取数量 BO
*
* @author owen
*/
@Data
public class CouponTakeCountBO {
/**
* 优惠劵模板编号
*/
private Long templateId;
/**
* 领取数量
*/
private Integer count;
}

View File

@ -32,7 +32,6 @@ public class TradeStatisticsController {
@Resource
private TradeStatisticsService tradeStatisticsService;
// TODO @疯狂要不这个就是 /trend/summary 的特例前端自己查询两次
@GetMapping("/summary")
@Operation(summary = "获得交易统计")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")
@ -40,7 +39,6 @@ public class TradeStatisticsController {
return success(tradeStatisticsService.getTradeSummaryComparison());
}
// TODO @疯狂直接 comparison主要 trend comparison 二选一一个是数据趋势一个是数据对比哈
@GetMapping("/trend/summary")
@Operation(summary = "获得交易状况统计")
@PreAuthorize("@ss.hasPermission('statistics:trade:query')")

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.statistics.service.trade;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeStatisticsComparisonRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeSummaryRespVO;
import cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeTrendSummaryRespVO;
@ -13,8 +14,6 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
/**
@ -79,18 +78,9 @@ public class TradeStatisticsServiceImpl implements TradeStatisticsService {
* @return 交易数据
*/
private TradeSummaryRespBO getTradeSummaryByMonths(int months) {
// TODO @疯狂可以在 LocalDateUtils 封装方法获得月份的开始以及结束两个方法然后这里就可以直接调用了
// 月份开始时间
LocalDateTime beginOfMonth = LocalDateTime.now()
.plusMonths(months)
.with(TemporalAdjusters.firstDayOfMonth())
.with(LocalTime.MIN);
// 月份截止时间
LocalDateTime endOfToday = LocalDateTime.now()
.plusMonths(months)
.with(TemporalAdjusters.lastDayOfMonth())
.with(LocalTime.MAX);
return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(beginOfMonth, endOfToday);
LocalDateTime monthDate = LocalDateTime.now().plusMonths(months);
return tradeStatisticsMapper.selectOrderCreateCountSumAndOrderPayPriceSumByTimeBetween(
LocalDateTimeUtils.beginOfMonth(monthDate), LocalDateTimeUtils.endOfMonth(monthDate));
}
}

View File

@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageRecordConvert;
import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
@ -84,7 +85,7 @@ public class AppBrokerageUserController {
LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday);
LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday);
Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(),
BrokerageRecordBizTypeEnum.ORDER.getType(), beginTime, endTime);
BrokerageRecordBizTypeEnum.ORDER, BrokerageRecordStatusEnum.SETTLEMENT, beginTime, endTime);
// 统计用户提现的佣金
Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryListByUserId(Collections.singleton(brokerageUser.getId()),
BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream()

View File

@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
@ -88,8 +87,8 @@ public interface BrokerageUserConvert {
return respVO;
}
default void copyTo(IPage<AppBrokerageUserChildSummaryRespVO> pageResult, Map<Long, MemberUserRespDTO> userMap) {
for (AppBrokerageUserChildSummaryRespVO vo : pageResult.getRecords()) {
default void copyTo(List<AppBrokerageUserChildSummaryRespVO> list, Map<Long, MemberUserRespDTO> userMap) {
for (AppBrokerageUserChildSummaryRespVO vo : list) {
Optional.ofNullable(userMap.get(vo.getId())).ifPresent(user ->
vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
}

View File

@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.order;
import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import cn.iocoder.yudao.module.trade.enums.delivery.DeliveryTypeEnum;
@ -105,7 +107,12 @@ public class TradeOrderDO extends BaseDO {
*/
private Boolean commentStatus;
// TODO @疯狂加一个推广人编号
/**
* 推广人编号
* {@link BrokerageUserDO#getId()}
* {@link MemberUserRespDTO#getId()}
*/
private Long brokerageUserId;
// ========== 价格 + 支付基本信息 ==========

View File

@ -80,10 +80,11 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
}
@Select("SELECT SUM(price) FROM trade_brokerage_record " +
"WHERE user_id = #{userId} AND biz_type = #{bizType} " +
"AND create_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
"WHERE user_id = #{userId} AND biz_type = #{bizType} AND status = #{status} " +
"AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} AND deleted = FALSE")
Integer selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(@Param("userId") Long userId,
@Param("bizType") Integer bizType,
@Param("status") Integer status,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
@ -98,4 +99,13 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
@Select("SELECT COUNT(1) FROM trade_brokerage_record " +
"WHERE biz_type = #{bizType} AND status = #{status} AND deleted = FALSE " +
"AND unfreeze_time BETWEEN #{beginTime} AND #{endTime} " +
"GROUP BY user_id HAVING SUM(price) > #{brokeragePrice}")
Integer selectCountByPriceGt(@Param("brokeragePrice") Integer brokeragePrice,
@Param("bizType") Integer bizType,
@Param("status") Integer status,
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -138,13 +138,35 @@ public interface BrokerageUserMapper extends BaseMapperX<BrokerageUserDO> {
@Param("beginTime") LocalDateTime beginTime,
@Param("endTime") LocalDateTime endTime);
/**
* 下级分销统计分页
*
* @param bizType 业务类型
* @param status 状态
* @param bindUserIds 绑定用户编号列表
* @param sortingField 排序字段
* @return 下级分销统计分页列表
*/
IPage<AppBrokerageUserChildSummaryRespVO> selectSummaryPageByUserId(Page<?> page,
@Param("ids") List<Long> ids, // BrokerageUser ids 数组
@Param("bizType") Integer bizType,
@Param("status") Integer status,
@Param("bindUserIds") List<Long> bindUserIds,
@Param("sortingField") SortingField sortingField);
/**
* 下级分销统计不分页
*
* @param bizType 业务类型
* @param status 状态
* @param bindUserIds 绑定用户编号列表
* @param sortingField 排序字段
* @return 下级分销统计列表
*/
List<AppBrokerageUserChildSummaryRespVO> selectSummaryListByUserId(@Param("bizType") Integer bizType,
@Param("status") Integer status,
@Param("bindUserIds") List<Long> bindUserIds,
@Param("sortingField") SortingField sortingField);
default List<BrokerageUserDO> selectListByBindUserId(Long bindUserId) {
return selectList(BrokerageUserDO::getBindUserId, bindUserId);
}

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageRecordDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.UserBrokerageSummaryRespBO;
@ -120,11 +121,13 @@ public interface BrokerageRecordService {
*
* @param userId 用户编号
* @param bizType 业务类型
* @param status 状态
* @param beginTime 开始时间
* @param endTime 截止时间
* @return 用户佣金合计
*/
Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime);
Integer getSummaryPriceByUserId(Long userId, BrokerageRecordBizTypeEnum bizType, BrokerageRecordStatusEnum status,
LocalDateTime beginTime, LocalDateTime endTime);
/**
* 获得用户佣金排行分页列表基于佣金总数

View File

@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.trade.service.brokerage;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
@ -265,9 +262,10 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
}
@Override
public Integer getSummaryPriceByUserId(Long userId, Integer bizType, LocalDateTime beginTime, LocalDateTime endTime) {
return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId, bizType,
beginTime, endTime);
public Integer getSummaryPriceByUserId(Long userId, BrokerageRecordBizTypeEnum bizType, BrokerageRecordStatusEnum status,
LocalDateTime beginTime, LocalDateTime endTime) {
return brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId,
bizType.getType(), status.getStatus(), beginTime, endTime);
}
@Override
@ -279,17 +277,18 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
}
// TODO @疯狂这个要不我们先做精准的先查询自己的推广金额然后查询比该金额多的有多少人
@Override
public Integer getUserRankByPrice(Long userId, LocalDateTime[] times) {
AppBrokerageUserRankPageReqVO pageParam = new AppBrokerageUserRankPageReqVO().setTimes(times);
// 取前 100
pageParam.setPageSize(100);
PageResult<AppBrokerageUserRankByPriceRespVO> pageResult = getBrokerageUserChildSummaryPageByPrice(pageParam);
// 获得索引
int index = CollUtil.indexOf(pageResult.getList(), user -> Objects.equals(userId, user.getId()));
// 用户的推广金额
Integer price = brokerageRecordMapper.selectSummaryPriceByUserIdAndBizTypeAndCreateTimeBetween(userId,
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
ArrayUtil.get(times, 0), ArrayUtil.get(times, 1));
// 排在用户前面的人数
Integer greaterCount = brokerageRecordMapper.selectCountByPriceGt(price,
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
ArrayUtil.get(times, 0), ArrayUtil.get(times, 1));
// 获得排名
return index + 1;
return ObjUtil.defaultIfNull(greaterCount, 0) + 1;
}
@Override

View File

@ -31,6 +31,7 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@ -225,25 +226,66 @@ public class BrokerageUserServiceImpl implements BrokerageUserService {
@Override
public PageResult<AppBrokerageUserChildSummaryRespVO> getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId) {
// 1.1 根据昵称过滤用户
List<Long> ids = StrUtil.isBlank(pageReqVO.getNickname())
? Collections.emptyList()
: convertList(memberUserApi.getUserListByNickname(pageReqVO.getNickname()), MemberUserRespDTO::getId);
// 1.2 生成推广员编号列表
// TODO @疯狂是不是可以先 1.2 查询出来然后查询对应的昵称进行过滤避免昵称过滤返回的 id 过多
// 生成推广员编号列表
List<Long> bindUserIds = buildBindUserIdsByLevel(userId, pageReqVO.getLevel());
// 2. 分页查询
IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
MyBatisUtils.buildPage(pageReqVO), ids, BrokerageRecordBizTypeEnum.ORDER.getType(),
BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
);
// 情况一没有昵称过滤条件时直接使用数据库的分页查询
if (StrUtil.isBlank(pageReqVO.getNickname())) {
// 1.1 分页查询
IPage<AppBrokerageUserChildSummaryRespVO> pageResult = brokerageUserMapper.selectSummaryPageByUserId(
MyBatisUtils.buildPage(pageReqVO), BrokerageRecordBizTypeEnum.ORDER.getType(),
BrokerageRecordStatusEnum.SETTLEMENT.getStatus(), bindUserIds, pageReqVO.getSortingField()
);
// 3. 拼接数据并返回
List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
BrokerageUserConvert.INSTANCE.copyTo(pageResult, userMap);
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
// 1.2 拼接数据并返回
List<Long> userIds = convertList(pageResult.getRecords(), AppBrokerageUserChildSummaryRespVO::getId);
Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(userIds);
BrokerageUserConvert.INSTANCE.copyTo(pageResult.getRecords(), userMap);
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
}
// 情况二有昵称过滤条件时需要跨模块Member过滤
// 2.1 查询所有匹配的分销用户
List<AppBrokerageUserChildSummaryRespVO> list = brokerageUserMapper.selectSummaryListByUserId(
BrokerageRecordBizTypeEnum.ORDER.getType(), BrokerageRecordStatusEnum.SETTLEMENT.getStatus(),
bindUserIds, pageReqVO.getSortingField()
);
if (CollUtil.isEmpty(list)) {
return PageResult.empty();
}
// 2.2 查出对应的用户信息
List<MemberUserRespDTO> users = memberUserApi.getUserList(convertList(list, AppBrokerageUserChildSummaryRespVO::getId));
if (CollUtil.isEmpty(users)) {
return PageResult.empty();
}
// 2.3 根据昵称过滤出用户编号
Map<Long, MemberUserRespDTO> userMap = users.stream()
.filter(user -> StrUtil.contains(user.getNickname(), pageReqVO.getNickname()))
.collect(Collectors.toMap(MemberUserRespDTO::getId, dto -> dto));
if (CollUtil.isEmpty(userMap)) {
return PageResult.empty();
}
// 2.4 根据用户编号过滤结果
list.removeIf(vo -> !userMap.containsKey(vo.getId()));
if (CollUtil.isEmpty(list)) {
return PageResult.empty();
}
// 2.5 处理分页
List<AppBrokerageUserChildSummaryRespVO> result = list.stream()
.skip((long) (pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize())
.limit(pageReqVO.getPageSize())
.collect(Collectors.toList());
if (CollUtil.isEmpty(result)) {
return PageResult.empty();
}
// 2.6 拼接数据并返回
BrokerageUserConvert.INSTANCE.copyTo(result, userMap);
return new PageResult<>(result, (long) list.size());
}
private boolean isUserCanBind(BrokerageUserDO user) {

View File

@ -40,6 +40,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettle
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@ -54,6 +55,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrderLog;
import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService;
import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageAddReqBO;
import cn.iocoder.yudao.module.trade.service.cart.CartService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
@ -108,6 +110,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private DeliveryExpressService deliveryExpressService;
@Resource
private TradeMessageService tradeMessageService;
@Resource
private BrokerageUserService brokerageUserService;
@Resource
private BrokerageRecordService brokerageRecordService;
@Resource
private ProductSpuApi productSpuApi;
@ -130,8 +136,6 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Resource
private MemberPointApi memberPointApi;
@Resource
private BrokerageRecordService brokerageRecordService;
@Resource
private ProductCommentApi productCommentApi;
@Resource
@ -301,6 +305,12 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 6. 插入订单日志
TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
// 7. 设置订单推广人
BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(order.getUserId());
if (brokerageUser != null && brokerageUser.getBindUserId() != null) {
tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setBrokerageUserId(brokerageUser.getBindUserId()));
}
// TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
}

View File

@ -4,7 +4,6 @@ import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculator;
@ -20,7 +19,8 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_STOCK_NOT_ENOUGH;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
/**
@ -82,18 +82,7 @@ public class TradePriceServiceImpl implements TradePriceService {
private List<ProductSpuRespDTO> checkSpuList(List<ProductSkuRespDTO> skuList) {
// 获得商品 SPU 数组
List<ProductSpuRespDTO> spus = productSpuApi.getSpuList(convertSet(skuList, ProductSkuRespDTO::getSpuId));
// 校验商品 SPU
spus.forEach(spu -> {
if (spu == null) {
throw exception(SPU_NOT_EXISTS);
}
if (!ProductSpuStatusEnum.isEnable(spu.getStatus())) {
throw exception(SPU_NOT_ENABLE);
}
});
return spus;
return productSpuApi.getSpuListAndValidate(convertSet(skuList, ProductSkuRespDTO::getSpuId));
}
}

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.util.BooleanUtil;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import lombok.extern.slf4j.Slf4j;
@ -16,7 +16,6 @@ import java.util.Optional;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
// TODO @疯狂这个可以搞个单测
/**
* 赠送积分的 {@link TradePriceCalculator} 实现类
*
@ -28,14 +27,14 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
public class TradePointGiveCalculator implements TradePriceCalculator {
@Resource
private MemberPointApi memberPointApi;
private MemberConfigApi memberConfigApi;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 1.1 校验积分功能是否开启
int givePointPerYuan = Optional.ofNullable(memberPointApi.getConfig())
.filter(config -> BooleanUtil.isTrue(config.getTradeDeductEnable()))
.map(MemberPointConfigRespDTO::getTradeGivePoint)
int givePointPerYuan = Optional.ofNullable(memberConfigApi.getConfig())
.filter(config -> BooleanUtil.isTrue(config.getPointTradeDeductEnable()))
.map(MemberConfigRespDTO::getPointTradeGivePoint)
.orElse(0);
if (givePointPerYuan <= 0) {
return;

View File

@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.member.api.point.MemberPointApi;
import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
@ -20,7 +20,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_PAY_PRICE_ILLEGAL;
// TODO @疯狂搞个单测嘿嘿
/**
* 使用积分的 {@link TradePriceCalculator} 实现类
*
@ -32,19 +31,21 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCU
public class TradePointUsePriceCalculator implements TradePriceCalculator {
@Resource
private MemberPointApi memberPointApi;
private MemberConfigApi memberConfigApi;
@Resource
private MemberUserApi memberUserApi;
@Override
public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
// 默认使用积分为 0
result.setUsePoint(0);
// 1.1 校验是否使用积分
if (!BooleanUtil.isTrue(param.getPointStatus())) {
result.setUsePoint(0);
return;
}
// 1.2 校验积分抵扣是否开启
MemberPointConfigRespDTO config = memberPointApi.getConfig();
MemberConfigRespDTO config = memberConfigApi.getConfig();
if (!isDeductPointEnable(config)) {
return;
}
@ -76,20 +77,20 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
TradePriceCalculatorHelper.recountAllPrice(result);
}
private boolean isDeductPointEnable(MemberPointConfigRespDTO config) {
private boolean isDeductPointEnable(MemberConfigRespDTO config) {
return config != null &&
!BooleanUtil.isTrue(config.getTradeDeductEnable()) && // 积分功能是否启用
config.getTradeDeductUnitPrice() != null && config.getTradeDeductUnitPrice() > 0; // 有没有配置1 积分抵扣多少分
BooleanUtil.isTrue(config.getPointTradeDeductEnable()) && // 积分功能是否启用
config.getPointTradeDeductUnitPrice() != null && config.getPointTradeDeductUnitPrice() > 0; // 有没有配置1 积分抵扣多少分
}
private Integer calculatePointPrice(MemberPointConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
private Integer calculatePointPrice(MemberConfigRespDTO config, Integer usePoint, TradePriceCalculateRespBO result) {
// 每个订单最多可以使用的积分数量
if (config.getTradeDeductMaxPrice() != null && config.getTradeDeductMaxPrice() > 0) {
usePoint = Math.min(usePoint, config.getTradeDeductMaxPrice());
if (config.getPointTradeDeductMaxPrice() != null && config.getPointTradeDeductMaxPrice() > 0) {
usePoint = Math.min(usePoint, config.getPointTradeDeductMaxPrice());
}
// TODO @疯狂这里应该是抵扣到只剩下 0.01
// 积分优惠金额
int pointPrice = usePoint * config.getTradeDeductUnitPrice();
int pointPrice = usePoint * config.getPointTradeDeductUnitPrice();
if (result.getPrice().getPayPrice() <= pointPrice) {
// 禁止 0 元购
throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL);
@ -99,7 +100,7 @@ public class TradePointUsePriceCalculator implements TradePriceCalculator {
// pointPrice = result.getPrice().getPayPrice();
// // 反推需要扣除的积分
// usePoint = NumberUtil.toBigDecimal(pointPrice)
// .divide(NumberUtil.toBigDecimal(config.getTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
// .divide(NumberUtil.toBigDecimal(config.getPointTradeDeductUnitPrice()), 0, RoundingMode.HALF_UP)
// .intValue();
// }
// 记录使用的积分

View File

@ -2,8 +2,7 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageUserMapper">
<select id="selectSummaryPageByUserId"
resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
<sql id="selectSummaryListByUserId">
SELECT bu.id, bu.bind_user_time AS brokerageTime,
(SELECT SUM(price) FROM trade_brokerage_record r
WHERE r.user_id = bu.id AND biz_type = #{bizType} AND r.status = #{status} AND r.deleted = FALSE) AS brokeragePrice,
@ -14,12 +13,6 @@
FROM trade_brokerage_user AS bu
<where>
bu.deleted = false
<if test="ids != null and ids.size() > 0">
and bu.id in
<foreach collection="ids" open="(" item="id" separator="," close=")">
#{id}
</foreach>
</if>
<if test="bindUserIds != null and bindUserIds.size() > 0">
and bu.bind_user_id in
<foreach collection="bindUserIds" open="(" item="bindUserId" separator="," close=")">
@ -41,6 +34,15 @@
ORDER BY bu.bind_user_time DESC
</otherwise>
</choose>
</sql>
<select id="selectSummaryPageByUserId"
resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
<include refid="selectSummaryListByUserId" />
</select>
<select id="selectSummaryListByUserId"
resultType="cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserChildSummaryRespVO">
<include refid="selectSummaryListByUserId" />
</select>
</mapper>

View File

@ -0,0 +1,98 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.ArrayList;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
/**
* {@link TradePointGiveCalculator} 的单元测试类
*
* @author owen
*/
public class TradePointGiveCalculatorTest extends BaseMockitoUnitTest {
@InjectMocks
private TradePointGiveCalculator tradePointGiveCalculator;
@Mock
private MemberConfigApi memberConfigApi;
@Test
public void testCalculate() {
// 准备参数
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
.setUserId(233L)
.setItems(asList(
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 全局积分
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 全局积分 + SKU 积分
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(4).setSelected(false), // 全局积分但是未选中
new TradePriceCalculateReqBO.Item().setSkuId(40L).setCount(5).setSelected(false) // 全局积分 + SKU 积分但是未选中
));
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
.setType(TradeOrderTypeEnum.NORMAL.getType())
.setPrice(new TradePriceCalculateRespBO.Price())
.setPromotions(new ArrayList<>())
.setItems(asList(
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
.setPrice(100).setSpuId(1L).setGivePoint(0),
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
.setPrice(50).setSpuId(2L).setGivePoint(100),
new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(4).setSelected(false)
.setPrice(30).setSpuId(3L).setGivePoint(0),
new TradePriceCalculateRespBO.OrderItem().setSkuId(40L).setCount(5).setSelected(false)
.setPrice(60).setSpuId(1L).setGivePoint(100)
));
// 保证价格被初始化上
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
TradePriceCalculatorHelper.recountAllPrice(result);
// mock 方法积分配置 信息
MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
.setPointTradeGivePoint(100)); // 1 元赠送多少分
when(memberConfigApi.getConfig()).thenReturn(memberConfig);
// 调用
tradePointGiveCalculator.calculate(param, result);
// 断言Price 部分
assertEquals(result.getGivePoint(), 2 * 100 + 3 * 50 + 100);
// 断言SKU 1
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
assertEquals(orderItem01.getSkuId(), 10L);
assertEquals(orderItem01.getCount(), 2);
assertEquals(orderItem01.getPrice(), 100);
assertEquals(orderItem01.getGivePoint(), 2 * 100); // 全局积分
// 断言SKU 2
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
assertEquals(orderItem02.getSkuId(), 20L);
assertEquals(orderItem02.getCount(), 3);
assertEquals(orderItem02.getPrice(), 50);
assertEquals(orderItem02.getGivePoint(), 3 * 50 + 100); // 全局积分 + SKU 积分
// 断言SKU 3
TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
assertEquals(orderItem03.getSkuId(), 30L);
assertEquals(orderItem03.getCount(), 4);
assertEquals(orderItem03.getPrice(), 30);
assertEquals(orderItem03.getGivePoint(), 0); // 全局积分但是未选中
// 断言SKU 4
TradePriceCalculateRespBO.OrderItem orderItem04 = result.getItems().get(3);
assertEquals(orderItem04.getSkuId(), 40L);
assertEquals(orderItem04.getCount(), 5);
assertEquals(orderItem04.getPrice(), 60);
assertEquals(orderItem04.getGivePoint(), 100); // 全局积分 + SKU 积分但是未选中
}
}

View File

@ -0,0 +1,332 @@
package cn.iocoder.yudao.module.trade.service.price.calculator;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.member.api.config.MemberConfigApi;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.ArrayList;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when;
/**
* {@link TradePointUsePriceCalculator } 的单元测试类
*
* @author owen
*/
public class TradePointUsePriceCalculatorTest extends BaseMockitoUnitTest {
@InjectMocks
private TradePointUsePriceCalculator tradePointUsePriceCalculator;
@Mock
private MemberConfigApi memberConfigApi;
@Mock
private MemberUserApi memberUserApi;
@Test
public void testCalculate_success() {
// 准备参数
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
.setUserId(233L).setPointStatus(true) // 是否使用积分
.setItems(asList(
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中不使用积分
));
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
.setType(TradeOrderTypeEnum.NORMAL.getType())
.setPrice(new TradePriceCalculateRespBO.Price())
.setPromotions(new ArrayList<>())
.setItems(asList(
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
.setPrice(100).setSpuId(1L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
.setPrice(50).setSpuId(2L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
.setPrice(30).setSpuId(3L)
));
// 保证价格被初始化上
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
TradePriceCalculatorHelper.recountAllPrice(result);
// mock 方法积分配置 信息
MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
.setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额单位分
.setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值
when(memberConfigApi.getConfig()).thenReturn(memberConfig);
// mock 方法会员 信息
MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
when(memberUserApi.getUser(user.getId())).thenReturn(user);
// 调用
tradePointUsePriceCalculator.calculate(param, result);
// 断言使用了多少积分
assertEquals(result.getUsePoint(), 100);
// 断言Price 部分
TradePriceCalculateRespBO.Price price = result.getPrice();
assertEquals(price.getTotalPrice(), 350);
assertEquals(price.getPayPrice(), 250);
assertEquals(price.getPointPrice(), 100);
// 断言SKU 1
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
assertEquals(orderItem01.getSkuId(), 10L);
assertEquals(orderItem01.getCount(), 2);
assertEquals(orderItem01.getPrice(), 100);
assertEquals(orderItem01.getPointPrice(), 57);
assertEquals(orderItem01.getPayPrice(), 143);
// 断言SKU 2
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
assertEquals(orderItem02.getSkuId(), 20L);
assertEquals(orderItem02.getCount(), 3);
assertEquals(orderItem02.getPrice(), 50);
assertEquals(orderItem02.getPointPrice(), 43);
assertEquals(orderItem02.getPayPrice(), 107);
// 断言SKU 3
TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
assertEquals(orderItem03.getSkuId(), 30L);
assertEquals(orderItem03.getCount(), 5);
assertEquals(orderItem03.getPrice(), 30);
assertEquals(orderItem03.getPointPrice(), 0);
assertEquals(orderItem03.getPayPrice(), 150);
// 断言Promotion 部分
assertEquals(result.getPromotions().size(), 1);
TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
assertEquals(promotion01.getId(), user.getId());
assertEquals(promotion01.getName(), "积分抵扣");
assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType());
assertEquals(promotion01.getTotalPrice(), 350);
assertEquals(promotion01.getDiscountPrice(), 100);
assertTrue(promotion01.getMatch());
assertEquals(promotion01.getDescription(), "积分抵扣:省 1.00 元");
assertEquals(promotion01.getItems().size(), 2);
TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
assertEquals(promotionItem011.getSkuId(), 10L);
assertEquals(promotionItem011.getTotalPrice(), 200);
assertEquals(promotionItem011.getDiscountPrice(), 57);
TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
assertEquals(promotionItem012.getSkuId(), 20L);
assertEquals(promotionItem012.getTotalPrice(), 150);
assertEquals(promotionItem012.getDiscountPrice(), 43);
}
/**
* 当用户积分充足时抵扣的金额为配置表的积分抵扣最大值
*/
@Test
public void testCalculate_TradeDeductMaxPrice() {
// 准备参数
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
.setUserId(233L).setPointStatus(true) // 是否使用积分
.setItems(asList(
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中不使用积分
));
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
.setType(TradeOrderTypeEnum.NORMAL.getType())
.setPrice(new TradePriceCalculateRespBO.Price())
.setPromotions(new ArrayList<>())
.setItems(asList(
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
.setPrice(100).setSpuId(1L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
.setPrice(50).setSpuId(2L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
.setPrice(30).setSpuId(3L)
));
// 保证价格被初始化上
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
TradePriceCalculatorHelper.recountAllPrice(result);
// mock 方法积分配置 信息
MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
.setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额单位分
.setPointTradeDeductMaxPrice(50)); // 积分抵扣最大值
when(memberConfigApi.getConfig()).thenReturn(memberConfig);
// mock 方法会员 信息
MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(100));
when(memberUserApi.getUser(user.getId())).thenReturn(user);
// 调用
tradePointUsePriceCalculator.calculate(param, result);
// 断言使用了多少积分
assertEquals(result.getUsePoint(), 50);
// 断言Price 部分
TradePriceCalculateRespBO.Price price = result.getPrice();
assertEquals(price.getTotalPrice(), 350);
assertEquals(price.getPayPrice(), 300);
assertEquals(price.getPointPrice(), 50);
// 断言SKU 1
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
assertEquals(orderItem01.getSkuId(), 10L);
assertEquals(orderItem01.getCount(), 2);
assertEquals(orderItem01.getPrice(), 100);
assertEquals(orderItem01.getPointPrice(), 28);
assertEquals(orderItem01.getPayPrice(), 172);
// 断言SKU 2
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
assertEquals(orderItem02.getSkuId(), 20L);
assertEquals(orderItem02.getCount(), 3);
assertEquals(orderItem02.getPrice(), 50);
assertEquals(orderItem02.getPointPrice(), 22);
assertEquals(orderItem02.getPayPrice(), 128);
// 断言SKU 3
TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
assertEquals(orderItem03.getSkuId(), 30L);
assertEquals(orderItem03.getCount(), 5);
assertEquals(orderItem03.getPrice(), 30);
assertEquals(orderItem03.getPointPrice(), 0);
assertEquals(orderItem03.getPayPrice(), 150);
// 断言Promotion 部分
assertEquals(result.getPromotions().size(), 1);
TradePriceCalculateRespBO.Promotion promotion01 = result.getPromotions().get(0);
assertEquals(promotion01.getId(), user.getId());
assertEquals(promotion01.getName(), "积分抵扣");
assertEquals(promotion01.getType(), PromotionTypeEnum.POINT.getType());
assertEquals(promotion01.getTotalPrice(), 350);
assertEquals(promotion01.getDiscountPrice(), 50);
assertTrue(promotion01.getMatch());
assertEquals(promotion01.getDescription(), "积分抵扣:省 0.50 元");
assertEquals(promotion01.getItems().size(), 2);
TradePriceCalculateRespBO.PromotionItem promotionItem011 = promotion01.getItems().get(0);
assertEquals(promotionItem011.getSkuId(), 10L);
assertEquals(promotionItem011.getTotalPrice(), 200);
assertEquals(promotionItem011.getDiscountPrice(), 28);
TradePriceCalculateRespBO.PromotionItem promotionItem012 = promotion01.getItems().get(1);
assertEquals(promotionItem012.getSkuId(), 20L);
assertEquals(promotionItem012.getTotalPrice(), 150);
assertEquals(promotionItem012.getDiscountPrice(), 22);
}
/**
* 订单不使用积分不会产生优惠
*/
@Test
public void testCalculate_PointStatusFalse() {
// 准备参数
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
.setUserId(233L).setPointStatus(false) // 是否使用积分
.setItems(asList(
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中不使用积分
));
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
.setType(TradeOrderTypeEnum.NORMAL.getType())
.setPrice(new TradePriceCalculateRespBO.Price())
.setPromotions(new ArrayList<>())
.setItems(asList(
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
.setPrice(100).setSpuId(1L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
.setPrice(50).setSpuId(2L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
.setPrice(30).setSpuId(3L)
));
// 保证价格被初始化上
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
TradePriceCalculatorHelper.recountAllPrice(result);
// 调用
tradePointUsePriceCalculator.calculate(param, result);
// 断言没有使用积分
assertNotUsePoint(result);
}
/**
* 会员积分不足不会产生优惠
*/
@Test
public void testCalculate_UserPointNotEnough() {
// 准备参数
TradePriceCalculateReqBO param = new TradePriceCalculateReqBO()
.setUserId(233L).setPointStatus(true) // 是否使用积分
.setItems(asList(
new TradePriceCalculateReqBO.Item().setSkuId(10L).setCount(2).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(20L).setCount(3).setSelected(true), // 使用积分
new TradePriceCalculateReqBO.Item().setSkuId(30L).setCount(5).setSelected(false) // 未选中不使用积分
));
TradePriceCalculateRespBO result = new TradePriceCalculateRespBO()
.setType(TradeOrderTypeEnum.NORMAL.getType())
.setPrice(new TradePriceCalculateRespBO.Price())
.setPromotions(new ArrayList<>())
.setItems(asList(
new TradePriceCalculateRespBO.OrderItem().setSkuId(10L).setCount(2).setSelected(true)
.setPrice(100).setSpuId(1L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(20L).setCount(3).setSelected(true)
.setPrice(50).setSpuId(2L),
new TradePriceCalculateRespBO.OrderItem().setSkuId(30L).setCount(5).setSelected(false)
.setPrice(30).setSpuId(3L)
));
// 保证价格被初始化上
TradePriceCalculatorHelper.recountPayPrice(result.getItems());
TradePriceCalculatorHelper.recountAllPrice(result);
// mock 方法积分配置 信息
MemberConfigRespDTO memberConfig = randomPojo(MemberConfigRespDTO.class,
o -> o.setPointTradeDeductEnable(true) // 启用积分折扣
.setPointTradeDeductUnitPrice(1) // 1 积分抵扣多少金额单位分
.setPointTradeDeductMaxPrice(100)); // 积分抵扣最大值
when(memberConfigApi.getConfig()).thenReturn(memberConfig);
// mock 方法会员 信息
MemberUserRespDTO user = randomPojo(MemberUserRespDTO.class, o -> o.setId(param.getUserId()).setPoint(0));
when(memberUserApi.getUser(user.getId())).thenReturn(user);
// 调用
tradePointUsePriceCalculator.calculate(param, result);
// 断言没有使用积分
assertNotUsePoint(result);
}
/**
* 断言没有使用积分
*/
private static void assertNotUsePoint(TradePriceCalculateRespBO result) {
// 断言使用了多少积分
assertEquals(result.getUsePoint(), 0);
// 断言Price 部分
TradePriceCalculateRespBO.Price price = result.getPrice();
assertEquals(price.getTotalPrice(), 350);
assertEquals(price.getPayPrice(), 350);
assertEquals(price.getPointPrice(), 0);
// 断言SKU 1
TradePriceCalculateRespBO.OrderItem orderItem01 = result.getItems().get(0);
assertEquals(orderItem01.getSkuId(), 10L);
assertEquals(orderItem01.getCount(), 2);
assertEquals(orderItem01.getPrice(), 100);
assertEquals(orderItem01.getPointPrice(), 0);
assertEquals(orderItem01.getPayPrice(), 200);
// 断言SKU 2
TradePriceCalculateRespBO.OrderItem orderItem02 = result.getItems().get(1);
assertEquals(orderItem02.getSkuId(), 20L);
assertEquals(orderItem02.getCount(), 3);
assertEquals(orderItem02.getPrice(), 50);
assertEquals(orderItem02.getPointPrice(), 0);
assertEquals(orderItem02.getPayPrice(), 150);
// 断言SKU 3
TradePriceCalculateRespBO.OrderItem orderItem03 = result.getItems().get(2);
assertEquals(orderItem03.getSkuId(), 30L);
assertEquals(orderItem03.getCount(), 5);
assertEquals(orderItem03.getPrice(), 30);
assertEquals(orderItem03.getPointPrice(), 0);
assertEquals(orderItem03.getPayPrice(), 150);
// 断言Promotion 部分
assertEquals(result.getPromotions().size(), 0);
}
}

View File

@ -22,6 +22,11 @@
<artifactId>yudao-common</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mq</artifactId>
<scope>compile</scope>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.member.api.config;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
/**
* 用户配置 API 接口
*
* @author owen
*/
public interface MemberConfigApi {
/**
* 获得积分配置
*
* @return 积分配置
*/
MemberConfigRespDTO getConfig();
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.member.api.point.dto;
package cn.iocoder.yudao.module.member.api.config.dto;
import lombok.Data;
@ -8,25 +8,25 @@ import lombok.Data;
* @author 芋道源码
*/
@Data
public class MemberPointConfigRespDTO {
public class MemberConfigRespDTO {
/**
* 积分抵扣开关
*/
private Boolean tradeDeductEnable;
private Boolean pointTradeDeductEnable;
/**
* 积分抵扣单位
* <p>
* 1 积分抵扣多少分
*/
private Integer tradeDeductUnitPrice;
private Integer pointTradeDeductUnitPrice;
/**
* 积分抵扣最大值
*/
private Integer tradeDeductMaxPrice;
private Integer pointTradeDeductMaxPrice;
/**
* 1 元赠送多少分
*/
private Integer tradeGivePoint;
private Integer pointTradeGivePoint;
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.member.api.point;
import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import javax.validation.constraints.Min;
@ -12,14 +11,6 @@ import javax.validation.constraints.Min;
*/
public interface MemberPointApi {
// TODO @疯狂这个我们要不要搞成通用的会员配置MemberConfig
/**
* 获得积分配置
*
* @return 积分配置
*/
MemberPointConfigRespDTO getConfig();
/**
* 增加用户积分
*

View File

@ -38,6 +38,7 @@ public interface ErrorCodeConstants {
//========== 签到配置 1-004-009-000 ==========
ErrorCode SIGN_IN_CONFIG_NOT_EXISTS = new ErrorCode(1_004_009_000, "签到天数规则不存在");
ErrorCode SIGN_IN_CONFIG_EXISTS = new ErrorCode(1_004_009_001, "签到天数规则已存在");
ErrorCode SIGN_IN_CONFIG_AWARD_EMPTY = new ErrorCode(1_004_009_002, "签到奖励积分和经验不能同时为空");
//========== 签到配置 1-004-010-000 ==========
ErrorCode SIGN_IN_RECORD_TODAY_EXISTS = new ErrorCode(1_004_010_000,"今日已签到,请勿重复签到");

View File

@ -17,6 +17,7 @@ import java.util.Objects;
public enum MemberPointBizTypeEnum implements IntArrayValuable {
SIGN(1, "签到", "签到获得 {} 积分", true),
ADMIN(2, "管理员修改", "管理员修改 {} 积分", true),
ORDER_GIVE(10, "订单奖励", "下单获得 {} 积分", true), // 支付订单时赠送积分
ORDER_CANCEL(11, "订单取消", "订单取消,退还 {} 积分", true), // 取消订单时退回积分
ORDER_USE(12, "订单使用", "下单使用 {} 积分", false), // 下单时扣减积分

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.member.mq.message.user;
import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotNull;
/**
* 新人券发放消息
*
* @author owen
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterCouponSendMessage extends AbstractStreamMessage {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
@Override
public String getStreamKey() {
return "member.register-coupon.send";
}
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.member.api.config;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 用户配置 API 实现类
*
* @author owen
*/
@Service
@Validated
public class MemberConfigApiImpl implements MemberConfigApi {
@Resource
private MemberConfigService memberConfigService;
@Override
public MemberConfigRespDTO getConfig() {
return MemberConfigConvert.INSTANCE.convert01(memberConfigService.getConfig());
}
}

View File

@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.member.api.point;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -25,13 +22,6 @@ public class MemberPointApiImpl implements MemberPointApi {
@Resource
private MemberPointRecordService memberPointRecordService;
@Resource
private MemberPointConfigService memberPointConfigService;
@Override
public MemberPointConfigRespDTO getConfig() {
return MemberPointConfigConvert.INSTANCE.convert01(memberPointConfigService.getPointConfig());
}
@Override
public void addPoint(Long userId, Integer point, Integer bizType, String bizId) {

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.member.controller.admin.config;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
import io.swagger.v3.oas.annotations.Operation;
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.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 会员设置")
@RestController
@RequestMapping("/member/config")
@Validated
public class MemberConfigController {
@Resource
private MemberConfigService memberConfigService;
@PutMapping("/save")
@Operation(summary = "保存会员配置")
@PreAuthorize("@ss.hasPermission('member:config:save')")
public CommonResult<Boolean> saveConfig(@Valid @RequestBody MemberConfigSaveReqVO saveReqVO) {
memberConfigService.saveConfig(saveReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得会员配置")
@PreAuthorize("@ss.hasPermission('member:config:query')")
public CommonResult<MemberConfigRespVO> getConfig() {
MemberConfigDO config = memberConfigService.getConfig();
return success(MemberConfigConvert.INSTANCE.convert(config));
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
package cn.iocoder.yudao.module.member.controller.admin.config.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -6,26 +6,26 @@ import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 会员积分配置 Base VO提供给添加修改详细的子 VO 使用
* 会员配置 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class MemberPointConfigBaseVO {
public class MemberConfigBaseVO {
@Schema(description = "积分抵扣开关", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "积分抵扣开发不能为空")
private Boolean tradeDeductEnable;
private Boolean pointTradeDeductEnable;
@Schema(description = "积分抵扣,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "13506")
@NotNull(message = "积分抵扣不能为空")
private Integer tradeDeductUnitPrice;
private Integer pointTradeDeductUnitPrice;
@Schema(description = "积分抵扣最大值", requiredMode = Schema.RequiredMode.REQUIRED, example = "32428")
@NotNull(message = "积分抵扣最大值不能为空")
private Integer tradeDeductMaxPrice;
private Integer pointTradeDeductMaxPrice;
@Schema(description = "1 元赠送多少分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "1 元赠送积分不能为空")
private Integer tradeGivePoint;
private Integer pointTradeGivePoint;
}

View File

@ -1,15 +1,15 @@
package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
package cn.iocoder.yudao.module.member.controller.admin.config.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 会员积分配置 Response VO")
@Schema(description = "管理后台 - 会员配置 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MemberPointConfigRespVO extends MemberPointConfigBaseVO {
public class MemberConfigRespVO extends MemberConfigBaseVO {
@Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;

View File

@ -0,0 +1,13 @@
package cn.iocoder.yudao.module.member.controller.admin.config.vo;
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 MemberConfigSaveReqVO extends MemberConfigBaseVO {
}

View File

@ -1,45 +0,0 @@
package cn.iocoder.yudao.module.member.controller.admin.point;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigRespVO;
import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
import cn.iocoder.yudao.module.member.service.point.MemberPointConfigService;
import io.swagger.v3.oas.annotations.Operation;
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.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 会员积分设置")
@RestController
@RequestMapping("/member/point/config")
@Validated
public class MemberPointConfigController {
@Resource
private MemberPointConfigService memberPointConfigService;
@PutMapping("/save")
@Operation(summary = "保存会员积分配置")
@PreAuthorize("@ss.hasPermission('point:config:save')")
public CommonResult<Boolean> savePointConfig(@Valid @RequestBody MemberPointConfigSaveReqVO saveReqVO) {
memberPointConfigService.savePointConfig(saveReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得会员积分配置")
@PreAuthorize("@ss.hasPermission('point:config:query')")
public CommonResult<MemberPointConfigRespVO> getPointConfig() {
MemberPointConfigDO config = memberPointConfigService.getPointConfig();
return success(MemberPointConfigConvert.INSTANCE.convert(config));
}
}

View File

@ -1,13 +0,0 @@
package cn.iocoder.yudao.module.member.controller.admin.point.vo.config;
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 MemberPointConfigSaveReqVO extends MemberPointConfigBaseVO {
}

View File

@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
/**
* 签到规则 Base VO提供给添加修改详细的子 VO 使用
@ -20,8 +21,14 @@ public class MemberSignInConfigBaseVO {
@Schema(description = "奖励积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "奖励积分不能为空")
@PositiveOrZero(message = "奖励积分不能小于 0")
private Integer point;
@Schema(description = "奖励经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "奖励经验不能为空")
@PositiveOrZero(message = "奖励经验不能小于 0")
private Integer experience;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
@InEnum(CommonStatusEnum.class)

View File

@ -21,7 +21,7 @@ public class MemberSignInRecordRespVO {
@Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer day;
@Schema(description = "签到的", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@Schema(description = "签到的", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer point;
@Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)

View File

@ -3,17 +3,16 @@ package cn.iocoder.yudao.module.member.controller.admin.user;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserRespVO;
import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateLevelReqVO;
import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
import cn.iocoder.yudao.module.member.controller.admin.user.vo.*;
import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
import cn.iocoder.yudao.module.member.service.tag.MemberTagService;
import cn.iocoder.yudao.module.member.service.user.MemberUserService;
import io.swagger.v3.oas.annotations.Operation;
@ -33,6 +32,7 @@ import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 会员用户")
@RestController
@ -48,6 +48,8 @@ public class MemberUserController {
private MemberLevelService memberLevelService;
@Resource
private MemberGroupService memberGroupService;
@Resource
private MemberPointRecordService memberPointRecordService;
@PutMapping("/update")
@Operation(summary = "更新会员用户")
@ -65,6 +67,23 @@ public class MemberUserController {
return success(true);
}
@PutMapping("/update-point")
@Operation(summary = "更新会员用户积分")
@PreAuthorize("@ss.hasPermission('member:user:update-point')")
public CommonResult<Boolean> updateUserPoint(@Valid @RequestBody MemberUserUpdatePointReqVO updateReqVO) {
memberPointRecordService.createPointRecord(updateReqVO.getId(), updateReqVO.getPoint(),
MemberPointBizTypeEnum.ADMIN, String.valueOf(getLoginUserId()));
return success(true);
}
@PutMapping("/update-balance")
@Operation(summary = "更新会员用户余额")
@PreAuthorize("@ss.hasPermission('member:user:update-balance')")
public CommonResult<Boolean> updateUserBalance(@Valid @RequestBody Long id) {
// todo @jason增加一个修改余额
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得会员用户")
@Parameter(name = "id", description = "编号", required = true, example = "1024")

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.member.controller.admin.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotBlank;
@ -10,9 +9,8 @@ import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 会员用户 修改等级 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MemberUserUpdateLevelReqVO extends MemberUserBaseVO {
public class MemberUserUpdateLevelReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
@NotNull(message = "用户编号不能为空")

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.member.controller.admin.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 会员用户 修改积分 Request VO")
@Data
@ToString(callSuper = true)
public class MemberUserUpdatePointReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
@NotNull(message = "用户编号不能为空")
private Long id;
@Schema(description = "变动积分,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "变动积分不能为空")
private Integer point;
}

View File

@ -1,13 +1,10 @@
package cn.iocoder.yudao.module.member.controller.app.signin;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -29,17 +26,8 @@ public class AppMemberSignInController {
// TODO @xiaqing合并到 AppMemberSignInRecordController getSignInRecordSummary 里哈
@Operation(summary = "个人签到信息")
@GetMapping("/get-summary")
public CommonResult getUserSummary(){
public CommonResult getUserSummary() {
return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
}
// TODO @xiaqing泛型
// TODO @xiaqing合并到 AppMemberSignInRecordController createSignInRecord 里哈
@Operation(summary = "会员签到")
@PostMapping("/create")
public CommonResult create(){
MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
}
}

View File

@ -18,7 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -50,16 +49,12 @@ public class AppMemberSignInRecordController {
return success(respVO);
}
// TODO 芋艿临时 mock => UserSignController.info
@PostMapping("/create")
@Operation(summary = "签到")
@PreAuthenticated
public CommonResult<AppMemberSignInRecordRespVO> createSignInRecord() {
AppMemberSignInRecordRespVO respVO = new AppMemberSignInRecordRespVO()
.setPoint(10)
.setDay(10)
.setCreateTime(LocalDateTime.now());
return success(respVO);
MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
}
@GetMapping("/page")

View File

@ -12,9 +12,12 @@ public class AppMemberSignInRecordRespVO {
@Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer day;
@Schema(description = "签到的", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@Schema(description = "签到的", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer point;
@Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer experience;
@Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -15,6 +15,9 @@ public class AppMemberSignInRecordRespVO {
@Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer point;
@Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer experience;
@Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.member.convert.config;
import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 会员配置 Convert
*
* @author QingX
*/
@Mapper
public interface MemberConfigConvert {
MemberConfigConvert INSTANCE = Mappers.getMapper(MemberConfigConvert.class);
MemberConfigRespVO convert(MemberConfigDO bean);
MemberConfigDO convert(MemberConfigSaveReqVO bean);
MemberConfigRespDTO convert01(MemberConfigDO config);
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.member.convert.point;
import cn.iocoder.yudao.module.member.api.point.dto.MemberPointConfigRespDTO;
import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigRespVO;
import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 会员积分配置 Convert
*
* @author QingX
*/
@Mapper
public interface MemberPointConfigConvert {
MemberPointConfigConvert INSTANCE = Mappers.getMapper(MemberPointConfigConvert.class);
MemberPointConfigRespVO convert(MemberPointConfigDO bean);
MemberPointConfigDO convert(MemberPointConfigSaveReqVO bean);
MemberPointConfigRespDTO convert01(MemberPointConfigDO pointConfig);
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.member.dal.dataobject.point;
package cn.iocoder.yudao.module.member.dal.dataobject.config;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
@ -7,19 +7,19 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 会员积分配置 DO
* 会员配置 DO
*
* @author QingX
*/
@TableName(value = "member_point_config", autoResultMap = true)
@KeySequence("member_point_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@TableName(value = "member_config", autoResultMap = true)
@KeySequence("member_config_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberPointConfigDO extends BaseDO {
public class MemberConfigDO extends BaseDO {
/**
* 自增主键
@ -29,20 +29,20 @@ public class MemberPointConfigDO extends BaseDO {
/**
* 积分抵扣开关
*/
private Boolean tradeDeductEnable;
private Boolean pointTradeDeductEnable;
/**
* 积分抵扣单位
*
* 1 积分抵扣多少分
*/
private Integer tradeDeductUnitPrice;
private Integer pointTradeDeductUnitPrice;
/**
* 积分抵扣最大值
*/
private Integer tradeDeductMaxPrice;
private Integer pointTradeDeductMaxPrice;
/**
* 1 元赠送多少分
*/
private Integer tradeGivePoint;
private Integer pointTradeGivePoint;
}

View File

@ -35,6 +35,10 @@ public class MemberSignInConfigDO extends BaseDO {
* 奖励积分
*/
private Integer point;
/**
* 奖励经验
*/
private Integer experience;
/**
* 状态

View File

@ -35,10 +35,12 @@ public class MemberSignInRecordDO extends BaseDO {
*/
private Integer day;
/**
* 签到的
* 签到的
*/
private Integer point;
// TODO 疯狂签到的经验
/**
* 签到的经验
*/
private Integer experience;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.member.dal.mysql.config;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 积分设置 Mapper
*
* @author QingX
*/
@Mapper
public interface MemberConfigMapper extends BaseMapperX<MemberConfigDO> {
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.member.dal.mysql.point;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 积分设置 Mapper
*
* @author QingX
*/
@Mapper
public interface MemberPointConfigMapper extends BaseMapperX<MemberPointConfigDO> {
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.member.mq.producer.user;
import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
import cn.iocoder.yudao.module.member.mq.message.user.RegisterCouponSendMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 新人券发放 Producer
*
* @author owen
*/
@Slf4j
@Component
public class RegisterCouponProducer {
@Resource
private RedisMQTemplate redisMQTemplate;
/**
* 发送 {@link RegisterCouponSendMessage} 消息
*
* @param userId 用户编号
*/
public void sendMailSendMessage(Long userId) {
redisMQTemplate.send(new RegisterCouponSendMessage().setUserId(userId));
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.member.service.config;
import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
import javax.validation.Valid;
/**
* 会员配置 Service 接口
*
* @author QingX
*/
public interface MemberConfigService {
/**
* 保存会员配置
*
* @param saveReqVO 更新信息
*/
void saveConfig(@Valid MemberConfigSaveReqVO saveReqVO);
/**
* 获得会员配置
*
* @return 积分配置
*/
MemberConfigDO getConfig();
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.member.service.config;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
import cn.iocoder.yudao.module.member.dal.mysql.config.MemberConfigMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
/**
* 会员配置 Service 实现类
*
* @author QingX
*/
@Service
@Validated
public class MemberConfigServiceImpl implements MemberConfigService {
@Resource
private MemberConfigMapper memberConfigMapper;
@Override
public void saveConfig(MemberConfigSaveReqVO saveReqVO) {
// 存在则进行更新
MemberConfigDO dbConfig = getConfig();
if (dbConfig != null) {
memberConfigMapper.updateById(MemberConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
return;
}
// 不存在则进行插入
memberConfigMapper.insert(MemberConfigConvert.INSTANCE.convert(saveReqVO));
}
@Override
public MemberConfigDO getConfig() {
List<MemberConfigDO> list = memberConfigMapper.selectList();
return CollectionUtils.getFirst(list);
}
}

View File

@ -239,7 +239,8 @@ public class MemberLevelServiceImpl implements MemberLevelService {
// 1. 创建经验记录
MemberUserDO user = memberUserService.getUser(userId);
int userExperience = NumberUtil.max(user.getExperience() + experience, 0); // 防止扣出负数
Integer userExperience = ObjUtil.defaultIfNull(user.getExperience(), 0);
userExperience = NumberUtil.max(userExperience + experience, 0); // 防止扣出负数
MemberLevelRecordDO levelRecord = new MemberLevelRecordDO()
.setUserId(user.getId())
.setExperience(experience)

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.member.service.point;
import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
import javax.validation.Valid;
/**
* 会员积分配置 Service 接口
*
* @author QingX
*/
public interface MemberPointConfigService {
/**
* 保存会员积分配置
*
* @param saveReqVO 更新信息
*/
void savePointConfig(@Valid MemberPointConfigSaveReqVO saveReqVO);
/**
* 获得会员积分配置
*
* @return 积分配置
*/
MemberPointConfigDO getPointConfig();
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.yudao.module.member.service.point;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.member.controller.admin.point.vo.config.MemberPointConfigSaveReqVO;
import cn.iocoder.yudao.module.member.convert.point.MemberPointConfigConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointConfigDO;
import cn.iocoder.yudao.module.member.dal.mysql.point.MemberPointConfigMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
/**
* 会员积分配置 Service 实现类
*
* @author QingX
*/
@Service
@Validated
public class MemberPointConfigServiceImpl implements MemberPointConfigService {
@Resource
private MemberPointConfigMapper memberPointConfigMapper;
@Override
public void savePointConfig(MemberPointConfigSaveReqVO saveReqVO) {
// 存在则进行更新
MemberPointConfigDO dbConfig = getPointConfig();
if (dbConfig != null) {
memberPointConfigMapper.updateById(MemberPointConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
return;
}
// 不存在则进行插入
memberPointConfigMapper.insert(MemberPointConfigConvert.INSTANCE.convert(saveReqVO));
}
@Override
public MemberPointConfigDO getPointConfig() {
List<MemberPointConfigDO> list = memberPointConfigMapper.selectList();
return CollectionUtils.getFirst(list);
}
}

View File

@ -66,6 +66,9 @@ public class MemberPointRecordServiceImpl implements MemberPointRecordService {
@Override
@Transactional(rollbackFor = Exception.class)
public void createPointRecord(Long userId, Integer point, MemberPointBizTypeEnum bizType, String bizId) {
if (point == 0) {
return;
}
// 1. 校验用户积分余额
MemberUserDO user = memberUserService.getUser(userId);
Integer userPoint = ObjectUtil.defaultIfNull(user.getPoint(), 0);

View File

@ -11,10 +11,10 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_CONFIG_EXISTS;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.SIGN_IN_CONFIG_NOT_EXISTS;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
/**
* 签到规则 Service 实现类
@ -30,6 +30,8 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
@Override
public Long createSignInConfig(MemberSignInConfigCreateReqVO createReqVO) {
// 校验奖励积分奖励经验
validatePointAndExperience(createReqVO.getPoint(), createReqVO.getExperience());
// 判断是否重复插入签到天数
validateSignInConfigDayDuplicate(createReqVO.getDay(), null);
@ -42,6 +44,8 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
@Override
public void updateSignInConfig(MemberSignInConfigUpdateReqVO updateReqVO) {
// 校验奖励积分奖励经验
validatePointAndExperience(updateReqVO.getPoint(), updateReqVO.getExperience());
// 校验存在
validateSignInConfigExists(updateReqVO.getId());
// 判断是否重复插入签到天数
@ -70,7 +74,7 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
* 校验 day 是否重复
*
* @param day
* @param id 编号只有更新的时候会传递
* @param id 编号只有更新的时候会传递
*/
private void validateSignInConfigDayDuplicate(Integer day, Long id) {
MemberSignInConfigDO config = memberSignInConfigMapper.selectByDay(day);
@ -84,13 +88,20 @@ public class MemberSignInConfigServiceImpl implements MemberSignInConfigService
}
}
private void validatePointAndExperience(Integer point, Integer experience) {
// 奖励积分经验 至少要配置一个否则没有意义
if (Objects.equals(point, 0) && Objects.equals(experience, 0)) {
throw exception(SIGN_IN_CONFIG_AWARD_EMPTY);
}
}
@Override
public MemberSignInConfigDO getSignInConfig(Long id) {
return memberSignInConfigMapper.selectById(id);
}
@Override
public List <MemberSignInConfigDO> getSignInConfigList() {
public List<MemberSignInConfigDO> getSignInConfigList() {
List<MemberSignInConfigDO> list = memberSignInConfigMapper.selectList();
list.sort(Comparator.comparing(MemberSignInConfigDO::getDay));
return list;

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.service.signin;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
@ -13,8 +14,13 @@ import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO
import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInConfigMapper;
import cn.iocoder.yudao.module.member.dal.mysql.signin.MemberSignInRecordMapper;
import cn.iocoder.yudao.module.member.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
@ -40,20 +46,24 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
private MemberSignInRecordMapper signInRecordMapper;
@Resource
private MemberSignInConfigMapper signInConfigMapper;
@Resource
private MemberPointRecordService pointRecordService;
@Resource
private MemberLevelService memberLevelService;
@Resource
private MemberUserApi memberUserApi;
@Override
public AppMemberSignInSummaryRespVO getSignInRecordSummary(Long userId) {
AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
AppMemberSignInSummaryRespVO vo = new AppMemberSignInSummaryRespVO();
vo.setTotalDay(0);
vo.setContinuousDay(0);
vo.setTodaySignIn(false);
//获取用户签到的记录按照天数倒序获取
List <MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
List<MemberSignInRecordDO> signInRecordDOList = signInRecordMapper.selectListByUserId(userId);
// TODO @xiaqingif 空的时候直接 return这样括号少逻辑更简洁
if(!CollectionUtils.isEmpty(signInRecordDOList)){
if (!CollectionUtils.isEmpty(signInRecordDOList)) {
//设置总签到天数
vo.setTotalDay(signInRecordDOList.size()); // TODO @xiaqing是不是不用读取 signInRecordDOList 所有的而是 count下然后另外再读取一条最后一条
//判断当天是否有签到复用校验方法
@ -61,11 +71,11 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
try {
validSignDay(signInRecordDOList.get(0));
vo.setTodaySignIn(false);
}catch (Exception e){
} catch (Exception e) {
vo.setTodaySignIn(true);
}
//如果当天签到了则说明连续签到天数有意义否则直接用默认值0
if(vo.getTodaySignIn()){
if (vo.getTodaySignIn()) {
//下方计算连续签到从2天开始此处直接设置一天连续签到
vo.setContinuousDay(1);
//判断连续签到天数
@ -73,10 +83,10 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
for (int i = 1; i < signInRecordDOList.size(); i++) {
//前一天减1等于当前天数则说明连续继续循环
LocalDate cur = signInRecordDOList.get(i).getCreateTime().toLocalDate();
LocalDate pre = signInRecordDOList.get(i-1).getCreateTime().toLocalDate();
if(1==daysBetween(cur,pre)){
vo.setContinuousDay(i+1);
}else{
LocalDate pre = signInRecordDOList.get(i - 1).getCreateTime().toLocalDate();
if (1 == daysBetween(cur, pre)) {
vo.setContinuousDay(i + 1);
} else {
break;
}
}
@ -87,16 +97,16 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
return vo;
}
private long daysBetween(LocalDate date1,LocalDate date2){
private long daysBetween(LocalDate date1, LocalDate date2) {
return ChronoUnit.DAYS.between(date1, date2);
}
@Override
public PageResult <MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
public PageResult<MemberSignInRecordDO> getSignInRecordPage(MemberSignInRecordPageReqVO pageReqVO) {
// 根据用户昵称查询出用户ids
Set <Long> userIds = null;
Set<Long> userIds = null;
if (StringUtils.isNotBlank(pageReqVO.getNickname())) {
List <MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
List<MemberUserRespDTO> users = memberUserApi.getUserListByNickname(pageReqVO.getNickname());
// 如果查询用户结果为空直接返回无需继续查询
if (CollectionUtils.isEmpty(users)) {
return PageResult.empty();
@ -113,50 +123,63 @@ public class MemberSignInRecordServiceImpl implements MemberSignInRecordService
}
@Override
@Transactional(rollbackFor = Exception.class)
public MemberSignInRecordDO createSignRecord(Long userId) {
// 获取当前用户签到的最大天数
// TODO @xiaqingdb 操作dou封装到 mapper
// TODO @xiaqingmaxSignDay是不是变量叫 lastRecord 会更容易理解哈
MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX <MemberSignInRecordDO>()
MemberSignInRecordDO maxSignDay = signInRecordMapper.selectOne(new LambdaQueryWrapperX<MemberSignInRecordDO>()
.eq(MemberSignInRecordDO::getUserId, userId)
.orderByDesc(MemberSignInRecordDO::getDay)
.last("limit 1"));
// 判断是否重复签到
validSignDay(maxSignDay);
// TODO @xiaqing可以使用 // 进行注释
/**1.查询出当前签到的天数**/
// 1. 查询出当前签到的天数
MemberSignInRecordDO sign = new MemberSignInRecordDO().setUserId(userId); // TODO @xiaqing应该使用 record 变量会更合适
sign.setDay(1); // 设置签到初始化天数
sign.setPoint(0); // 设置签到分数默认为 0
sign.setPoint(0); // 设置签到积分默认为 0
sign.setExperience(0); // 设置签到经验默认为 0
// 如果不为空则修改当前签到对应的天数
// TODO @xiaqing应该是要判断连续哈就是昨天
if (maxSignDay != null) {
sign.setDay(maxSignDay.getDay() + 1);
}
/**2.获取签到对应的分数**/
// 2. 获取签到对应的积分数
// 获取所有的签到规则按照天数排序只获取启用的 TODO @xiaqing不要使用 signInConfigMapper 直接查询而是要通过 SigninConfigService
List <MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX <MemberSignInConfigDO>()
List<MemberSignInConfigDO> configDOList = signInConfigMapper.selectList(new LambdaQueryWrapperX<MemberSignInConfigDO>()
.eq(MemberSignInConfigDO::getStatus, CommonStatusEnum.ENABLE.getStatus())
.orderByAsc(MemberSignInConfigDO::getDay));
// 如果签到的天数大于最大启用的规则天数直接给最大签到的分数
// 如果签到的天数大于最大启用的规则天数直接给最大签到的分数
// TODO @xiaqing超过最大配置的天数应该直接重置到第一天哈
MemberSignInConfigDO lastConfig = configDOList.get(configDOList.size() - 1);
if (sign.getDay() > lastConfig.getDay()) {
sign.setPoint(lastConfig.getPoint());
sign.setExperience(lastConfig.getExperience());
} else {
configDOList.forEach(el -> {
// 循环匹配对应天数设置对应分数
// 循环匹配对应天数设置对应分数
// TODO @xiaqing使用 equals另外这种不应该去遍历比较从可读性来说应该 CollUtil.findOne()
if (el.getDay() == sign.getDay()) {
sign.setPoint(el.getPoint());
sign.setExperience(el.getExperience());
}
});
}
// 3. 插入当前签到获取的分数
// 3. 插入签到记录
signInRecordMapper.insert(sign);
// 4. 增加积分
if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) {
pointRecordService.createPointRecord(userId, sign.getPoint(), MemberPointBizTypeEnum.SIGN, String.valueOf(sign.getId()));
}
// 5. 增加经验
if (!ObjectUtils.equalsAny(sign.getPoint(), null, 0)) {
memberLevelService.addExperience(userId, sign.getExperience(), MemberExperienceBizTypeEnum.SIGN_IN, String.valueOf(sign.getId()));
}
return sign;
}

View File

@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
import cn.iocoder.yudao.module.member.mq.producer.user.RegisterCouponProducer;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
@ -58,6 +59,9 @@ public class MemberUserServiceImpl implements MemberUserService {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private RegisterCouponProducer registerCouponProducer;
@Override
public MemberUserDO getUserByMobile(String mobile) {
return memberUserMapper.selectByMobile(mobile);
@ -89,6 +93,9 @@ public class MemberUserServiceImpl implements MemberUserService {
user.setPassword(encodePassword(password)); // 加密密码
user.setRegisterIp(registerIp);
memberUserMapper.insert(user);
// 发送 MQ 消息发放新人券
registerCouponProducer.sendMailSendMessage(user.getId());
return user;
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletUserReqVO;
import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 用户钱包")
@RestController
@RequestMapping("/pay/wallet")
@Validated
@Slf4j
public class PayWalletController {
@Resource
private PayWalletService payWalletService;
@GetMapping("/user-wallet")
@PreAuthorize("@ss.hasPermission('pay:wallet:query')")
@Operation(summary = "获得用户钱包明细")
public CommonResult<PayWalletRespVO> getByUser(PayWalletUserReqVO reqVO) {
PayWalletDO wallet = payWalletService.getWalletByUserIdAndType(reqVO.getUserId(), reqVO.getUserType());
return success(PayWalletConvert.INSTANCE.convert02(wallet));
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* 用户钱包 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class PayWalletBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户类型不能为空")
private Byte userType;
@Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "余额,单位分不能为空")
private Integer balance;
@Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "累计支出,单位分不能为空")
private Integer totalExpense;
@Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "累计充值,单位分不能为空")
private Integer totalRecharge;
@Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737")
@NotNull(message = "冻结金额,单位分不能为空")
private Integer freezePrice;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
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 PayWalletRespVO extends PayWalletBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528")
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 用户钱包明细 Request VO")
@Data
public class PayWalletUserReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户类型不能为空")
@InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.convert.wallet;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.PayWalletRespVO;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import org.mapstruct.Mapper;
@ -11,4 +12,6 @@ public interface PayWalletConvert {
PayWalletConvert INSTANCE = Mappers.getMapper(PayWalletConvert.class);
AppPayWalletRespVO convert(PayWalletDO bean);
PayWalletRespVO convert02(PayWalletDO wallet);
}

View File

@ -13,10 +13,10 @@ public interface PayWalletService {
/**
* 获取钱包信息
*
* <p>
* 如果不存在则创建钱包由于用户注册时候不会创建钱包
*
* @param userId 用户编号
* @param userId 用户编号
* @param userType 用户类型
*/
PayWalletDO getOrCreateWallet(Long userId, Integer userType);
@ -31,10 +31,10 @@ public interface PayWalletService {
/**
* 钱包订单支付
*
* @param userId 用户 id
* @param userType 用户类型
* @param userId 用户 id
* @param userType 用户类型
* @param outTradeNo 外部订单号
* @param price 金额
* @param price 金额
*/
PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
@ -43,17 +43,17 @@ public interface PayWalletService {
*
* @param outRefundNo 外部退款号
* @param refundPrice 退款金额
* @param reason 退款原因
* @param reason 退款原因
*/
PayWalletTransactionDO orderRefund(String outRefundNo, Integer refundPrice, String reason);
/**
* 扣减钱包余额
*
* @param walletId 钱包 id
* @param bizId 业务关联 id
* @param bizType 业务关联分类
* @param price 扣减金额
* @param walletId 钱包 id
* @param bizId 业务关联 id
* @param bizType 业务关联分类
* @param price 扣减金额
* @return 钱包流水
*/
PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
@ -63,9 +63,9 @@ public interface PayWalletService {
* 增加钱包余额
*
* @param walletId 钱包 id
* @param bizId 业务关联 id
* @param bizType 业务关联分类
* @param price 增加金额
* @param bizId 业务关联 id
* @param bizType 业务关联分类
* @param price 增加金额
* @return 钱包流水
*/
PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
@ -74,15 +74,25 @@ public interface PayWalletService {
/**
* 冻结钱包部分余额
*
* @param id 钱包编号
* @param id 钱包编号
* @param price 冻结金额
*/
void freezePrice(Long id, Integer price);
/**
* 解冻钱包余额
* @param id 钱包编号
*
* @param id 钱包编号
* @param price 解冻金额
*/
void unFreezePrice(Long id, Integer price);
/**
* 获得用户的钱包明细
*
* @param userId 用户编号
* @param userType 用户类型
* @return 用户的钱包明细
*/
PayWalletDO getWalletByUserIdAndType(Long userId, Integer userType);
}

View File

@ -195,4 +195,9 @@ public class PayWalletServiceImpl implements PayWalletService {
}
}
@Override
public PayWalletDO getWalletByUserIdAndType(Long userId, Integer userType) {
return walletMapper.selectByUserIdAndType(userId, userType);
}
}