diff --git a/.cursor/rules/1.mdc b/.cursor/rules/1.mdc deleted file mode 100644 index 19111e9..0000000 --- a/.cursor/rules/1.mdc +++ /dev/null @@ -1,48 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- ---- -description: -globs: -alwaysApply: false ---- - -# Your rule content -#角色 -你是一名精通开发的高级工程师,拥有10年以上的应用开发经验,熟悉*等开发工具和技术栈。 -你的任务是帮助用户设计和开发易用且易于推护的 *** 应用。始终遵循最佳实践,并坚持干净代码和健壮架构的原则。 - -#目标 -你的目标是以用户容易理解的方式帮助他们完成“应用的设计和开发工作,确保应用功能完善、性能优异、用户体验良好。 - -#要求 -在理解用户需求、设计UI、编写代码、解决问题和项目选代优化时,你应该始终遵循以下原则: - - -##需求理解 --充分理解用户需求,站在用户角度思考,分析需求是否存在缺漏,并与用户讨论完善需求; --选择最简单的解决方案来满足用户需求,避免过度设计。 -##UI和样式设计 --使用现代UI框架进行样式设计(例如***,这里可以根据不同开发项目仔纽展开,比如使用哪些视觉规范或者UI框架,没有的话也可以不用过多展开); --在不同平台上实现一致的设计和响应式模式 -##代码编写 -技术选型:根据项目需求选择合适的技术栈(例如***,这里需要仔细展开,比如介招某个技术栈用在什么地方,以及要遵循什么最佳实践) -代码结构:强调代码的清晰性、模块化、可维护性,遵循最佳实践(如DRY原则、最小权限原则、响应式设计等) --代码安全性:在编写代码时,始终考虑安全性,避免引入漏洞,确保用户输入的安全处理 --性能优化:优化代码的性能,减少资源占用,提升加载速度,确保项目的高效运行 --测试与文档:编写单元测试,确保代码的健壮性,并提供清晰的中文注释和文档。方便后续阅读和维护 -##问题解决 --全面阅读相关代码,理解***应用的工作原理 --根据用户的反馈分析问题的原因,提出解决问题的思路 --确保每次代码变更不会破坏现有功能,且尽可能保持最小的改动 -##迭代优化 -与用户保持密切沟通,根据反读调整功能和设计,确保应用符合用户需求 -在不确定需求时,主动询问用户以澄清需求或技术细节 -##方法论 --系统2思维:以分析严谨的方式解决问题。将需求分解为更小、可管理的部分,并在实施前仔细考虑每一步 -思维树:评估多种可能的解决方案及其后果。使用结构化的方法探索不同的路径。并选择最优的解决方案 --选代改进:在最终确定代码之前,考虑改进、边缘情况和优化。通过潜在增强的迭代,确保最终解决方案是健壮的 - - diff --git a/.gitignore b/.gitignore index fc5b4bf..ce7c267 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ target/* /.nb-gradle/ /build/ .idea +.cursor +.lingma diff --git a/.lingma/rules/project_rule.md b/.lingma/rules/project_rule.md deleted file mode 100644 index aea8af4..0000000 --- a/.lingma/rules/project_rule.md +++ /dev/null @@ -1,2 +0,0 @@ -**添加规则文件可帮助模型精准理解你的编码偏好,如框架、代码风格等** -**规则文件只对当前工程生效,单文件限制10000字符。如果无需将该文件提交到远程 Git 仓库,请将其添加到 .gitignore** \ No newline at end of file diff --git a/logs/gateway-server.log.2025-05-22.0.gz b/logs/gateway-server.log.2025-05-22.0.gz deleted file mode 100644 index 4e92a6e..0000000 Binary files a/logs/gateway-server.log.2025-05-22.0.gz and /dev/null differ diff --git a/logs/gateway-server.log.2025-05-23.0.gz b/logs/gateway-server.log.2025-05-23.0.gz deleted file mode 100644 index da74149..0000000 Binary files a/logs/gateway-server.log.2025-05-23.0.gz and /dev/null differ diff --git a/logs/infra-server.log.2025-07-25.0.gz b/logs/infra-server.log.2025-07-25.0.gz deleted file mode 100644 index 16fd04f..0000000 Binary files a/logs/infra-server.log.2025-07-25.0.gz and /dev/null differ diff --git a/logs/infra-server.log.2025-07-28.0.gz b/logs/infra-server.log.2025-07-28.0.gz deleted file mode 100644 index 1aa7573..0000000 Binary files a/logs/infra-server.log.2025-07-28.0.gz and /dev/null differ diff --git a/logs/system-server.log.2025-05-22.0.gz b/logs/system-server.log.2025-05-22.0.gz deleted file mode 100644 index 63e381e..0000000 Binary files a/logs/system-server.log.2025-05-22.0.gz and /dev/null differ diff --git a/logs/system-server.log.2025-05-23.0.gz b/logs/system-server.log.2025-05-23.0.gz deleted file mode 100644 index 7015e55..0000000 Binary files a/logs/system-server.log.2025-05-23.0.gz and /dev/null differ diff --git a/logs/system-server.log.2025-07-28.0.gz b/logs/system-server.log.2025-07-28.0.gz deleted file mode 100644 index 1f4fd79..0000000 Binary files a/logs/system-server.log.2025-07-28.0.gz and /dev/null differ diff --git a/pom.xml b/pom.xml index 14c7e35..58d6d26 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,22 @@ + + + prod + + + + src/main/resources + + *.yaml + + + + + + + diff --git a/sql/db2/README.md b/sql/db2/README.md deleted file mode 100644 index d9a95a8..0000000 --- a/sql/db2/README.md +++ /dev/null @@ -1,3 +0,0 @@ -暂未适配 IBM DB2 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。 - -你需要把表结构与数据导入到 DM 数据库,我a来测试与适配代码。 diff --git a/sql/mysql/ai-manage.sql b/sql/mysql/ai-manage.sql new file mode 100644 index 0000000..3cc9615 --- /dev/null +++ b/sql/mysql/ai-manage.sql @@ -0,0 +1,113 @@ +DROP TABLE IF EXISTS `tz_ai_sample`; +CREATE TABLE `tz_ai_sample` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `sample_file_id` bigint NOT NULL COMMENT '样本文件id', + `sample_name` varchar(64) NULL DEFAULT '' COMMENT '样本名称', + `sample_time` varchar(16) NULL DEFAULT '' COMMENT '样本时长', + `sample_mine_type` varchar(16) NULL DEFAULT '' COMMENT '样本格式', + `sample_size` varchar(16) NULL DEFAULT '' COMMENT '样本大小', + `remark` varchar(255) NULL DEFAULT '' COMMENT '样本注释', + `creator` varchar(64) NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_create_time` (`create_time` ASC) USING BTREE +) ENGINE = InnoDB COMMENT = '样本库'; + + +DROP TABLE IF EXISTS `tz_ai_sample_tag_relate`; +CREATE TABLE `tz_ai_sample_tag_relate` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `sample_id` bigint NOT NULL COMMENT '样本id', + `sample_tag_id` bigint NOT NULL COMMENT '样本标签id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB COMMENT = '样本-标签关联表'; + + + +DROP TABLE IF EXISTS `tz_ai_sample_tag`; +CREATE TABLE `tz_ai_sample_tag` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tag_name` varchar(64) NULL DEFAULT '' COMMENT '标签名称', + `creator` varchar(64) NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_create_time` (`create_time` ASC) USING BTREE +) ENGINE = InnoDB COMMENT = '样本标签库'; + +DROP TABLE IF EXISTS `tz_ai_sample_tag_group`; +CREATE TABLE `tz_ai_sample_tag_group` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `group_name` varchar(64) NULL DEFAULT '' COMMENT '分组名称', + `creator` varchar(64) NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_create_time` (`create_time` ASC) USING BTREE +) ENGINE = InnoDB COMMENT = '样本标签分组库'; + + + +DROP TABLE IF EXISTS `tz_ai_sample_tag_group_relate`; +CREATE TABLE `tz_ai_sample_tag_group_relate` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `sample_tag_id` bigint NOT NULL COMMENT '样本标签id', + `sample_tag_group_id` bigint NOT NULL COMMENT '样本标签分组id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB COMMENT = '样本标签-分组关联表'; + + +DROP TABLE IF EXISTS `tz_ai_dialog`; +CREATE TABLE `tz_ai_dialog` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `title` varchar(128) DEFAULT '' COMMENT '对话标题', + `user_id` bigint NOT NULL COMMENT '用户id', + `dialog_status` int DEFAULT NULL COMMENT '对话状态(1active, 2archived, 3deleted)', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB COMMENT ='ai-对话表'; + +DROP TABLE IF EXISTS `tz_ai_dialog_message`; +CREATE TABLE `tz_ai_dialog_message` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `dialog_id` bigint NOT NULL COMMENT '对话id', + `content_text` text COMMENT '内容', + `content_type` int DEFAULT NULL COMMENT '文本类型(1text,2file)', + `message_order` int DEFAULT NULL COMMENT '对话中的顺序', + `message_status` int DEFAULT NULL COMMENT '消息状态 1正常 0删除', + `pet_id` bigint DEFAULT NULL COMMENT '宠物id', + `pet_name` varchar(255) DEFAULT NULL COMMENT '宠物名称', + `pet_avatar` varchar(255) DEFAULT NULL COMMENT '宠物头像', + `pet_type` varchar(64) DEFAULT NULL COMMENT '宠物类型', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `trans_result` text COMMENT '回答结果', + `content_duration` bigint DEFAULT NULL COMMENT '文件时长', + `trans_status` int DEFAULT NULL COMMENT '翻译状态(1成功 0失败)', + `source_result` text COMMENT '原始结果', + `file_name` varchar(255) DEFAULT NULL COMMENT '文件名称', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB COMMENT ='ai-对话消息表'; + + diff --git a/sql/mysql/member.sql b/sql/mysql/member.sql new file mode 100644 index 0000000..873a6f6 --- /dev/null +++ b/sql/mysql/member.sql @@ -0,0 +1,51 @@ +-- 会员用户表 (tz_member_user) +CREATE TABLE `tz_member_user` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `mobile` varchar(11) NOT NULL COMMENT '手机', + `password` varchar(128) NOT NULL COMMENT '加密后的密码', + `status` tinyint(4) NOT NULL COMMENT '帐号状态 (枚举 CommonStatusEnum)', + `register_ip` varchar(32) DEFAULT NULL COMMENT '注册 IP', + `register_terminal` tinyint(4) DEFAULT NULL COMMENT '注册终端 (枚举 TerminalEnum)', + `login_ip` varchar(32) DEFAULT NULL COMMENT '最后登录IP', + `login_date` datetime DEFAULT NULL COMMENT '最后登录时间', + `nickname` varchar(64) DEFAULT NULL COMMENT '用户昵称', + `avatar` varchar(512) DEFAULT NULL COMMENT '用户头像', + `name` varchar(64) DEFAULT NULL COMMENT '真实名字', + `sex` tinyint(4) DEFAULT NULL COMMENT '性别 (枚举 SexEnum)', + `birthday` datetime DEFAULT NULL COMMENT '出生日期', + `area_id` int(11) DEFAULT NULL COMMENT '所在地 (关联 Area.id 字段)', + `mark` varchar(512) DEFAULT NULL COMMENT '用户备注', + `point` int(11) DEFAULT NULL COMMENT '积分', + `tag_ids` varchar(512) DEFAULT NULL COMMENT '会员标签列表,以逗号分隔', + `level_id` bigint(20) DEFAULT NULL COMMENT '会员级别编号 (关联 MemberLevelDO.id 字段)', + `experience` int(11) DEFAULT NULL COMMENT '会员经验', + `group_id` bigint(20) DEFAULT NULL COMMENT '用户分组编号 (关联 MemberGroupDO.id 字段)', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_mobile` (`mobile`), + KEY `idx_level_id` (`level_id`), + KEY `idx_group_id` (`group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员用户表'; + +-- 用户收件地址表 (tz_member_address) +CREATE TABLE `tz_member_address` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint(20) NOT NULL COMMENT '用户编号', + `name` varchar(64) NOT NULL COMMENT '收件人名称', + `mobile` varchar(11) NOT NULL COMMENT '手机号', + `area_id` bigint(20) NOT NULL COMMENT '地区编号', + `detail_address` varchar(512) NOT NULL COMMENT '收件详细地址', + `default_status` tinyint(1) DEFAULT NULL COMMENT '是否默认 (true - 默认收件地址)', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户收件地址表'; \ No newline at end of file diff --git a/sql/mysql/order.sql b/sql/mysql/order.sql new file mode 100644 index 0000000..0d45660 --- /dev/null +++ b/sql/mysql/order.sql @@ -0,0 +1,309 @@ +-- 基础实体表结构 (BaseDO) 不单独建表,字段会被继承到其他表中 +CREATE TABLE `tz_trade_order` +( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单编号,主键自增', + `order_num` varchar(64) NOT NULL COMMENT '订单流水号', + `order_category_id` bigint NOT NULL COMMENT '订单类目id', + `order_category_name` varchar(64) NOT NULL COMMENT '订单类目名称', + `order_type` tinyint NOT NULL COMMENT '订单类型 (枚举 TradeOrderTypeEnum)', + `order_terminal` tinyint NOT NULL COMMENT '订单来源 (枚举 TerminalEnum)', + `order_status` tinyint NOT NULL COMMENT '订单状态 (枚举 TradeOrderStatusEnum)', + `user_id` bigint NOT NULL COMMENT '用户编号', + `user_ip` varchar(64) DEFAULT NULL COMMENT '用户IP', + `user_name` varchar(64) DEFAULT NULL COMMENT '用户昵称', + `user_mobile` varchar(11) DEFAULT NULL COMMENT '用户手机号', + `user_remark` varchar(512) DEFAULT NULL COMMENT '用户备注', + `finish_time` datetime DEFAULT NULL COMMENT '订单完成时间', + `cancel_time` datetime DEFAULT NULL COMMENT '订单取消时间', + `cancel_type` tinyint DEFAULT NULL COMMENT '取消类型 (枚举 TradeOrderCancelTypeEnum)', + `cancel_reason` varchar(128) DEFAULT NULL COMMENT '取消原因', + `merchant_id` bigint DEFAULT NULL COMMENT '商家编号', + `merchant_name` varchar(64) DEFAULT NULL COMMENT '商家名称', + `merchant_remark` varchar(512) DEFAULT NULL COMMENT '商家备注', + `comment_status` tinyint(1) DEFAULT NULL COMMENT '是否评价 (true-已评价, false-未评价)', + `expense_price` int(11) DEFAULT NULL COMMENT '商品成本(单),单位:分', + `price` int(11) DEFAULT NULL COMMENT '商品原价(单),单位:分', + `discount_price` int(11) DEFAULT NULL COMMENT '优惠金额(总),单位:分', + `delivery_price` int(11) DEFAULT NULL COMMENT '运费金额(总),单位:分', + `adjust_price` int(11) DEFAULT NULL COMMENT '订单调价(总),单位:分', + `pay_price` int(11) DEFAULT NULL COMMENT '应付金额(总),单位:分', + `live_price` int(11) DEFAULT NULL COMMENT '实收金额(总),单位:分', + `pay_order_id` bigint DEFAULT NULL COMMENT '支付订单编号', + `pay_status` tinyint(1) DEFAULT NULL COMMENT '是否已支付 (true-已支付, false-未支付)', + `pay_type` tinyint DEFAULT NULL COMMENT '支付方式(PayTypeEnum)', + `pay_time` datetime DEFAULT NULL COMMENT '付款时间', + `pay_channel_code` tinyint DEFAULT NULL COMMENT '支付渠道(PayTypeEnum)', + `delivery_type` tinyint DEFAULT NULL COMMENT '配送方式 (枚举 DeliveryTypeEnum)', + `logistics_id` bigint DEFAULT '0' COMMENT '发货物流公司编号', + `logistics_no` varchar(64) DEFAULT '' COMMENT '发货物流单号', + `delivery_time` datetime DEFAULT NULL COMMENT '发货时间', + `receive_time` datetime DEFAULT NULL COMMENT '收货时间', + `receiver_name` varchar(64) DEFAULT NULL COMMENT '收件人名称', + `receiver_mobile` varchar(20) DEFAULT NULL COMMENT '收件人手机', + `receiver_area_id` int DEFAULT NULL COMMENT '收件人地区编号', + `receiver_detail_address` varchar(512) DEFAULT NULL COMMENT '收件人详细地址', + `pick_up_store_id` bigint DEFAULT NULL COMMENT '自提门店编号', + `pick_up_verify_code` varchar(64) DEFAULT NULL COMMENT '自提核销码', + `refund_status` tinyint DEFAULT NULL COMMENT '退款状态 (枚举 TradeOrderRefundStatusEnum)', + `refund_price` int DEFAULT NULL COMMENT '退款金额,单位:分', + `refund_time` datetime DEFAULT NULL COMMENT '退款时间', + `after_sale_id` bigint DEFAULT NULL COMMENT '售后单编号', + `after_sale_status` tinyint DEFAULT NULL COMMENT '售后状态 (枚举 TradeOrderItemAfterSaleStatusEnum)', + `finance_id` bigint DEFAULT NULL COMMENT '财务单编号', + `finance_status` tinyint DEFAULT NULL COMMENT '财务状态 (枚举 TradeOrderItemFinanceEnum)', + `version` int NOT NULL DEFAULT '0' COMMENT '版本号(乐观锁)', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '最后更新时间', + `creator` varchar(64) DEFAULT NULL COMMENT '创建者', + `updater` varchar(64) DEFAULT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_order_num` (`order_num`), + KEY `idx_user_id` (`user_id`), + KEY `idx_status` (`order_status`), + KEY `idx_pay_status` (`pay_status`) +) ENGINE = InnoDB + AUTO_INCREMENT = 2 + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_0900_ai_ci COMMENT ='交易订单表'; + + +-- 交易订单项表 (trade_order_item) +CREATE TABLE `tz_trade_order_item` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint(20) NOT NULL COMMENT '用户编号', + `order_id` bigint(20) NOT NULL COMMENT '订单编号', + `cart_id` bigint(20) DEFAULT NULL COMMENT '购物车项编号', + `spu_id` bigint(20) NOT NULL COMMENT '商品 SPU 编号', + `spu_name` varchar(256) NOT NULL COMMENT '商品 SPU 名称', + `spu_type` tinyint(1) NOT NULL COMMENT '商品类型(1商品 2服务)', + `sku_id` bigint(20) NOT NULL COMMENT '商品 SKU 编号', + `sku_name` bigint(20) NOT NULL COMMENT '商品 SKU 名称', + `pic_url` varchar(512) NOT NULL COMMENT '商品图片', + `count` int(11) NOT NULL COMMENT '购买数量', + `unit` varchar(16) NOT NULL COMMENT '商品单位', + `expense_price` int(11) NOT NULL COMMENT '商品成本(单),单位:分', + `price` int(11) NOT NULL COMMENT '商品原价(单),单位:分', + `discount_price` int(11) NOT NULL COMMENT '优惠金额(总),单位:分', + `delivery_price` int(11) NOT NULL COMMENT '运费金额(总),单位:分', + `adjust_price` int(11) NOT NULL COMMENT '订单调价(总),单位:分', + `pay_price` int(11) NOT NULL COMMENT '应付金额(总),单位:分', + `live_price` int(11) NOT NULL COMMENT '实收金额(总),单位:分', + `sub_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '预约类型(1预约 2 加急)', + `sub_time` datetime NOT NULL COMMENT '预约时间', + `serve_address` datetime NOT NULL COMMENT '服务地址', + `properties` json DEFAULT NULL COMMENT '属性数组', + `serve_info` json DEFAULT NULL COMMENT '服务信息', + `serve_ext_info` json DEFAULT NULL COMMENT '扩展服务信息,存储额外的服务相关数据', + `price_ext_info` json DEFAULT NULL COMMENT '附加费信息', + `version` int(11) DEFAULT '0' COMMENT '版本号(乐观锁)', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_sku_id` (`sku_id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='交易订单项表'; + + +-- 订单日志表 (trade_order_log) +CREATE TABLE `tz_trade_order_log` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint(20) NOT NULL COMMENT '用户编号', + `user_type` tinyint(4) NOT NULL COMMENT '用户类型 (枚举 UserTypeEnum)', + `order_id` bigint(20) NOT NULL COMMENT '订单号', + `before_status` int(11) DEFAULT NULL COMMENT '操作前状态', + `after_status` int(11) DEFAULT NULL COMMENT '操作后状态', + `operate_type` tinyint(4) NOT NULL COMMENT '操作类型 (枚举 TradeOrderOperateTypeEnum)', + `content` varchar(1024) NOT NULL COMMENT '订单日志信息', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_user_id` (`user_id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='订单日志表'; + +-- 售后订单表 (tz_trade_after_sale) +CREATE TABLE `tz_trade_after_sale` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '售后编号,主键自增', + `no` varchar(64) NOT NULL COMMENT '售后单号', + `status` tinyint(4) NOT NULL COMMENT '退款状态 (枚举 AfterSaleStatusEnum)', + `way` tinyint(4) NOT NULL COMMENT '售后方式 (枚举 AfterSaleWayEnum)', + `type` tinyint(4) NOT NULL COMMENT '售后类型 (枚举 AfterSaleTypeEnum)', + `user_id` bigint(20) NOT NULL COMMENT '用户编号', + `apply_reason` varchar(256) NOT NULL COMMENT '申请原因', + `apply_description` varchar(512) DEFAULT NULL COMMENT '补充描述', + `apply_pic_urls` json DEFAULT NULL COMMENT '补充凭证图片', + `order_id` bigint(20) NOT NULL COMMENT '交易订单编号', + `order_no` varchar(64) NOT NULL COMMENT '订单流水号', + `order_item_id` bigint(20) NOT NULL COMMENT '交易订单项编号', + `prod_id` bigint(20) NOT NULL COMMENT '商品 SPU 编号', + `prod_name` varchar(256) NOT NULL COMMENT '商品 SPU 名称', + `sku_id` bigint(20) NOT NULL COMMENT '商品 SKU 编号', + `properties` json DEFAULT NULL COMMENT '属性数组', + `pic_url` varchar(512) DEFAULT NULL COMMENT '商品图片', + `count` int(11) NOT NULL COMMENT '退货商品数量', + `audit_time` datetime DEFAULT NULL COMMENT '审批时间', + `audit_user_id` bigint(20) DEFAULT NULL COMMENT '审批人', + `audit_reason` varchar(512) DEFAULT NULL COMMENT '审批备注', + `refund_price` int(11) NOT NULL COMMENT '退款金额,单位:分', + `pay_refund_id` bigint(20) DEFAULT NULL COMMENT '支付退款编号', + `refund_time` datetime DEFAULT NULL COMMENT '退款时间', + `logistics_id` bigint(20) DEFAULT NULL COMMENT '退货物流公司编号', + `logistics_no` varchar(64) DEFAULT NULL COMMENT '退货物流单号', + `delivery_time` datetime DEFAULT NULL COMMENT '退货时间', + `receive_time` datetime DEFAULT NULL COMMENT '收货时间', + `receive_reason` varchar(512) DEFAULT NULL COMMENT '收货备注', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_no` (`no`), + KEY `idx_user_id` (`user_id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_order_item_id` (`order_item_id`), + KEY `idx_status` (`status`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='售后订单表'; + +-- 交易售后日志表 (tz_trade_after_sale_log) +CREATE TABLE `tz_trade_after_sale_log` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint(20) NOT NULL COMMENT '用户编号', + `user_type` tinyint(4) NOT NULL COMMENT '用户类型 (枚举 UserTypeEnum)', + `after_sale_id` bigint(20) NOT NULL COMMENT '售后编号', + `before_status` tinyint(4) DEFAULT NULL COMMENT '操作前状态', + `after_status` tinyint(4) DEFAULT NULL COMMENT '操作后状态', + `operate_type` tinyint(4) NOT NULL COMMENT '操作类型 (枚举 AfterSaleOperateTypeEnum)', + `content` varchar(512) NOT NULL COMMENT '操作明细', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_after_sale_id` (`after_sale_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_operate_type` (`operate_type`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='交易售后日志表'; + + +-- 快递公司表 (trade_delivery_express) +CREATE TABLE `tz_trade_delivery_express` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号,自增', + `code` varchar(64) NOT NULL COMMENT '快递公司 code', + `name` varchar(128) NOT NULL COMMENT '快递公司名称', + `logo` varchar(512) DEFAULT NULL COMMENT '快递公司 logo', + `sort` int(11) DEFAULT NULL COMMENT '排序', + `status` tinyint(4) NOT NULL COMMENT '状态 (枚举 CommonStatusEnum)', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='快递公司表'; + +-- 快递运费模板表 (trade_delivery_express_template) +CREATE TABLE `tz_trade_delivery_express_template` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号,自增', + `name` varchar(128) NOT NULL COMMENT '模板名称', + `charge_mode` tinyint(4) NOT NULL COMMENT '配送计费方式 (枚举 DeliveryExpressChargeModeEnum)', + `sort` int(11) DEFAULT NULL COMMENT '排序', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='快递运费模板表'; + +-- 快递运费模板计费配置表 (trade_delivery_express_template_charge) +CREATE TABLE `tz_trade_delivery_express_template_charge` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号,自增', + `template_id` bigint(20) NOT NULL COMMENT '配送模板编号', + `area_ids` varchar(512) NOT NULL COMMENT '配送区域编号列表', + `charge_mode` tinyint(4) NOT NULL COMMENT '配送计费方式', + `start_count` double NOT NULL COMMENT '首件数量(件数,重量,或体积)', + `start_price` int(11) NOT NULL COMMENT '起步价,单位:分', + `extra_count` double NOT NULL COMMENT '续件数量(件, 重量,或体积)', + `extra_price` int(11) NOT NULL COMMENT '额外价,单位:分', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_template_id` (`template_id`), + CONSTRAINT `fk_express_template_charge_template_id` FOREIGN KEY (`template_id`) REFERENCES `tz_trade_delivery_express_template` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='快递运费模板计费配置表'; + +-- 快递运费模板包邮配置表 (trade_delivery_express_template_free) +CREATE TABLE `tz_trade_delivery_express_template_free` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `template_id` bigint(20) NOT NULL COMMENT '配送模板编号', + `area_ids` varchar(512) NOT NULL COMMENT '配送区域编号列表', + `free_price` int(11) DEFAULT NULL COMMENT '包邮金额,单位:分', + `free_count` int(11) DEFAULT NULL COMMENT '包邮件数', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_template_id` (`template_id`), + CONSTRAINT `fk_express_template_free_template_id` FOREIGN KEY (`template_id`) REFERENCES `tz_trade_delivery_express_template` (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='快递运费模板包邮配置表'; + +-- 自提门店表 (trade_delivery_pick_up_store) +CREATE TABLE `tz_trade_delivery_pick_up_store` +( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `name` varchar(128) NOT NULL COMMENT '门店名称', + `introduction` varchar(512) DEFAULT NULL COMMENT '门店简介', + `phone` varchar(20) NOT NULL COMMENT '门店手机', + `area_id` int(11) NOT NULL COMMENT '区域编号', + `detail_address` varchar(512) NOT NULL COMMENT '门店详细地址', + `logo` varchar(512) DEFAULT NULL COMMENT '门店 logo', + `opening_time` time NOT NULL COMMENT '营业开始时间', + `closing_time` time NOT NULL COMMENT '营业结束时间', + `latitude` double DEFAULT NULL COMMENT '纬度', + `longitude` double DEFAULT NULL COMMENT '经度', + `verify_user_ids` varchar(512) DEFAULT NULL COMMENT '核销员工用户编号数组', + `status` tinyint(4) NOT NULL COMMENT '门店状态 (枚举 CommonStatusEnum)', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_area_id` (`area_id`), + KEY `idx_status` (`status`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='自提门店表'; + + diff --git a/sql/mysql/pay.sql b/sql/mysql/pay.sql new file mode 100644 index 0000000..d3d093e --- /dev/null +++ b/sql/mysql/pay.sql @@ -0,0 +1,211 @@ +-- 支付应用表 (tz_pay_app) +CREATE TABLE `tz_pay_app` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '应用编号,数据库自增', + `app_key` varchar(64) NOT NULL COMMENT '应用标识', + `name` varchar(128) NOT NULL COMMENT '应用名', + `status` tinyint(4) NOT NULL COMMENT '状态 (枚举 CommonStatusEnum)', + `remark` varchar(512) DEFAULT NULL COMMENT '备注', + `order_notify_url` varchar(512) DEFAULT NULL COMMENT '支付结果的回调地址', + `refund_notify_url` varchar(512) DEFAULT NULL COMMENT '退款结果的回调地址', + `transfer_notify_url` varchar(512) DEFAULT NULL COMMENT '转账结果的回调地址', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_app_key` (`app_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付应用表'; + +-- 支付渠道表 (tz_pay_channel) +CREATE TABLE `tz_pay_channel` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '渠道编号,数据库自增', + `code` varchar(32) NOT NULL COMMENT '渠道编码 (枚举 PayChannelEnum)', + `status` tinyint(4) NOT NULL COMMENT '状态 (枚举 CommonStatusEnum)', + `fee_rate` double DEFAULT NULL COMMENT '渠道费率,单位:百分比', + `remark` varchar(512) DEFAULT NULL COMMENT '备注', + `app_id` bigint(20) NOT NULL COMMENT '应用编号 (关联 tz_pay_app.id)', + `config` json DEFAULT NULL COMMENT '支付渠道配置', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code_app_id` (`code`,`app_id`), + KEY `idx_app_id` (`app_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付渠道表'; + +-- 支付通知日志表 (tz_pay_notify_log) +CREATE TABLE `tz_pay_notify_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志编号,自增', + `task_id` bigint(20) NOT NULL COMMENT '通知任务编号 (关联 tz_pay_notify_task.id)', + `notify_times` int(11) NOT NULL COMMENT '第几次被通知', + `response` varchar(1024) DEFAULT NULL COMMENT 'HTTP 响应结果', + `status` tinyint(4) NOT NULL COMMENT '支付通知状态 (枚举 PayNotifyStatusEnum)', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_task_id` (`task_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付通知日志表'; + +-- 支付通知任务表 (tz_pay_notify_task) +CREATE TABLE `tz_pay_notify_task` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号,自增', + `app_id` bigint(20) NOT NULL COMMENT '应用编号 (关联 tz_pay_app.id)', + `type` tinyint(4) NOT NULL COMMENT '通知类型 (枚举 PayNotifyTypeEnum)', + `data_id` bigint(20) NOT NULL COMMENT '数据编号', + `merchant_order_id` varchar(64) DEFAULT NULL COMMENT '商户订单编号', + `merchant_transfer_id` varchar(64) DEFAULT NULL COMMENT '商户转账单编号', + `status` tinyint(4) NOT NULL COMMENT '通知状态 (枚举 PayNotifyStatusEnum)', + `next_notify_time` datetime DEFAULT NULL COMMENT '下一次通知时间', + `last_execute_time` datetime DEFAULT NULL COMMENT '最后一次执行时间', + `notify_times` int(11) DEFAULT NULL COMMENT '当前通知次数', + `max_notify_times` int(11) DEFAULT NULL COMMENT '最大可通知次数', + `notify_url` varchar(512) NOT NULL COMMENT '通知地址', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + KEY `idx_app_id` (`app_id`), + KEY `idx_type_data_id` (`type`,`data_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付通知任务表'; + +-- 支付订单表 (tz_pay_order) +CREATE TABLE `tz_pay_order` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单编号,数据库自增', + `app_id` bigint(20) NOT NULL COMMENT '应用编号 (关联 tz_pay_app.id)', + `channel_id` bigint(20) NOT NULL COMMENT '渠道编号 (关联 tz_pay_channel.id)', + `channel_code` varchar(32) NOT NULL COMMENT '渠道编码 (枚举 PayChannelEnum)', + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号', + `subject` varchar(256) NOT NULL COMMENT '商品标题', + `body` varchar(512) DEFAULT NULL COMMENT '商品描述信息', + `notify_url` varchar(512) DEFAULT NULL COMMENT '异步通知地址', + `price` int(11) NOT NULL COMMENT '支付金额,单位:分', + `channel_fee_rate` double DEFAULT NULL COMMENT '渠道手续费,单位:百分比', + `channel_fee_price` int(11) DEFAULT NULL COMMENT '渠道手续金额,单位:分', + `status` tinyint(4) NOT NULL COMMENT '支付状态 (枚举 PayOrderStatusEnum)', + `user_ip` varchar(32) DEFAULT NULL COMMENT '用户 IP', + `expire_time` datetime DEFAULT NULL COMMENT '订单失效时间', + `success_time` datetime DEFAULT NULL COMMENT '订单支付成功时间', + `extension_id` bigint(20) DEFAULT NULL COMMENT '支付成功的订单拓展单编号 (关联 tz_pay_order_extension.id)', + `no` varchar(64) DEFAULT NULL COMMENT '支付成功的外部订单号 (关联 tz_pay_order_extension.no)', + `refund_price` int(11) DEFAULT NULL COMMENT '退款总金额,单位:分', + `channel_user_id` varchar(128) DEFAULT NULL COMMENT '渠道用户编号', + `channel_order_no` varchar(128) DEFAULT NULL COMMENT '渠道订单号', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_merchant_order_id_app_id` (`merchant_order_id`,`app_id`), + KEY `idx_app_id` (`app_id`), + KEY `idx_channel_id` (`channel_id`), + KEY `idx_status` (`status`), + KEY `idx_extension_id` (`extension_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表'; + +-- 支付订单扩展表 (tz_pay_order_extension) +CREATE TABLE `tz_pay_order_extension` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单拓展编号,数据库自增', + `no` varchar(64) NOT NULL COMMENT '外部订单号', + `order_id` bigint(20) NOT NULL COMMENT '订单号 (关联 tz_pay_order.id)', + `channel_id` bigint(20) NOT NULL COMMENT '渠道编号 (关联 tz_pay_channel.id)', + `channel_code` varchar(32) NOT NULL COMMENT '渠道编码', + `user_ip` varchar(32) DEFAULT NULL COMMENT '用户 IP', + `status` tinyint(4) NOT NULL COMMENT '支付状态 (枚举 PayOrderStatusEnum)', + `channel_extras` json DEFAULT NULL COMMENT '支付渠道的额外参数', + `channel_error_code` varchar(64) DEFAULT NULL COMMENT '调用渠道的错误码', + `channel_error_msg` varchar(512) DEFAULT NULL COMMENT '调用渠道报错时,错误信息', + `channel_notify_data` text DEFAULT NULL COMMENT '支付渠道的同步/异步通知的内容', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_no` (`no`), + KEY `idx_order_id` (`order_id`), + KEY `idx_channel_id` (`channel_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单扩展表'; + +-- 支付退款单表 (tz_pay_refund) +CREATE TABLE `tz_pay_refund` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '退款单编号,数据库自增', + `no` varchar(64) NOT NULL COMMENT '外部退款号', + `app_id` bigint(20) NOT NULL COMMENT '应用编号 (关联 tz_pay_app.id)', + `channel_id` bigint(20) NOT NULL COMMENT '渠道编号 (关联 tz_pay_channel.id)', + `channel_code` varchar(32) NOT NULL COMMENT '渠道编码 (枚举 PayChannelEnum)', + `order_id` bigint(20) NOT NULL COMMENT '订单编号 (关联 tz_pay_order.id)', + `order_no` varchar(64) NOT NULL COMMENT '支付订单编号', + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号', + `merchant_refund_id` varchar(64) NOT NULL COMMENT '商户退款订单号', + `notify_url` varchar(512) DEFAULT NULL COMMENT '异步通知地址', + `status` tinyint(4) NOT NULL COMMENT '退款状态 (枚举 PayRefundStatusEnum)', + `pay_price` int(11) NOT NULL COMMENT '支付金额,单位:分', + `refund_price` int(11) NOT NULL COMMENT '退款金额,单位:分', + `reason` varchar(512) DEFAULT NULL COMMENT '退款原因', + `user_ip` varchar(32) DEFAULT NULL COMMENT '用户 IP', + `channel_order_no` varchar(128) DEFAULT NULL COMMENT '渠道订单号', + `channel_refund_no` varchar(128) DEFAULT NULL COMMENT '渠道退款单号', + `success_time` datetime DEFAULT NULL COMMENT '退款成功时间', + `channel_error_code` varchar(64) DEFAULT NULL COMMENT '调用渠道的错误码', + `channel_error_msg` varchar(512) DEFAULT NULL COMMENT '调用渠道的错误提示', + `channel_notify_data` text DEFAULT NULL COMMENT '支付渠道的同步/异步通知的内容', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_no` (`no`), + UNIQUE KEY `uk_merchant_refund_id_app_id` (`merchant_refund_id`,`app_id`), + KEY `idx_app_id` (`app_id`), + KEY `idx_channel_id` (`channel_id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付退款单表'; + +-- 转账单表 (tz_pay_transfer) +CREATE TABLE `tz_pay_transfer` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', + `no` varchar(64) NOT NULL COMMENT '转账单号', + `app_id` bigint(20) NOT NULL COMMENT '应用编号 (关联 tz_pay_app.id)', + `channel_id` bigint(20) NOT NULL COMMENT '转账渠道编号 (关联 tz_pay_channel.id)', + `channel_code` varchar(32) NOT NULL COMMENT '转账渠道编码 (枚举 PayChannelEnum)', + `merchant_transfer_id` varchar(64) NOT NULL COMMENT '商户转账单编号', + `type` tinyint(4) NOT NULL COMMENT '类型 (枚举 PayTransferTypeEnum)', + `subject` varchar(256) NOT NULL COMMENT '转账标题', + `price` int(11) NOT NULL COMMENT '转账金额,单位:分', + `user_name` varchar(64) DEFAULT NULL COMMENT '收款人姓名', + `status` tinyint(4) NOT NULL COMMENT '转账状态 (枚举 PayTransferStatusRespEnum)', + `success_time` datetime DEFAULT NULL COMMENT '订单转账成功时间', + `alipay_logon_id` varchar(128) DEFAULT NULL COMMENT '支付宝登录号', + `openid` varchar(128) DEFAULT NULL COMMENT '微信 openId', + `notify_url` varchar(512) DEFAULT NULL COMMENT '异步通知地址', + `user_ip` varchar(32) DEFAULT NULL COMMENT '用户 IP', + `channel_extras` json DEFAULT NULL COMMENT '渠道的额外参数', + `channel_transfer_no` varchar(128) DEFAULT NULL COMMENT '渠道转账单号', + `channel_error_code` varchar(64) DEFAULT NULL COMMENT '调用渠道的错误码', + `channel_error_msg` varchar(512) DEFAULT NULL COMMENT '调用渠道的错误提示', + `channel_notify_data` text DEFAULT NULL COMMENT '渠道的同步/异步通知的内容', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL COMMENT '最后更新时间', + `creator` varchar(64) NOT NULL COMMENT '创建者', + `updater` varchar(64) NOT NULL COMMENT '更新者', + `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_no` (`no`), + UNIQUE KEY `uk_merchant_transfer_id_app_id` (`merchant_transfer_id`,`app_id`), + KEY `idx_app_id` (`app_id`), + KEY `idx_channel_id` (`channel_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='转账单表'; \ No newline at end of file diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/tashow-base.sql similarity index 100% rename from sql/mysql/ruoyi-vue-pro.sql rename to sql/mysql/tashow-base.sql diff --git a/sql/tools/.gitignore b/sql/tools/.gitignore deleted file mode 100644 index e00c3e7..0000000 --- a/sql/tools/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 忽略python虚拟环境 -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ diff --git a/sql/tools/README.md b/sql/tools/README.md deleted file mode 100644 index 94c5300..0000000 --- a/sql/tools/README.md +++ /dev/null @@ -1,130 +0,0 @@ -## 0. 友情提示 - -在 `sql/tools` 目录下,我们提供一些数据库相关的工具,包括测试数据库的快速启动、MySQL 转换其它数据库等等。 - -注意!所有的操作,必须在 `sql/tools` 目录下执行。 - -## 1. 测试数据库的快速启动 - -基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server 等数据库。 - -注意!使用 Docker Compose 启动完测试数据后,因为会自动导入项目的 SQL 脚本,所以可能需要等待 1-2 分钟。 - -### 1.1 MySQL - -```Bash -docker compose up -d mysql -``` - -#### 1.2 Oracle - -```Bash -## x86 版本 -docker compose up -d oracle - -## MacBook Apple Silicon -docker compose up -d oracle_m1 -``` - -> 注意:如果使用 MacBook Apple Silicon 版本,它的 ORACLE_SID 不是 XE,而是 FREE!!! - -### 1.3 PostgreSQL - -```Bash -docker compose up -d postgres -``` - -### 1.4 SQL Server - -```Bash -docker compose up -d sqlserver -# 注意:启动完 sqlserver 后,需要手动再执行如下命令,因为 SQL Server 不支持初始化脚本 -docker compose exec sqlserver bash /tmp/create_schema.sh -``` - -### 1.5 DM 达梦 - -① 下载达梦 Docker 镜像: 地址,点击“Docker 镜像”选项,进行下载。 - -② 加载镜像文件,在镜像 tar 文件所在目录运行: - -```Bash -docker load -i dm8_20240715_x86_rh6_rq_single.tar -``` - -③ 在项目 `sql/tools` 目录下运行: - -```Bash -docker compose up -d dm8 -# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本 -docker compose exec dm8 bash -c '/opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema.sql' -exit -``` - -### 1.6 KingbaseES 人大金仓 - -① 下载人大金仓 Docker 镜像: - -* [x86_64 版本](https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar) 【Windows 选择这个】 -* [aarch64 版本](https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar) 【MacBook Apple Silicon 选择这个】 - -② 加载镜像文件,在镜像 tar 文件所在目录运行: - -```Bash -docker load -i kdb_x86_64_V009R001C001B0025.tar -``` - -③ 在项目 `sql/tools` 目录下运行: - -```Bash -docker compose up -d kingbase -# 注意:启动完 kingbase 后,需要手动再执行如下命令 -docker compose exec kingbase bash -c 'ksql -U $DB_USER -d test -f /tmp/schema.sql' -``` - -### 1.7 华为 OpenGauss - -```Bash -docker compose up -d opengauss -# 注意:启动完 opengauss 后,需要手动再执行如下命令 -docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' -``` - -## 1.X 容器的销毁重建 - -开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 - -以 postgres 为例,操作如下: - -```Bash -docker compose down postgres -docker volume rm ruoyi-vue-pro_postgres -``` - -## 2. MySQL 转换其它数据库 - -项目提供了 `sql/tools/convertor.py` 脚本,支持将 MySQL 转换为 Oracle、PostgreSQL、SQL Server、达梦、人大金仓、OpenGauss 等数据库的脚本。 - -### 2.1 实现原理 - -通过读取 MySQL 的 `sql/mysql/ruoyi-vue-pro.sql` 数据库文件,转换成对应的数据库脚本。 - -### 2.2 使用方法 - -① 安装依赖库 `simple-ddl-parser` - -```bash -pip install simple-ddl-parser -# pip3 install simple-ddl-parser -``` - -② 在 `sql/tools/` 目录下,执行如下命令打印生成 postgres 的脚本内容,其他可选参数有:`oracle`、`sqlserver`、`dm8`、`kingbase`、`opengauss`: - -```Bash -python3 convertor.py postgres -# python3 convertor.py postgres > tmp.sql -``` - -程序将 SQL 脚本打印到终端,可以重定向到临时文件 `tmp.sql`。 - -确认无误后,可以利用 IDEA 进行格式化。当然,也可以直接导入到数据库中。 diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py deleted file mode 100644 index f672cd7..0000000 --- a/sql/tools/convertor.py +++ /dev/null @@ -1,844 +0,0 @@ -# encoding=utf8 -"""芋道系统数据库迁移工具 - -Author: dhb52 (https://gitee.com/dhb52) - -pip install simple-ddl-parser -""" - -import argparse -import pathlib -import re -import time -from abc import ABC, abstractmethod -from typing import Dict, Generator, Optional, Tuple, Union - -from simple_ddl_parser import DDLParser - -PREAMBLE = """/* - Yudao Database Transfer Tool - - Source Server Type : MySQL - - Target Server Type : {db_type} - - Date: {date} -*/ - -""" - - -def load_and_clean(sql_file: str) -> str: - """加载源 SQL 文件,并清理内容方便下一步 ddl 解析 - - Args: - sql_file (str): sql文件路径 - - Returns: - str: 清理后的sql文件内容 - """ - REPLACE_PAIR_LIST = ( - (" CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ", " "), - (" KEY `", " INDEX `"), - ("UNIQUE INDEX", "UNIQUE KEY"), - ("b'0'", "'0'"), - ("b'1'", "'1'"), - ) - - content = open(sql_file).read() - for replace_pair in REPLACE_PAIR_LIST: - content = content.replace(*replace_pair) - content = re.sub(r"ENGINE.*COMMENT", "COMMENT", content) - content = re.sub(r"ENGINE.*;", ";", content) - return content - - -class Convertor(ABC): - def __init__(self, src: str, db_type) -> None: - self.src = src - self.db_type = db_type - self.content = load_and_clean(self.src) - self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content) - - @abstractmethod - def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str: - """字段类型转换 - - Args: - type (str): 字段类型 - size (Optional[Union[int, Tuple[int]]]): 字段长度描述, 如varchar(255), decimal(10,2) - - Returns: - str: 类型定义 - """ - pass - - @abstractmethod - def gen_create(self, table_ddl: Dict) -> str: - """生成 create 脚本 - - Args: - table_ddl (Dict): 表DDL - - Returns: - str: 生成脚本 - """ - pass - - @abstractmethod - def gen_pk(self, table_name: str) -> str: - """生成主键定义 - - Args: - table_name (str): 表名 - - Returns: - str: 生成脚本 - """ - pass - - @abstractmethod - def gen_index(self, ddl: Dict) -> str: - """生成索引定义 - - Args: - table_ddl (Dict): 表DDL - - Returns: - str: 生成脚本 - """ - pass - - @abstractmethod - def gen_comment(self, table_sql: str, table_name: str) -> str: - """生成字段/表注释 - - Args: - table_sql (str): 原始表SQL - table_name (str): 表名 - - Returns: - str: 生成脚本 - """ - pass - - @abstractmethod - def gen_insert(self, table_name: str) -> str: - """生成 insert 语句块 - - Args: - table_name (str): 表名 - - Returns: - str: 生成脚本 - """ - pass - - def gen_dual(self) -> str: - """生成虚拟 dual 表 - - Returns: - str: 生成脚本, 默认返回空脚本, 表示当前数据库无需手工创建 - """ - return "" - - @staticmethod - def inserts(table_name: str, script_content: str) -> Generator: - PREFIX = f"INSERT INTO `{table_name}`" - - # 收集 `table_name` 对应的 insert 语句 - for line in script_content.split("\n"): - if line.startswith(PREFIX): - head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1) - head = head.strip().replace("`", "").lower() - tail = tail.strip().replace(r"\"", '"') - # tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'") - yield f"INSERT INTO {table_name.lower()} {head} VALUES {tail}" - - @staticmethod - def index(ddl: Dict) -> Generator: - """生成索引定义 - - Args: - ddl (Dict): 表DDL - - Yields: - Generator[str]: create index 语句 - """ - - def generate_columns(columns): - keys = [ - f"{col['name'].lower()}{' ' + col['order'].lower() if col['order'] != 'ASC' else ''}" - for col in columns[0] - ] - return ", ".join(keys) - - for no, index in enumerate(ddl["index"], 1): - columns = generate_columns(index["columns"]) - table_name = ddl["table_name"].lower() - yield f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})" - - @staticmethod - def filed_comments(table_sql: str) -> Generator: - for line in table_sql.split("\n"): - match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip()) - if match: - field = match.group(1) - comment_string = match.group(2).replace("\\n", "\n") - yield field, comment_string - - def table_comment(self, table_sql: str) -> str: - match = re.search(r"COMMENT \= '([^']+)';", table_sql) - return match.group(1) if match else None - - def print(self): - """打印转换后的sql脚本到终端""" - print( - PREAMBLE.format( - db_type=self.db_type, - date=time.strftime("%Y-%m-%d %H:%M:%S"), - ) - ) - - dual = self.gen_dual() - if dual: - print( - f"""-- ---------------------------- --- Table structure for dual --- ---------------------------- -{dual} -""" - ) - - error_scripts = [] - for table_sql in self.table_script_list: - ddl = DDLParser(table_sql.replace("`", "")).run() - - # 如果parse失败, 需要跟进 - if len(ddl) == 0: - error_scripts.append(table_sql) - continue - - table_ddl = ddl[0] - table_name = table_ddl["table_name"] - - # 忽略 quartz 的内容 - if table_name.lower().startswith("qrtz"): - continue - - # 为每个表生成个5个基本部分 - create = self.gen_create(table_ddl) - pk = self.gen_pk(table_name) - index = self.gen_index(table_ddl) - comment = self.gen_comment(table_sql, table_name) - inserts = self.gen_insert(table_name) - - # 组合当前表的DDL脚本 - script = f"""{create} - -{pk} - -{index} - -{comment} - -{inserts} -""" - - # 清理 - script = re.sub("\n{3,}", "\n\n", script).strip() + "\n" - - print(script) - - # 将parse失败的脚本打印出来 - if error_scripts: - for script in error_scripts: - print(script) - - -class PostgreSQLConvertor(Convertor): - def __init__(self, src): - super().__init__(src, "PostgreSQL") - - def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): - """类型转换""" - - type = type.lower() - - if type == "varchar": - return f"varchar({size})" - if type == "int": - return "int4" - if type == "bigint" or type == "bigint unsigned": - return "int8" - if type == "datetime": - return "timestamp" - if type == "bit": - return "bool" - if type in ("tinyint", "smallint"): - return "int2" - if type == "text": - return "text" - if type in ("blob", "mediumblob"): - return "bytea" - if type == "decimal": - return ( - f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric" - ) - - def gen_create(self, ddl: Dict) -> str: - """生成 create""" - - def _generate_column(col): - name = col["name"].lower() - if name == "deleted": - return "deleted int2 NOT NULL DEFAULT 0" - - type = col["type"].lower() - full_type = self.translate_type(type, col["size"]) - nullable = "NULL" if col["nullable"] else "NOT NULL" - default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - return f"{name} {full_type} {nullable} {default}" - - table_name = ddl["table_name"].lower() - columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] - filed_def_list = ",\n ".join(columns) - script = f"""-- ---------------------------- --- Table structure for {table_name} --- ---------------------------- -DROP TABLE IF EXISTS {table_name}; -CREATE TABLE {table_name} ( - {filed_def_list} -);""" - - return script - - def gen_index(self, ddl: Dict) -> str: - return "\n".join(f"{script};" for script in self.index(ddl)) - - def gen_comment(self, table_sql: str, table_name: str) -> str: - """生成字段及表的注释""" - - script = "" - for field, comment_string in self.filed_comments(table_sql): - script += ( - f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n" - ) - - table_comment = self.table_comment(table_sql) - if table_comment: - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" - - return script - - def gen_pk(self, table_name) -> str: - """生成主键定义""" - return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n" - - def gen_insert(self, table_name: str) -> str: - """生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence""" - - inserts = list(Convertor.inserts(table_name, self.content)) - ## 生成 insert 脚本 - script = "" - last_id = 0 - if inserts: - inserts_lines = "\n".join(inserts) - script += f"""\n\n-- ---------------------------- --- Records of {table_name.lower()} --- ---------------------------- --- @formatter:off -BEGIN; -{inserts_lines} -COMMIT; --- @formatter:on""" - match = re.search(r"VALUES \((\d+),", inserts[-1]) - if match: - last_id = int(match.group(1)) - - # 生成 Sequence - script += ( - "\n\n" - + f"""DROP SEQUENCE IF EXISTS {table_name}_seq; -CREATE SEQUENCE {table_name}_seq - START {last_id + 1};""" - ) - - return script - - def gen_dual(self) -> str: - return """DROP TABLE IF EXISTS dual; -CREATE TABLE dual -( - id int2 -); - -COMMENT ON TABLE dual IS '数据库连接的表'; - --- ---------------------------- --- Records of dual --- ---------------------------- --- @formatter:off -INSERT INTO dual VALUES (1); --- @formatter:on""" - - -class OracleConvertor(Convertor): - def __init__(self, src): - super().__init__(src, "Oracle") - - def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): - """类型转换""" - type = type.lower() - - if type == "varchar": - return f"varchar2({size if size < 4000 else 4000})" - if type == "int": - return "number" - if type == "bigint" or type == "bigint unsigned": - return "number" - if type == "datetime": - return "date" - if type == "bit": - return "number(1,0)" - if type in ("tinyint", "smallint"): - return "smallint" - if type == "text": - return "clob" - if type in ("blob", "mediumblob"): - return "blob" - if type == "decimal": - return ( - f"number({','.join(str(s) for s in size)})" if len(size) else "number" - ) - - def gen_create(self, ddl) -> str: - """生成 CREATE 语句""" - - def generate_column(col): - name = col["name"].lower() - if name == "deleted": - return "deleted number(1,0) DEFAULT 0 NOT NULL" - - type = col["type"].lower() - full_type = self.translate_type(type, col["size"]) - nullable = "NULL" if col["nullable"] else "NOT NULL" - default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - # Oracle 中 size 不能作为字段名 - field_name = '"size"' if name == "size" else name - # Oracle DEFAULT 定义在 NULLABLE 之前 - return f"{field_name} {full_type} {default} {nullable}" - - table_name = ddl["table_name"].lower() - columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]] - field_def_list = ",\n ".join(columns) - script = f"""-- ---------------------------- --- Table structure for {table_name} --- ---------------------------- -CREATE TABLE {table_name} ( - {field_def_list} -);""" - - # oracle INSERT '' 不能通过 NOT NULL 校验 - script = script.replace("DEFAULT '' NOT NULL", "DEFAULT '' NULL") - - return script - - def gen_index(self, ddl: Dict) -> str: - return "\n".join(f"{script};" for script in self.index(ddl)) - - def gen_comment(self, table_sql: str, table_name: str) -> str: - script = "" - for field, comment_string in self.filed_comments(table_sql): - script += ( - f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n" - ) - - table_comment = self.table_comment(table_sql) - if table_comment: - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" - - return script - - def gen_pk(self, table_name: str) -> str: - """生成主键定义""" - return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n" - - def gen_index(self, ddl: Dict) -> str: - return "\n".join(f"{script};" for script in self.index(ddl)) - - def gen_insert(self, table_name: str) -> str: - """拷贝 INSERT 语句""" - inserts = [] - for insert_script in Convertor.inserts(table_name, self.content): - # 对日期数据添加 TO_DATE 转换 - insert_script = re.sub( - r"('\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')", - r"to_date(\g<1>, 'SYYYY-MM-DD HH24:MI:SS')", - insert_script, - ) - inserts.append(insert_script) - - ## 生成 insert 脚本 - script = "" - last_id = 0 - if inserts: - inserts_lines = "\n".join(inserts) - script += f"""\n\n-- ---------------------------- --- Records of {table_name.lower()} --- ---------------------------- --- @formatter:off -{inserts_lines} -COMMIT; --- @formatter:on""" - match = re.search(r"VALUES \((\d+),", inserts[-1]) - if match: - last_id = int(match.group(1)) - - # 生成 Sequence - script += f""" - -CREATE SEQUENCE {table_name}_seq - START WITH {last_id + 1};""" - - return script - - -class SQLServerConvertor(Convertor): - """_summary_ - - Args: - Convertor (_type_): _description_ - """ - - def __init__(self, src): - super().__init__(src, "Microsoft SQL Server") - - def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): - """类型转换""" - - type = type.lower() - - if type == "varchar": - return f"nvarchar({size if size < 4000 else 4000})" - if type == "int": - return "int" - if type == "bigint" or type == "bigint unsigned": - return "bigint" - if type == "datetime": - return "datetime2" - if type == "bit": - return "varchar(1)" - if type in ("tinyint", "smallint"): - return "tinyint" - if type == "text": - return "nvarchar(max)" - if type in ("blob", "mediumblob"): - return "varbinary(max)" - if type == "decimal": - return ( - f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric" - ) - - def gen_create(self, ddl: Dict) -> str: - """生成 create""" - - def _generate_column(col): - name = col["name"].lower() - if name == "id": - return "id bigint NOT NULL PRIMARY KEY IDENTITY" - if name == "deleted": - return "deleted bit DEFAULT 0 NOT NULL" - - type = col["type"].lower() - full_type = self.translate_type(type, col["size"]) - nullable = "NULL" if col["nullable"] else "NOT NULL" - default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - return f"{name} {full_type} {default} {nullable}" - - table_name = ddl["table_name"].lower() - columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] - filed_def_list = ",\n ".join(columns) - script = f"""-- ---------------------------- --- Table structure for {table_name} --- ---------------------------- -DROP TABLE IF EXISTS {table_name} -GO -CREATE TABLE {table_name} ( - {filed_def_list} -) -GO""" - - return script - - def gen_comment(self, table_sql: str, table_name: str) -> str: - """生成字段及表的注释""" - - script = "" - - for field, comment_string in self.filed_comments(table_sql): - script += f"""EXEC sp_addextendedproperty - 'MS_Description', N'{comment_string}', - 'SCHEMA', N'dbo', - 'TABLE', N'{table_name}', - 'COLUMN', N'{field}' -GO - -""" - - table_comment = self.table_comment(table_sql) - if table_comment: - script += f"""EXEC sp_addextendedproperty - 'MS_Description', N'{table_comment}', - 'SCHEMA', N'dbo', - 'TABLE', N'{table_name}' -GO - -""" - return script - - def gen_pk(self, table_name: str) -> str: - """生成主键定义""" - return "" - - def gen_index(self, ddl: Dict) -> str: - """生成 index""" - return "\n".join(f"{script}\nGO" for script in self.index(ddl)) - - def gen_insert(self, table_name: str) -> str: - """生成 insert 语句""" - - # 收集 `table_name` 对应的 insert 语句 - inserts = [] - for insert_script in Convertor.inserts(table_name, self.content): - # SQLServer: 字符串前加N,hack,是否存在替换字符串内容的风险 - insert_script = insert_script.replace(", '", ", N'").replace( - "VALUES ('", "VALUES (N')" - ) - # 删除 insert 的结尾分号 - insert_script = re.sub(";$", r"\nGO", insert_script) - inserts.append(insert_script) - - ## 生成 insert 脚本 - script = "" - if inserts: - inserts_lines = "\n".join(inserts) - script += f"""\n\n-- ---------------------------- --- Records of {table_name.lower()} --- ---------------------------- --- @formatter:off -BEGIN TRANSACTION -GO -SET IDENTITY_INSERT {table_name.lower()} ON -GO -{inserts_lines} -SET IDENTITY_INSERT {table_name.lower()} OFF -GO -COMMIT -GO --- @formatter:on""" - - return script - - def gen_dual(self) -> str: - return """DROP TABLE IF EXISTS dual -GO -CREATE TABLE dual -( - id int -) -GO - -EXEC sp_addextendedproperty - 'MS_Description', N'数据库连接的表', - 'SCHEMA', N'dbo', - 'TABLE', N'dual' -GO - --- ---------------------------- --- Records of dual --- ---------------------------- --- @formatter:off -INSERT INTO dual VALUES (1) -GO --- @formatter:on""" - - -class DM8Convertor(Convertor): - def __init__(self, src): - super().__init__(src, "DM8") - - def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]): - """类型转换""" - type = type.lower() - - if type == "varchar": - return f"varchar({size})" - if type == "int": - return "int" - if type == "bigint" or type == "bigint unsigned": - return "bigint" - if type == "datetime": - return "datetime" - if type == "bit": - return "bit" - if type in ("tinyint", "smallint"): - return "smallint" - if type == "text": - return "text" - if type == "blob": - return "blob" - if type == "mediumblob": - return "varchar(10240)" - if type == "decimal": - return ( - f"decimal({','.join(str(s) for s in size)})" if len(size) else "decimal" - ) - - def gen_create(self, ddl) -> str: - """生成 CREATE 语句""" - - def generate_column(col): - name = col["name"].lower() - if name == "id": - return "id bigint NOT NULL PRIMARY KEY IDENTITY" - - type = col["type"].lower() - full_type = self.translate_type(type, col["size"]) - nullable = "NULL" if col["nullable"] else "NOT NULL" - default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - return f"{name} {full_type} {default} {nullable}" - - table_name = ddl["table_name"].lower() - columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]] - field_def_list = ",\n ".join(columns) - script = f"""-- ---------------------------- --- Table structure for {table_name} --- ---------------------------- -CREATE TABLE {table_name} ( - {field_def_list} -);""" - - # oracle INSERT '' 不能通过 NOT NULL 校验 - script = script.replace("DEFAULT '' NOT NULL", "DEFAULT '' NULL") - - return script - - def gen_index(self, ddl: Dict) -> str: - return "\n".join(f"{script};" for script in self.index(ddl)) - - def gen_comment(self, table_sql: str, table_name: str) -> str: - script = "" - for field, comment_string in self.filed_comments(table_sql): - script += ( - f"COMMENT ON COLUMN {table_name}.{field} IS '{comment_string}';" + "\n" - ) - - table_comment = self.table_comment(table_sql) - if table_comment: - script += f"COMMENT ON TABLE {table_name} IS '{table_comment}';\n" - - return script - - def gen_pk(self, table_name: str) -> str: - """生成主键定义""" - return "" - - def gen_index(self, ddl: Dict) -> str: - return "\n".join(f"{script};" for script in self.index(ddl)) - - def gen_insert(self, table_name: str) -> str: - """拷贝 INSERT 语句""" - inserts = list(Convertor.inserts(table_name, self.content)) - - ## 生成 insert 脚本 - script = "" - if inserts: - inserts_lines = "\n".join(inserts) - script += f"""\n\n-- ---------------------------- --- Records of {table_name.lower()} --- ---------------------------- --- @formatter:off -SET IDENTITY_INSERT {table_name.lower()} ON; -{inserts_lines} -COMMIT; -SET IDENTITY_INSERT {table_name.lower()} OFF; --- @formatter:on""" - - return script - - -class KingbaseConvertor(PostgreSQLConvertor): - def __init__(self, src): - super().__init__(src) - self.db_type = "Kingbase" - - def gen_create(self, ddl: Dict) -> str: - """生成 create""" - - def _generate_column(col): - name = col["name"].lower() - if name == "deleted": - return "deleted int2 NOT NULL DEFAULT 0" - - type = col["type"].lower() - full_type = self.translate_type(type, col["size"]) - nullable = "NULL" if col["nullable"] else "NOT NULL" - default = f"DEFAULT {col['default']}" if col["default"] is not None else "" - return f"{name} {full_type} {nullable} {default}" - - table_name = ddl["table_name"].lower() - columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]] - filed_def_list = ",\n ".join(columns) - script = f"""-- ---------------------------- --- Table structure for {table_name} --- ---------------------------- -DROP TABLE IF EXISTS {table_name}; -CREATE TABLE {table_name} ( - {filed_def_list} -);""" - - # Kingbase INSERT '' 不能通过 NOT NULL 校验 - script = script.replace("NOT NULL DEFAULT ''", "NULL DEFAULT ''") - - return script - - -class OpengaussConvertor(KingbaseConvertor): - def __init__(self, src): - super().__init__(src) - self.db_type = "OpenGauss" - - -def main(): - parser = argparse.ArgumentParser(description="芋道系统数据库转换工具") - parser.add_argument( - "type", - type=str, - help="目标数据库类型", - choices=["postgres", "oracle", "sqlserver", "dm8", "kingbase", "opengauss"], - ) - args = parser.parse_args() - - sql_file = pathlib.Path("../mysql/ruoyi-vue-pro.sql").resolve().as_posix() - convertor = None - if args.type == "postgres": - convertor = PostgreSQLConvertor(sql_file) - elif args.type == "oracle": - convertor = OracleConvertor(sql_file) - elif args.type == "sqlserver": - convertor = SQLServerConvertor(sql_file) - elif args.type == "dm8": - convertor = DM8Convertor(sql_file) - elif args.type == "kingbase": - convertor = KingbaseConvertor(sql_file) - elif args.type == "opengauss": - convertor = OpengaussConvertor(sql_file) - else: - raise NotImplementedError(f"不支持目标数据库类型: {args.type}") - - convertor.print() - - -if __name__ == "__main__": - main() diff --git a/sql/tools/docker-compose.yaml b/sql/tools/docker-compose.yaml deleted file mode 100644 index 0fa9513..0000000 --- a/sql/tools/docker-compose.yaml +++ /dev/null @@ -1,134 +0,0 @@ -name: ruoyi-vue-pro - -volumes: - mysql: { } - postgres: { } - sqlserver: { } - dm8: { } - kingbase: { } - opengauss: { } - -services: - mysql: - image: mysql:8.0.33 - restart: unless-stopped - environment: - TZ: Asia/Shanghai - MYSQL_ROOT_PASSWORD: 123456 - MYSQL_DATABASE: ruoyi-vue-pro - ports: - - "3306:3306" - volumes: - - mysql:/var/lib/mysql/ - # 注入初始化脚本 - - ./mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/init.sql:ro - command: - --default-authentication-plugin=mysql_native_password - --character-set-server=utf8mb4 - --collation-server=utf8mb4_general_ci - --explicit_defaults_for_timestamp=true - --lower_case_table_names=1 - - postgres: - image: postgres:14.2 - restart: unless-stopped - environment: - POSTGRES_USER: root - POSTGRES_PASSWORD: 123456 - POSTGRES_DB: ruoyi-vue-pro - ports: - - "5432:5432" - volumes: - - postgres:/var/lib/postgresql/data - # 注入初始化脚本 - - ../postgresql/quartz.sql:/docker-entrypoint-initdb.d/quartz.sql:ro - - ../postgresql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro - - oracle: - image: gvenzl/oracle-xe:18-slim-faststart - restart: unless-stopped - environment: - ## 登录信息 SID: XE user: system password: oracle - ORACLE_PASSWORD: oracle - ports: - - "1521:1521" - volumes: - - ../oracle/ruoyi-vue-pro.sql:/tmp/schema.sql:ro - # 创建app用户: ROOT/123456@//localhost/XEPDB1 - - ./oracle/1_create_user.sql:/docker-entrypoint-initdb.d/1_create_user.sql:ro - - ./oracle/2_create_schema.sh:/docker-entrypoint-initdb.d/2_create_schema.sh:ro - - oracle_m1: - image: einslib/oracle-19c:19.3.0-ee-slim-faststart - restart: unless-stopped - environment: - ## 登录信息 SID: FREE user: system password: oracle - ORACLE_PASSWORD: oracle - ports: - - "1521:1521" - volumes: - - ../oracle/ruoyi-vue-pro.sql:/tmp/schema.sql:ro - # 创建app用户: ROOT/123456@//localhost/XEPDB1 - - ./oracle/1_create_user.sql:/docker-entrypoint-initdb.d/1_create_user.sql:ro - - ./oracle/2_create_schema.sh:/docker-entrypoint-initdb.d/2_create_schema.sh:ro - - sqlserver: - image: mcr.microsoft.com/mssql/server:2017-latest - restart: unless-stopped - environment: - TZ: Asia/Shanghai - ACCEPT_EULA: "Y" - SA_PASSWORD: "Yudao@2024" - ports: - - "1433:1433" - volumes: - - sqlserver:/var/opt/mssql - - ../sqlserver/ruoyi-vue-pro.sql:/tmp/schema.sql:ro - # docker compose exec sqlserver bash /tmp/create_schema.sh - - ./sqlserver/create_schema.sh:/tmp/create_schema.sh:ro - - dm8: - # docker load -i dm8_20240715_x86_rh6_rq_single.tar - image: dm8_single:dm8_20240715_rev232765_x86_rh6_64 - restart: unless-stopped - environment: - PAGE_SIZE: 16 - LD_LIBRARY_PATH: /opt/dmdbms/bin - EXTENT_SIZE: 32 - BLANK_PAD_MODE: 1 - LOG_SIZE: 1024 - UNICODE_FLAG: 1 - LENGTH_IN_CHAR: 1 - INSTANCE_NAME: dm8_test - ports: - - "5236:5236" - volumes: - - dm8:/opt/dmdbms/data - - ../dm/ruoyi-vue-pro-dm8.sql:/tmp/schema.sql:ro - - kingbase: - image: kingbase_v009r001c001b0025_single_x86:v1 -# image: kingbase_v009r001c001b0025_single_arm:v1 - restart: unless-stopped - environment: - DB_USER: root - DB_PASSWORD: 123456 - ports: - - "54321:54321" - volumes: - - kingbase:/home/kingbase/userdata - - ../kingbase/ruoyi-vue-pro.sql:/tmp/schema.sql:ro - - opengauss: - image: opengauss/opengauss:5.0.0 - restart: unless-stopped - environment: - GS_USERNAME: root - GS_PASSWORD: Yudao@2024 - LD_LIBRARY_PATH: /usr/local/opengauss/lib:/usr/lib - ports: - - "5432:5432" - volumes: - - opengauss:/var/lib/opengauss - - ../opengauss/ruoyi-vue-pro.sql:/tmp/schema.sql:ro - # docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql' \ No newline at end of file diff --git a/sql/tools/oracle/1_create_user.sql b/sql/tools/oracle/1_create_user.sql deleted file mode 100644 index 58c9658..0000000 --- a/sql/tools/oracle/1_create_user.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER SESSION SET CONTAINER=XEPDB1; -CREATE USER ROOT IDENTIFIED BY 123456 QUOTA UNLIMITED ON USERS; -GRANT CONNECT, RESOURCE TO ROOT; diff --git a/sql/tools/oracle/2_create_schema.sh b/sql/tools/oracle/2_create_schema.sh deleted file mode 100644 index ce7955d..0000000 --- a/sql/tools/oracle/2_create_schema.sh +++ /dev/null @@ -1 +0,0 @@ -sqlplus -s ROOT/123456@//localhost/XEPDB1 @/tmp/schema.sql diff --git a/sql/tools/sqlserver/create_schema.sh b/sql/tools/sqlserver/create_schema.sh deleted file mode 100644 index 172650b..0000000 --- a/sql/tools/sqlserver/create_schema.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -Q "CREATE DATABASE [ruoyi-vue-pro]; -GO" -/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -d 'ruoyi-vue-pro' -i /tmp/schema.sql diff --git a/tashow-dependencies/pom.xml b/tashow-dependencies/pom.xml index c427091..0c12cba 100644 --- a/tashow-dependencies/pom.xml +++ b/tashow-dependencies/pom.xml @@ -34,6 +34,8 @@ 2.3.1 + 2.7.0 + 4.6.0 2.4.0 @@ -150,6 +152,47 @@ tashow-data-permission ${revision} + + com.tashow.cloud + tashow-sdk-file + ${revision} + + + + com.tashow.cloud + tashow-trade-api + ${revision} + + + com.tashow.cloud + tashow-module-trade + ${revision} + + + com.tashow.cloud + tashow-pay-api + ${revision} + + + com.tashow.cloud + tashow-sdk-payment + ${revision} + + + com.tashow.cloud + tashow-member-api + ${revision} + + + com.tashow.cloud + tashow-module-pay + ${revision} + + + com.tashow.cloud + tashow-sdk-payment + ${revision} + @@ -397,6 +440,22 @@ ${spring-boot-admin.version} + + + com.github.xingfudeshi + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-api + ${springdoc.version} + + + com.github.xiaoymin + knife4j-gateway-spring-boot-starter + 4.5.0 + org.mockito diff --git a/tashow-feign/pom.xml b/tashow-feign/pom.xml index 762c0de..9650f12 100644 --- a/tashow-feign/pom.xml +++ b/tashow-feign/pom.xml @@ -14,6 +14,9 @@ tashow-infra-api tashow-system-api tashow-product-api + tashow-trade-api + tashow-pay-api + tashow-member-api diff --git a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java index 2ecf928..cbbcd25 100644 --- a/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java +++ b/tashow-feign/tashow-infra-api/src/main/java/com/tashow/cloud/infraapi/api/file/FileApi.java @@ -9,50 +9,54 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +/** + * RPC 服务 - 文件 + */ @FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = -/** RPC 服务 - 文件 */ public interface FileApi { - String PREFIX = ApiConstants.PREFIX + "/file"; + String PREFIX = ApiConstants.PREFIX + "/file"; - /** - * 保存文件,并返回文件的访问路径 - * - * @param content 文件内容 - * @return 文件路径 - */ - default String createFile(byte[] content) { - return createFile(null, null, content); - } + /** + * 保存文件,并返回文件的访问路径 + * + * @param content 文件内容 + * @return 文件路径 + */ + default String createFile(byte[] content) { + return createFile(null, null, content); + } - /** - * 保存文件,并返回文件的访问路径 - * - * @param path 文件路径 - * @param content 文件内容 - * @return 文件路径 - */ - default String createFile(String path, byte[] content) { - return createFile(null, path, content); - } + /** + * 保存文件,并返回文件的访问路径 + * + * @param path 文件路径 + * @param content 文件内容 + * @return 文件路径 + */ + default String createFile(String path, byte[] content) { + return createFile(null, path, content); + } - /** - * 保存文件,并返回文件的访问路径 - * - * @param name 原文件名称 - * @param path 文件路径 - * @param content 文件内容 - * @return 文件路径 - */ - default String createFile( - @RequestParam("name") String name, - @RequestParam("path") String path, - @RequestParam("content") byte[] content) { - return createFile(new FileCreateReqDTO().setName(name).setPath(path).setContent(content)) - .getCheckedData(); - } + /** + * 保存文件,并返回文件的访问路径 + * + * @param name 原文件名称 + * @param path 文件路径 + * @param content 文件内容 + * @return 文件路径 + */ + default String createFile( + @RequestParam("name") String name, + @RequestParam("path") String path, + @RequestParam("content") byte[] content) { + return createFile(new FileCreateReqDTO().setName(name).setPath(path).setContent(content)) + .getCheckedData(); + } - @PostMapping(PREFIX + "/create") - /** 保存文件,并返回文件的访问路径 */ - CommonResult createFile(@Valid @RequestBody FileCreateReqDTO createReqDTO); + /** + * 保存文件,并返回文件的访问路径 + */ + @PostMapping(PREFIX + "/create") + CommonResult createFile(@Valid @RequestBody FileCreateReqDTO createReqDTO); } diff --git a/tashow-feign/tashow-member-api/pom.xml b/tashow-feign/tashow-member-api/pom.xml new file mode 100644 index 0000000..c6d8085 --- /dev/null +++ b/tashow-feign/tashow-member-api/pom.xml @@ -0,0 +1,40 @@ + + + + com.tashow.cloud + tashow-feign + ${revision} + + 4.0.0 + tashow-member-api + jar + + ${project.artifactId} + + member 模块 API,暴露给其它模块调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/address/MemberAddressApi.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/address/MemberAddressApi.java new file mode 100644 index 0000000..2ae0f10 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/address/MemberAddressApi.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.memberapi.api.address; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.memberapi.api.address.dto.MemberAddressRespDTO; +import com.tashow.cloud.memberapi.enums.ApiConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * RPC 服务 - 用户收件地址 + */ +@FeignClient(name = ApiConstants.NAME) +public interface MemberAddressApi { + + String PREFIX = ApiConstants.PREFIX + "/address"; + + /** + * 获得用户收件地址 + * @param id 收件地址编号 + * @param userId 用户编号 + * @return + */ + @GetMapping(PREFIX + "/get") + CommonResult getAddress(@RequestParam("id") Long id, + @RequestParam("userId") Long userId); + + /** + * 获得用户默认收件地址 + * @param userId 用户编号 + * @return + */ + @GetMapping(PREFIX + "/get-default") + CommonResult getDefaultAddress(@RequestParam("userId") Long userId); + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/address/dto/MemberAddressRespDTO.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/address/dto/MemberAddressRespDTO.java new file mode 100644 index 0000000..7bcb898 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/address/dto/MemberAddressRespDTO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.memberapi.api.address.dto; + +import lombok.Data; + +/** + * RPC 服务 - 用户收件地址 Response DTO + */ +@Data +public class MemberAddressRespDTO { + + //编号 + private Long id; + + //用户编号 + private Long userId; + + //收件人名称 + private String name; + + //手机号 + private String mobile; + + //地区编号 + private Integer areaId; + + //收件详细地址 + private String detailAddress; + + //是否默认 + private Boolean defaultStatus; + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/package-info.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/package-info.java new file mode 100644 index 0000000..b192222 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/package-info.java @@ -0,0 +1,4 @@ +/** + * member API 包,定义暴露给其它模块的 API + */ +package com.tashow.cloud.memberapi.api; diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/user/MemberUserApi.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/user/MemberUserApi.java new file mode 100644 index 0000000..b8c6c07 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/user/MemberUserApi.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.memberapi.api.user; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.memberapi.api.user.dto.MemberUserRespDTO; +import com.tashow.cloud.memberapi.enums.ApiConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMap; + +/** + * RPC 服务 - 会员用户 + */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +public interface MemberUserApi { + + String PREFIX = ApiConstants.PREFIX + "/user"; + + + /** + * 获得会员用户 Map + * + * @param ids 用户编号的数组 + * @return 会员用户 Map + */ + default Map getUserMap(Collection ids) { + List list = getUserList(ids).getCheckedData(); + return convertMap(list, MemberUserRespDTO::getId); + } + + /** + * 获得会员用户信息 + * @param id 编号 + * @return + */ + @GetMapping(PREFIX + "/get") + CommonResult getUser(@RequestParam("id") Long id); + + /** + * 获得会员用户信息们 + * @param ids 用户编号的数组 + * @return + */ + @GetMapping(PREFIX + "/list") + CommonResult> getUserList(@RequestParam("ids") Collection ids); + + /** + * 基于用户昵称,模糊匹配用户列表 + * @param nickname 用户昵称,模糊匹配 + * @return + */ + @GetMapping(PREFIX + "/list-by-nickname") + CommonResult> getUserListByNickname(@RequestParam("nickname") String nickname); + + /** + * 基于手机号,精准匹配用户 + * @param mobile 基于手机号,精准匹配用户 + * @return + */ + @GetMapping(PREFIX + "/get-by-mobile") + CommonResult getUserByMobile(@RequestParam("mobile") String mobile); + + /** + * 校验用户是否存在 + * @param id 用户编号 + * @return + */ + @GetMapping(PREFIX + "/valid") + CommonResult validateUser(@RequestParam("id") Long id); + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/user/dto/MemberUserRespDTO.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/user/dto/MemberUserRespDTO.java new file mode 100644 index 0000000..432db65 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/api/user/dto/MemberUserRespDTO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.memberapi.api.user.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * RPC 服务 - 用户信息 Response DTO + */ +@Data +public class MemberUserRespDTO { + + //用户编号 + private Long id; + + //昵称 + private String nickname; + + //帐号状态 + private Integer status; // 参见 CommonStatusEnum 枚举 + + //用户头像 + private String avatar; + + //手机号 + private String mobile; + + //创建时间 + private LocalDateTime createTime; + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/ApiConstants.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/ApiConstants.java new file mode 100644 index 0000000..8f71452 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.memberapi.enums; + + +import com.tashow.cloud.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "member-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/member"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/DictTypeConstants.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/DictTypeConstants.java new file mode 100644 index 0000000..54b139c --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/DictTypeConstants.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.memberapi.enums; + +/** + * Member 字典类型的枚举类 + * + * @author owen + */ +public interface DictTypeConstants { + + /** + * 会员经验记录 - 业务类型 + */ + String MEMBER_EXPERIENCE_BIZ_TYPE = "member_experience_biz_type"; + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/ErrorCodeConstants.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..7f287b2 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/ErrorCodeConstants.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.memberapi.enums; + + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * Member 错误码枚举类 + *

+ * member 系统,使用 1-004-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== 用户相关 1-004-001-000 ============ + ErrorCode USER_NOT_EXISTS = new ErrorCode(1_004_001_000, "用户不存在"); + ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_004_001_001, "手机号未注册用户"); + ErrorCode USER_MOBILE_USED = new ErrorCode(1_004_001_002, "修改手机失败,该手机号({})已经被使用"); + ErrorCode USER_POINT_NOT_ENOUGH = new ErrorCode(1_004_001_003, "用户积分余额不足"); + + // ========== AUTH 模块 1-004-003-000 ========== + ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_004_003_000, "登录失败,账号密码不正确"); + ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_004_003_001, "登录失败,账号被禁用"); + ErrorCode AUTH_SOCIAL_USER_NOT_FOUND = new ErrorCode(1_004_003_005, "登录失败,解析不到三方登录信息"); + ErrorCode AUTH_MOBILE_USED = new ErrorCode(1_004_003_007, "手机号已经被使用"); + + // ========== 用户收件地址 1-004-004-000 ========== + ErrorCode ADDRESS_NOT_EXISTS = new ErrorCode(1_004_004_000, "用户收件地址不存在"); + + //========== 用户标签 1-004-006-000 ========== + ErrorCode TAG_NOT_EXISTS = new ErrorCode(1_004_006_000, "用户标签不存在"); + ErrorCode TAG_NAME_EXISTS = new ErrorCode(1_004_006_001, "用户标签已经存在"); + ErrorCode TAG_HAS_USER = new ErrorCode(1_004_006_002, "用户标签下存在用户,无法删除"); + + //========== 积分配置 1-004-007-000 ========== + + //========== 积分记录 1-004-008-000 ========== + ErrorCode POINT_RECORD_BIZ_NOT_SUPPORT = new ErrorCode(1_004_008_000, "用户积分记录业务类型不支持"); + + //========== 签到配置 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, "签到天数规则已存在"); + + //========== 签到配置 1-004-010-000 ========== + ErrorCode SIGN_IN_RECORD_TODAY_EXISTS = new ErrorCode(1_004_010_000, "今日已签到,请勿重复签到"); + + //========== 用户等级 1-004-011-000 ========== + ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(1_004_011_000, "用户等级不存在"); + ErrorCode LEVEL_NAME_EXISTS = new ErrorCode(1_004_011_001, "用户等级名称[{}]已被使用"); + ErrorCode LEVEL_VALUE_EXISTS = new ErrorCode(1_004_011_002, "用户等级值[{}]已被[{}]使用"); + ErrorCode LEVEL_EXPERIENCE_MIN = new ErrorCode(1_004_011_003, "升级经验必须大于上一个等级[{}]设置的升级经验[{}]"); + ErrorCode LEVEL_EXPERIENCE_MAX = new ErrorCode(1_004_011_004, "升级经验必须小于下一个等级[{}]设置的升级经验[{}]"); + ErrorCode LEVEL_HAS_USER = new ErrorCode(1_004_011_005, "用户等级下存在用户,无法删除"); + + ErrorCode EXPERIENCE_BIZ_NOT_SUPPORT = new ErrorCode(1_004_011_201, "用户经验业务类型不支持"); + + //========== 用户分组 1-004-012-000 ========== + ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_004_012_000, "用户分组不存在"); + ErrorCode GROUP_HAS_USER = new ErrorCode(1_004_012_001, "用户分组下存在用户,无法删除"); + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/MemberExperienceBizTypeEnum.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/MemberExperienceBizTypeEnum.java new file mode 100644 index 0000000..5dc903f --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/MemberExperienceBizTypeEnum.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.memberapi.enums; + +import cn.hutool.core.util.EnumUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 会员经验 - 业务类型 + * + * @author owen + */ +@Getter +@AllArgsConstructor +public enum MemberExperienceBizTypeEnum { + + /** + * 管理员调整、邀请新用户、下单、退单、签到、抽奖 + */ + ADMIN(0, "管理员调整", "管理员调整获得 {} 经验", true), + INVITE_REGISTER(1, "邀新奖励", "邀请好友获得 {} 经验", true), + SIGN_IN(4, "签到奖励", "签到获得 {} 经验", true), + LOTTERY(5, "抽奖奖励", "抽奖获得 {} 经验", true), + ORDER_GIVE(11, "下单奖励", "下单获得 {} 经验", true), + ORDER_GIVE_CANCEL(12, "下单奖励(整单取消)", "取消订单获得 {} 经验", false), // ORDER_GIVE 的取消 + ORDER_GIVE_CANCEL_ITEM(13, "下单奖励(单个退款)", "退款订单获得 {} 经验", false), // ORDER_GIVE 的取消 + ; + + /** + * 业务类型 + */ + private final int type; + /** + * 标题 + */ + private final String title; + /** + * 描述 + */ + private final String description; + /** + * 是否为扣减积分 + */ + private final boolean add; + + public static MemberExperienceBizTypeEnum getByType(Integer type) { + return EnumUtil.getBy(MemberExperienceBizTypeEnum.class, + e -> Objects.equals(type, e.getType())); + } +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/point/MemberPointBizTypeEnum.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/point/MemberPointBizTypeEnum.java new file mode 100644 index 0000000..783f1f3 --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/enums/point/MemberPointBizTypeEnum.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.memberapi.enums.point; + +import cn.hutool.core.util.EnumUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 会员积分的业务类型枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum MemberPointBizTypeEnum implements ArrayValuable { + + SIGN(1, "签到", "签到获得 {} 积分", true), + ADMIN(2, "管理员修改", "管理员修改 {} 积分", true), + + ORDER_USE(11, "订单积分抵扣", "下单使用 {} 积分", false), // 下单时,扣减积分 + ORDER_USE_CANCEL(12, "订单积分抵扣(整单取消)", "订单取消,退还 {} 积分", true), // ORDER_USE 的取消 + ORDER_USE_CANCEL_ITEM(13, "订单积分抵扣(单个退款)", "订单退款,退还 {} 积分", true), // ORDER_USE 的取消 + + ORDER_GIVE(21, "订单积分奖励", "下单获得 {} 积分", true), // 支付订单时,赠送积分 + ORDER_GIVE_CANCEL(22, "订单积分奖励(整单取消)", "订单取消,退还 {} 积分", false), // ORDER_GIVE 的取消 + ORDER_GIVE_CANCEL_ITEM(23, "订单积分奖励(单个退款)", "订单退款,扣除赠送的 {} 积分", false) // ORDER_GIVE 的取消 + ; + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + /** + * 描述 + */ + private final String description; + /** + * 是否为扣减积分 + */ + private final boolean add; + + @Override + public Integer[] array() { + return new Integer[0]; + } + + public static MemberPointBizTypeEnum getByType(Integer type) { + return EnumUtil.getBy(MemberPointBizTypeEnum.class, + e -> Objects.equals(type, e.getType())); + } + +} diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/message/package-info.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/message/package-info.java new file mode 100644 index 0000000..2c1759a --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/message/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的消息 + */ +package com.tashow.cloud.memberapi.message; diff --git a/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/message/user/MemberUserCreateMessage.java b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/message/user/MemberUserCreateMessage.java new file mode 100644 index 0000000..d7f42fc --- /dev/null +++ b/tashow-feign/tashow-member-api/src/main/java/com/tashow/cloud/memberapi/message/user/MemberUserCreateMessage.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.memberapi.message.user; + +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * 会员用户创建消息 + * + * @author owen + */ +@Data +public class MemberUserCreateMessage { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + +} diff --git a/tashow-feign/tashow-pay-api/pom.xml b/tashow-feign/tashow-pay-api/pom.xml new file mode 100644 index 0000000..4474ffc --- /dev/null +++ b/tashow-feign/tashow-pay-api/pom.xml @@ -0,0 +1,44 @@ + + + + com.tashow.cloud + tashow-feign + ${revision} + + 4.0.0 + tashow-pay-api + jar + + ${project.artifactId} + + pay 模块 API,暴露给其它模块调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + com.tashow.cloud + tashow-sdk-payment + + + + diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayOrderNotifyReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayOrderNotifyReqDTO.java new file mode 100644 index 0000000..7bb6dd3 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayOrderNotifyReqDTO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.payapi.api.notify.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 支付单的通知 Request DTO + * + * @author 芋道源码 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayOrderNotifyReqDTO { + + /** + * 商户订单编号 + */ + @NotEmpty(message = "商户订单号不能为空") + private String merchantOrderId; + + /** + * 支付订单编号 + */ + @NotNull(message = "支付订单编号不能为空") + private Long payOrderId; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayRefundNotifyReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayRefundNotifyReqDTO.java new file mode 100644 index 0000000..f5c4ed3 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayRefundNotifyReqDTO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.payapi.api.notify.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 退款单的通知 Request DTO + * + * @author 芋道源码 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundNotifyReqDTO { + + /** + * 商户退款单编号 + */ + @NotEmpty(message = "商户退款单编号不能为空") + private String merchantOrderId; + + /** + * 支付退款编号 + */ + @NotNull(message = "支付退款编号不能为空") + private Long payRefundId; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayTransferNotifyReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayTransferNotifyReqDTO.java new file mode 100644 index 0000000..f9e8baf --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/dto/PayTransferNotifyReqDTO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.payapi.api.notify.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 转账单的通知 Request DTO + * + * @author jason + */ +@Data +public class PayTransferNotifyReqDTO { + + /** + * 商户转账单号 + */ + @NotEmpty(message = "商户转账单号不能为空") + private String merchantTransferId; + + /** + * 转账订单编号 + */ + @NotNull(message = "转账订单编号不能为空") + private Long payTransferId; +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/package-info.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/notify/package-info.java new file mode 100644 index 0000000..e69de29 diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/PayOrderApi.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/PayOrderApi.java new file mode 100644 index 0000000..638cb62 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/PayOrderApi.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.payapi.api.order; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.payapi.enums.ApiConstants; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +/** + * RPC 服务 - 支付单 + */ +@FeignClient(name = ApiConstants.NAME) // +public interface PayOrderApi { + + String PREFIX = ApiConstants.PREFIX + "/order"; + + /*创建支付单*/ + @PostMapping(PREFIX + "/create") + CommonResult createOrder(@Valid @RequestBody PayOrderCreateReqDTO reqDTO); + + /*获得支付单*/ + @GetMapping(PREFIX + "/get") + @PermitAll + CommonResult getOrder(@RequestParam("id") Long id); + + /** + * 更新支付订单价格 + * @param id 支付单编号 + * @param payPrice 支付单价格 + * @return + */ + @PutMapping(PREFIX + "/update-price") + CommonResult updatePayOrderPrice(@RequestParam("id") Long id ,//支付单编号 + @RequestParam("payPrice") Integer payPrice); + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/dto/PayOrderCreateReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/dto/PayOrderCreateReqDTO.java new file mode 100644 index 0000000..368720d --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/dto/PayOrderCreateReqDTO.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.payapi.api.order.dto; + +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 支付单创建 Request DTO + */ +@Data +public class PayOrderCreateReqDTO implements Serializable { + + public static final int SUBJECT_MAX_LENGTH = 32; + + /** + * 应用标识 + */ + @NotNull(message = "应用标识不能为空") + private String appKey; + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + // ========== 商户相关字段 ========== + + /** + * 商户订单编号 + */ + @NotEmpty(message = "商户订单编号不能为空") + private String merchantOrderId; + /** + * 商品标题 + */ + @NotEmpty(message = "商品标题不能为空") + @Length(max = SUBJECT_MAX_LENGTH, message = "商品标题不能超过 32") + private String subject; + /** + * 商品描述 + */ + @Length(max = 128, message = "商品描述信息长度不能超过128") + private String body; + + // ========== 订单相关字段 ========== + + /** + * 支付金额,单位:分 + */ + @NotNull(message = "支付金额不能为空") + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") + private Integer price; + + /** + * 支付过期时间 + */ + @NotNull(message = "支付过期时间不能为空") + private LocalDateTime expireTime; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/dto/PayOrderRespDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/dto/PayOrderRespDTO.java new file mode 100644 index 0000000..5e03032 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/order/dto/PayOrderRespDTO.java @@ -0,0 +1,46 @@ +package com.tashow.cloud.payapi.api.order.dto; + +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import lombok.Data; + +/** + * 支付单信息 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayOrderRespDTO { + + /** + * 订单编号,数据库自增 + */ + private Long id; + /** + * 渠道编码 + * + * 枚举 PayChannelEnum + */ + private String channelCode; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一 + */ + private String merchantOrderId; + + // ========== 订单相关字段 ========== + /** + * 支付金额,单位:分 + */ + private Integer price; + /** + * 支付状态 + * + * 枚举 {@link PayOrderStatusEnum} + */ + private Integer status; + + // ========== 渠道相关字段 ========== + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/PayRefundApi.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/PayRefundApi.java new file mode 100644 index 0000000..abe0300 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/PayRefundApi.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.payapi.api.refund; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundRespDTO; +import com.tashow.cloud.payapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * RPC 服务 - 退款单 + */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +public interface PayRefundApi { + + String PREFIX = ApiConstants.PREFIX + "/refund"; + + /** + * 创建退款单 + * @param reqDTO + * @return + */ + @PostMapping(PREFIX + "/create") + CommonResult createRefund(@Valid @RequestBody PayRefundCreateReqDTO reqDTO); + + /** + * 获得退款单 + * @param id 退款单编号 + * @return + */ + @GetMapping(PREFIX + "/get") + CommonResult getRefund(@RequestParam("id") Long id); + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/dto/PayRefundCreateReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/dto/PayRefundCreateReqDTO.java new file mode 100644 index 0000000..9936e56 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/dto/PayRefundCreateReqDTO.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.payapi.api.refund.dto; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +/** + * 退款单创建 Request DTO + * + * @author 芋道源码 + */ +@Data +public class PayRefundCreateReqDTO { + + /** + * 应用标识 + */ + @NotNull(message = "应用标识不能为空") + private String appKey; + /** + * 用户 IP + */ + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + */ + @NotEmpty(message = "商户订单编号不能为空") + private String merchantOrderId; + + /** + * 商户退款编号 + */ + @NotEmpty(message = "商户退款编号不能为空") + private String merchantRefundId; + + /** + * 退款描述 + */ + @NotEmpty(message = "退款描述不能为空") + @Length(max = 128, message = "退款描述长度不能超过 128") + private String reason; + + // ========== 订单相关字段 ========== + + /** + * 退款金额,单位:分 + */ + @NotNull(message = "退款金额不能为空") + @Min(value = 1, message = "退款金额必须大于零") + private Integer price; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/dto/PayRefundRespDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/dto/PayRefundRespDTO.java new file mode 100644 index 0000000..5771bc0 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/refund/dto/PayRefundRespDTO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.payapi.api.refund.dto; + +import com.tashow.cloud.payapi.enums.refund.PayRefundStatusEnum; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 退款单信息 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayRefundRespDTO { + + /** + * 退款单编号 + */ + private Long id; + + // ========== 退款相关字段 ========== + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer status; + /** + * 退款金额,单位:分 + */ + private Integer refundPrice; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + */ + private String merchantOrderId; + /** + * 退款成功时间 + */ + private LocalDateTime successTime; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/PayTransferApi.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/PayTransferApi.java new file mode 100644 index 0000000..d5e5e03 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/PayTransferApi.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.payapi.api.transfer; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.payapi.api.transfer.dto.PayTransferCreateReqDTO; +import com.tashow.cloud.payapi.enums.ApiConstants; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * RPC 服务 - 转账单 + */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +public interface PayTransferApi { + + String PREFIX = ApiConstants.PREFIX + "/transfer"; + + /** + * 创建转账单 + * @param reqDTO + * @return + */ + @PostMapping(PREFIX + "/create") + CommonResult createTransfer(@Valid @RequestBody PayTransferCreateReqDTO reqDTO); + + /** + * 获得转账单 + * @param id 转账单编号 + * @return + */ + @GetMapping(PREFIX + "/get") + CommonResult getTransfer(@RequestParam("id") Long id); + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/dto/PayTransferCreateReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/dto/PayTransferCreateReqDTO.java new file mode 100644 index 0000000..654ec6f --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/dto/PayTransferCreateReqDTO.java @@ -0,0 +1,77 @@ +package com.tashow.cloud.payapi.api.transfer.dto; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +/** + * 转账单创建 Request DTO + * + * @author jason + */ +@Data +public class PayTransferCreateReqDTO { + + /** + * 应用标识 + */ + @NotNull(message = "应用标识不能为空") + private String appKey; + + @NotEmpty(message = "转账渠道不能为空") + private String channelCode; + + /** + * 转账渠道的额外参数 + */ + private Map channelExtras; + + @NotEmpty(message = "用户 IP 不能为空") + private String userIp; + + /** + * 类型 + */ + @NotNull(message = "转账类型不能为空") + @InEnum(PayTransferTypeEnum.class) + private Integer type; + + + /** + * 商户转账单编号 + */ + @NotEmpty(message = "商户转账单编号能为空") + private String merchantTransferId; + + /** + * 转账金额,单位:分 + */ + @Min(value = 1, message = "转账金额必须大于零") + @NotNull(message = "转账金额不能为空") + private Integer price; + + /** + * 转账标题 + */ + @NotEmpty(message = "转账标题不能为空") + private String subject; + + /** + * 收款人姓名 + */ + @NotBlank(message = "收款人姓名不能为空", groups = {PayTransferTypeEnum.Alipay.class}) + private String userName; + + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class}) + private String alipayLogonId; + + // ========== 微信转账相关字段 ========== + @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class}) + private String openid; +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/dto/PayTransferRespDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/dto/PayTransferRespDTO.java new file mode 100644 index 0000000..8616096 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/transfer/dto/PayTransferRespDTO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.payapi.api.transfer.dto; + +import com.tashow.cloud.payapi.enums.transfer.PayTransferStatusEnum; +import lombok.Data; + +@Data +public class PayTransferRespDTO { + + /** + * 编号 + */ + private Long id; + + /** + * 转账单号 + */ + private String no; + + /** + * 转账金额,单位:分 + */ + private Integer price; + + /** + * 转账状态 + * + * 枚举 {@link PayTransferStatusEnum} + */ + private Integer status; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/wallet/PayWalletApi.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/wallet/PayWalletApi.java new file mode 100644 index 0000000..5ff6959 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/wallet/PayWalletApi.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.payapi.api.wallet; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.payapi.api.wallet.dto.PayWalletAddBalanceReqDTO; +import com.tashow.cloud.payapi.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * RPC 服务 - 钱包 + */ +@FeignClient(name = ApiConstants.NAME) // TODO 芋艿:fallbackFactory = +public interface PayWalletApi { + + String PREFIX = ApiConstants.PREFIX + "/wallet"; + + /** + * 添加钱包余额 + * @param reqDTO + * @return + */ + @PostMapping(PREFIX + "/add-balance") + CommonResult addWalletBalance(@Valid @RequestBody PayWalletAddBalanceReqDTO reqDTO); + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/wallet/dto/PayWalletAddBalanceReqDTO.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/wallet/dto/PayWalletAddBalanceReqDTO.java new file mode 100644 index 0000000..d644cba --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/api/wallet/dto/PayWalletAddBalanceReqDTO.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.payapi.api.wallet.dto; + +import com.tashow.cloud.common.enums.UserTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 钱包余额增加 Request DTO + * + * @author 芋道源码 + */ +@Data +public class PayWalletAddBalanceReqDTO { + + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + * + * 关联 {@link UserTypeEnum} + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 关联业务分类 + */ + @NotNull(message = "关联业务分类不能为空") + private Integer bizType; + /** + * 关联业务编号 + */ + @NotNull(message = "关联业务编号不能为空") + private String bizId; + + /** + * 交易金额,单位分 + * + * 正值表示余额增加,负值表示余额减少 + */ + @NotNull(message = "交易金额不能为空") + private Integer price; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/ApiConstants.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/ApiConstants.java new file mode 100644 index 0000000..2019c34 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.payapi.enums; + + +import com.tashow.cloud.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "pay-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/pay"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/DictTypeConstants.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/DictTypeConstants.java new file mode 100644 index 0000000..9c553e7 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/DictTypeConstants.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.payapi.enums; + +/** + * Pay 字典类型的枚举类 + * + * @author 芋道源码 + */ +public interface DictTypeConstants { + + String CHANNEL_CODE = "pay_channel_code"; // 支付渠道编码 + + String ORDER_STATUS = "pay_order_status"; // 支付渠道 + + String REFUND_STATUS = "pay_order_status"; // 退款状态 + + String NOTIFY_STATUS = "pay_notify_status"; // 回调状态 + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/ErrorCodeConstants.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..8dc4225 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/ErrorCodeConstants.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.payapi.enums; + + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * Pay 错误码 Core 枚举类 + * + * pay 系统,使用 1-007-000-000 段 + */ +public interface ErrorCodeConstants { + + // ========== APP 模块 1-007-000-000 ========== + ErrorCode APP_NOT_FOUND = new ErrorCode(1_007_000_000, "App 不存在"); + ErrorCode APP_IS_DISABLE = new ErrorCode(1_007_000_002, "App 已经被禁用"); + ErrorCode APP_EXIST_ORDER_CANT_DELETE = new ErrorCode(1_007_000_003, "支付应用存在支付订单,无法删除"); + ErrorCode APP_EXIST_REFUND_CANT_DELETE = new ErrorCode(1_007_000_004, "支付应用存在退款订单,无法删除"); + ErrorCode APP_KEY_EXISTS = new ErrorCode(1_007_000_005, "支付应用标识已经存在"); + + // ========== CHANNEL 模块 1-007-001-000 ========== + ErrorCode CHANNEL_NOT_FOUND = new ErrorCode(1_007_001_000, "支付渠道的配置不存在"); + ErrorCode CHANNEL_IS_DISABLE = new ErrorCode(1_007_001_001, "支付渠道已经禁用"); + ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1_007_001_004, "已存在相同的渠道"); + + // ========== ORDER 模块 1-007-002-000 ========== + ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1_007_002_000, "支付订单不存在"); + ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_002_001, "支付订单不处于待支付"); + ErrorCode PAY_ORDER_STATUS_IS_SUCCESS = new ErrorCode(1_007_002_002, "订单已支付,请刷新页面"); + ErrorCode PAY_ORDER_IS_EXPIRED = new ErrorCode(1_007_002_003, "支付订单已经过期"); + ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_002_004, "发起支付报错,错误码:{},错误提示:{}"); + ErrorCode PAY_ORDER_REFUND_FAIL_STATUS_ERROR = new ErrorCode(1_007_002_005, "支付订单退款失败,原因:状态不是已支付或已退款"); + + // ========== ORDER 模块(拓展单) 1-007-003-000 ========== + ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1_007_003_000, "支付交易拓展单不存在"); + ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_003_001, "支付交易拓展单不处于待支付"); + ErrorCode PAY_ORDER_EXTENSION_IS_PAID = new ErrorCode(1_007_003_002, "订单已支付,请等待支付结果"); + + // ========== 支付模块(退款) 1-007-006-000 ========== + ErrorCode REFUND_PRICE_EXCEED = new ErrorCode(1_007_006_000, "退款金额超过订单可退款金额"); + ErrorCode REFUND_HAS_REFUNDING = new ErrorCode(1_007_006_002, "已经有退款在处理中"); + ErrorCode REFUND_EXISTS = new ErrorCode(1_007_006_003, "已经存在退款单"); + ErrorCode REFUND_NOT_FOUND = new ErrorCode(1_007_006_004, "支付退款单不存在"); + ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_006_005, "支付退款单不处于待退款"); + + // ========== 钱包模块 1-007-007-000 ========== + ErrorCode WALLET_NOT_FOUND = new ErrorCode(1_007_007_000, "用户钱包不存在"); + ErrorCode WALLET_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_007_001, "钱包余额不足"); + ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1_007_007_002, "未找到对应的钱包交易"); + ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1_007_007_003, "已经存在钱包退款"); + ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1_007_007_004, "钱包冻结余额不足"); + + // ========== 钱包充值模块 1-007-008-000 ========== + ErrorCode WALLET_RECHARGE_NOT_FOUND = new ErrorCode(1_007_008_000, "钱包充值记录不存在"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_008_001, "钱包充值更新支付状态失败,钱包充值记录不是【未支付】状态"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_008_002, "钱包充值更新支付状态失败,支付单编号不匹配"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_008_003, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态"); + ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_008_004, "钱包充值更新支付状态失败,支付单金额不匹配"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_008_005, "钱包发起退款失败,钱包充值订单未支付"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_008_006, "钱包发起退款失败,钱包充值订单已退款"); + ErrorCode WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH = new ErrorCode(1_007_008_007, "钱包发起退款失败,钱包余额不足"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_008_008, "钱包退款更新失败,钱包退款单编号不匹配"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_008_009, "钱包退款更新失败,退款订单不存在"); + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_008_010, "钱包退款更新失败,退款单金额不匹配"); + ErrorCode WALLET_RECHARGE_PACKAGE_NOT_FOUND = new ErrorCode(1_007_008_011, "钱包充值套餐不存在"); + ErrorCode WALLET_RECHARGE_PACKAGE_IS_DISABLE = new ErrorCode(1_007_008_012, "钱包充值套餐已禁用"); + ErrorCode WALLET_RECHARGE_PACKAGE_NAME_EXISTS = new ErrorCode(1_007_008_013, "钱包充值套餐名称已存在"); + + // ========== 转账模块 1-007-009-000 ========== + ErrorCode PAY_TRANSFER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_007_009_000, "发起转账报错,错误码:{},错误提示:{}"); + ErrorCode PAY_TRANSFER_NOT_FOUND = new ErrorCode(1_007_009_001, "转账单不存在"); + ErrorCode PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH = new ErrorCode(1_007_009_002, "两次相同转账请求的类型不匹配"); + ErrorCode PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "两次相同转账请求的金额不匹配"); + ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经发起,请查询转账订单相关状态"); + ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账"); + ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中"); + + // ========== 示例订单 1-007-900-000 ========== + ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1_007_900_000, "示例订单不存在"); + ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_007_900_001, "示例订单更新支付状态失败,订单不是【未支付】状态"); + ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1_007_900_002, "示例订单更新支付状态失败,支付单编号不匹配"); + ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_007_900_003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态"); + ErrorCode DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_007_900_004, "示例订单更新支付状态失败,支付单金额不匹配"); + ErrorCode DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(1_007_900_005, "发起退款失败,示例订单未支付"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(1_007_900_006, "发起退款失败,示例订单已退款"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_007_900_007, "发起退款失败,退款订单不存在"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(1_007_900_008, "发起退款失败,退款订单未退款成功"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_007_900_009, "发起退款失败,退款单编号不匹配"); + ErrorCode DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_007_900_010, "发起退款失败,退款单金额不匹配"); + + // ========== 示例转账订单 1-007-901-001 ========== + ErrorCode DEMO_TRANSFER_NOT_FOUND = new ErrorCode(1_007_901_001, "示例转账单不存在"); + ErrorCode DEMO_TRANSFER_FAIL_TRANSFER_ID_ERROR = new ErrorCode(1_007_901_002, "转账失败,转账单编号不匹配"); + ErrorCode DEMO_TRANSFER_FAIL_PRICE_NOT_MATCH = new ErrorCode(1_007_901_003, "转账失败,转账单金额不匹配"); +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/MessageTemplateConstants.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/MessageTemplateConstants.java new file mode 100644 index 0000000..2723f09 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/MessageTemplateConstants.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.payapi.enums; + +/** + * 通知模板枚举类 + * + * @author HUIHUI + */ +public interface MessageTemplateConstants { + + // ======================= 小程序订阅消息 ======================= + + String WXA_WALLET_RECHARGER_PAID = "充值成功通知"; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/notify/PayNotifyStatusEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/notify/PayNotifyStatusEnum.java new file mode 100644 index 0000000..7bf09a8 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/notify/PayNotifyStatusEnum.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.payapi.enums.notify; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付通知状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayNotifyStatusEnum { + + WAITING(0, "等待通知"), + SUCCESS(10, "通知成功"), + FAILURE(20, "通知失败"), // 多次尝试,彻底失败 + REQUEST_SUCCESS(21, "请求成功,但是结果失败"), + REQUEST_FAILURE(22, "请求失败"), + + ; + + /** + * 状态 + */ + private final Integer status; + /** + * 名字 + */ + private final String name; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/notify/PayNotifyTypeEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/notify/PayNotifyTypeEnum.java new file mode 100644 index 0000000..bd0cc60 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/notify/PayNotifyTypeEnum.java @@ -0,0 +1,29 @@ +package com.tashow.cloud.payapi.enums.notify; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付通知类型 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayNotifyTypeEnum { + + ORDER(1, "支付单"), + REFUND(2, "退款单"), + TRANSFER(3, "转账单") + ; + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/order/PayOrderStatusEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/order/PayOrderStatusEnum.java new file mode 100644 index 0000000..e296926 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/order/PayOrderStatusEnum.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.payapi.enums.order; + +import com.tashow.cloud.common.core.ArrayValuable; +import com.tashow.cloud.common.util.object.ObjectUtils; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 支付订单的状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayOrderStatusEnum implements ArrayValuable { + + WAITING(0, "未支付"), + SUCCESS(10, "支付成功"), + REFUND(20, "已退款"), + CLOSED(30, "支付关闭"), // 注意:全部退款后,还是 REFUND 状态 + ; + + private final Integer status; + private final String name; + + @Override + public Integer[] array() { + return new Integer[0]; + } + + /** + * 判断是否等待支付 + * + * @param status 状态 + * @return 是否等待支付 + */ + public static boolean isWaiting(Integer status) { + return Objects.equals(status, WAITING.getStatus()); + } + + /** + * 判断是否支付成功 + * + * @param status 状态 + * @return 是否支付成功 + */ + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + /** + * 判断是否支付成功或者已退款 + * + * @param status 状态 + * @return 是否支付成功或者已退款 + */ + public static boolean isSuccessOrRefund(Integer status) { + return ObjectUtils.equalsAny(status, + SUCCESS.getStatus(), REFUND.getStatus()); + } + + /** + * 判断是否支付关闭 + * + * @param status 状态 + * @return 是否支付关闭 + */ + public static boolean isClosed(Integer status) { + return Objects.equals(status, CLOSED.getStatus()); + } + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/refund/PayRefundStatusEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/refund/PayRefundStatusEnum.java new file mode 100644 index 0000000..a4a2b51 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/refund/PayRefundStatusEnum.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.payapi.enums.refund; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * 渠道的退款状态枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum PayRefundStatusEnum { + + WAITING(0, "未退款"), + SUCCESS(10, "退款成功"), + FAILURE(20, "退款失败"); + + private final Integer status; + private final String name; + + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + public static boolean isFailure(Integer status) { + return Objects.equals(status, FAILURE.getStatus()); + } + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/transfer/PayTransferStatusEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/transfer/PayTransferStatusEnum.java new file mode 100644 index 0000000..ef181d6 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/transfer/PayTransferStatusEnum.java @@ -0,0 +1,59 @@ +package com.tashow.cloud.payapi.enums.transfer; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Objects; + +/** + * @author jason + */ +@Getter +@AllArgsConstructor +public enum PayTransferStatusEnum { + + WAITING(0, "等待转账"), + /** + * TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现 + */ + IN_PROGRESS(10, "转账进行中"), + + SUCCESS(20, "转账成功"), + /** + * 转账关闭 (失败,或者其它情况) // TODO 改成 转账失败状态 + */ + CLOSED(30, "转账关闭"); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + public static boolean isSuccess(Integer status) { + return Objects.equals(status, SUCCESS.getStatus()); + } + + public static boolean isClosed(Integer status) { + return Objects.equals(status, CLOSED.getStatus()); + } + + public static boolean isWaiting(Integer status) { + return Objects.equals(status, WAITING.getStatus()); + } + public static boolean isInProgress(Integer status) { + return Objects.equals(status, IN_PROGRESS.getStatus()); + } + + /** + * 是否处于待转账或者转账中的状态 + * @param status 状态 + */ + public static boolean isPendingStatus(Integer status) { + return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.getStatus()); + } + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/transfer/PayTransferTypeEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/transfer/PayTransferTypeEnum.java new file mode 100644 index 0000000..6b00742 --- /dev/null +++ b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/transfer/PayTransferTypeEnum.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.payapi.enums.transfer; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 转账类型枚举 + * + * @author jason + */ +@AllArgsConstructor +@Getter +public enum PayTransferTypeEnum implements ArrayValuable { + + ALIPAY_BALANCE(1, "支付宝余额"), + WX_BALANCE(2, "微信余额"), + BANK_CARD(3, "银行卡"), + WALLET_BALANCE(4, "钱包余额"); + + public interface WxPay { + } + + public interface Alipay { + } + + private final Integer type; + private final String name; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new); + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static PayTransferTypeEnum typeOf(Integer type) { + return ArrayUtil.firstMatch(item -> item.getType().equals(type), values()); + } + +} diff --git a/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/wallet/PayWalletBizTypeEnum.java b/tashow-feign/tashow-pay-api/src/main/java/com/tashow/cloud/payapi/enums/wallet/PayWalletBizTypeEnum.java new file mode 100644 index 0000000..e69de29 diff --git a/tashow-feign/tashow-trade-api/pom.xml b/tashow-feign/tashow-trade-api/pom.xml new file mode 100644 index 0000000..3e8d53a --- /dev/null +++ b/tashow-feign/tashow-trade-api/pom.xml @@ -0,0 +1,40 @@ + + + + com.tashow.cloud + tashow-feign + ${revision} + + 4.0.0 + tashow-trade-api + jar + + ${project.artifactId} + + trade 模块 API,暴露给其它模块调用 + + + + + com.tashow.cloud + tashow-common + + + + + org.springframework.boot + spring-boot-starter-validation + true + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + true + + + + diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/order/TradeOrderApi.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/order/TradeOrderApi.java new file mode 100644 index 0000000..57d0e30 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/order/TradeOrderApi.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.tradeapi.api.order; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.tradeapi.api.order.dto.TradeOrderRespDTO; +import com.tashow.cloud.tradeapi.enums.ApiConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Collection; +import java.util.List; + +@FeignClient(name = ApiConstants.NAME) +public interface TradeOrderApi { + + String PREFIX = ApiConstants.PREFIX + "/order"; + + /*获得订单列表*/ + @GetMapping(PREFIX + "/order/list") + CommonResult> getOrderList(@RequestParam("ids") Collection ids); + + /*获得订单*/ + @GetMapping(PREFIX + "/get") + CommonResult getOrder(@RequestParam("id") Long id); + + @PutMapping(PREFIX + "/cancel-paid") + CommonResult cancelPaidOrder(@RequestParam("userId") Long userId, + @RequestParam("orderId") Long orderId, + @RequestParam("cancelType") Integer cancelType); + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/order/dto/TradeOrderRespDTO.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/order/dto/TradeOrderRespDTO.java new file mode 100644 index 0000000..4dfab03 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/order/dto/TradeOrderRespDTO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.tradeapi.api.order.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 订单信息 Response DTO + * RPC 服务 - 订单信息 Response DTO + * @author HUIHUI + */ +@Data +public class TradeOrderRespDTO { + + // ========== 订单基本信息 ========== + + //订单编号 + private Long id; + + //订单流水号 + private String no; + + //订单类型 + private Integer type; // 参见 TradeOrderTypeEnum 枚举 + + //订单来源 + private Integer terminal; // 参见 TerminalEnum 枚举 + + //用户编号 + private Long userId; + + //用户 IP + private String userIp; + + //用户备注 + private String userRemark; + + //订单状态 + private Integer status; // 参见 TradeOrderStatusEnum 枚举 + + //购买的商品数量 + private Integer productCount; + + //订单完成时间 + private LocalDateTime finishTime; + + //订单取消时间 + private LocalDateTime cancelTime; + + //取消类型 + private Integer cancelType; // 参见 TradeOrderCancelTypeEnum 枚举 + + //商家备注 + private String remark; + + //是否评价 + private Boolean commentStatus; + + // ========== 价格 + 支付基本信息 ========== + + //支付订单编号 + private Long payOrderId; + + //是否已支付 + private Boolean payStatus; + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/package-info.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/package-info.java new file mode 100644 index 0000000..33ff7ee --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/api/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.tradeapi.api; \ No newline at end of file diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/ApiConstants.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/ApiConstants.java new file mode 100644 index 0000000..2082491 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/ApiConstants.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.tradeapi.enums; + + +import com.tashow.cloud.common.enums.RpcConstants; + +/** + * API 相关的枚举 + * + * @author 芋道源码 + */ +public class ApiConstants { + + /** + * 服务名 + * + * 注意,需要保证和 spring.application.name 保持一致 + */ + public static final String NAME = "trade-server"; + + public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/trade"; + + public static final String VERSION = "1.0.0"; + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/DictTypeConstants.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/DictTypeConstants.java new file mode 100644 index 0000000..cec7a1a --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/DictTypeConstants.java @@ -0,0 +1,12 @@ +package com.tashow.cloud.tradeapi.enums; + +/** + * Trade 字典类型的枚举类 + * + * @author owen + */ +public interface DictTypeConstants { + + String BROKERAGE_WITHDRAW_STATUS = "brokerage_withdraw_status"; // 佣金提现状态 + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/ErrorCodeConstants.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/ErrorCodeConstants.java new file mode 100644 index 0000000..3f0a983 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/ErrorCodeConstants.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.tradeapi.enums; + + +import com.tashow.cloud.common.exception.ErrorCode; + +/** + * Trade 错误码枚举类 + * trade 系统,使用 1-011-000-000 段 + * + * @author LeeYan9 + * @since 2022-08-26 + */ +public interface ErrorCodeConstants { + + // ========== Order 模块 1-011-000-000 ========== + ErrorCode ORDER_ITEM_NOT_FOUND = new ErrorCode(1_011_000_010, "交易订单项不存在"); + ErrorCode ORDER_NOT_FOUND = new ErrorCode(1_011_000_011, "交易订单不存在"); + ErrorCode ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL = new ErrorCode(1_011_000_012, "交易订单项更新售后状态失败,请重试"); + ErrorCode ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_013, "交易订单更新支付状态失败,订单不是【未支付】状态"); + ErrorCode ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(1_011_000_014, "交易订单更新支付状态失败,支付单编号不匹配"); + ErrorCode ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_015, "交易订单更新支付状态失败,支付单状态不是【支付成功】状态"); + ErrorCode ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_011_000_016, "交易订单更新支付状态失败,支付单金额不匹配"); + ErrorCode ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED = new ErrorCode(1_011_000_017, "交易订单发货失败,订单不是【待发货】状态"); + ErrorCode ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_018, "交易订单收货失败,订单不是【待收货】状态"); + ErrorCode ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED = new ErrorCode(1_011_000_019, "创建交易订单项的评价失败,订单不是【已完成】状态"); + ErrorCode ORDER_COMMENT_STATUS_NOT_FALSE = new ErrorCode(1_011_000_020, "创建交易订单项的评价失败,订单已评价"); + ErrorCode ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE = new ErrorCode(1_011_000_021, "交易订单发货失败,订单已退款或部分退款"); + ErrorCode ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_022, "交易订单发货失败,拼团未成功"); + ErrorCode ORDER_DELIVERY_FAIL_BARGAIN_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_023, "交易订单发货失败,砍价未成功"); + ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1_011_000_024, "交易订单发货失败,发货类型不是快递"); + ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_025, "交易订单取消失败,订单不是【待支付】状态"); + ErrorCode ORDER_UPDATE_PRICE_FAIL_PAID = new ErrorCode(1_011_000_026, "支付订单调价失败,原因:支付订单已付款,不能调价"); + ErrorCode ORDER_UPDATE_PRICE_FAIL_ALREADY = new ErrorCode(1_011_000_027, "支付订单调价失败,原因:已经修改过价格"); + ErrorCode ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR = new ErrorCode(1_011_000_028, "支付订单调价失败,原因:调整后支付价格不能小于 0.01 元"); + ErrorCode ORDER_DELETE_FAIL_STATUS_NOT_CANCEL = new ErrorCode(1_011_000_029, "交易订单删除失败,订单不是【已取消】状态"); + ErrorCode ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP = new ErrorCode(1_011_000_030, "交易订单自提失败,收货方式不是【用户自提】"); + ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态"); + ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单"); + ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态"); + ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_034, "交易订单自提失败,原因:你没有核销该门店订单的权限"); + ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_035, "交易订单创建失败,原因:用户积分不足"); + + // ========== After Sale 模块 1-011-000-100 ========== + ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在"); + ErrorCode AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR = new ErrorCode(1_011_000_101, "申请退款金额错误"); + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED = new ErrorCode(1_011_000_102, "订单已关闭,无法申请售后"); + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID = new ErrorCode(1_011_000_103, "订单未支付,无法申请售后"); + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED = new ErrorCode(1_011_000_104, "订单未发货,无法申请【退货退款】售后"); + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED = new ErrorCode(1_011_000_105, "订单项已申请售后,无法重复申请"); + ErrorCode AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY = new ErrorCode(1_011_000_106, "审批失败,售后状态不处于审批中"); + ErrorCode AFTER_SALE_UPDATE_STATUS_FAIL = new ErrorCode(1_011_000_107, "操作售后单失败,请刷新后重试"); + ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1_011_000_108, "退货失败,售后单状态不处于【待买家退货】"); + ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1_011_000_109, "确认收货失败,售后单状态不处于【待确认收货】"); + ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】"); + ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY = + new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】"); + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_112, "订单拼团中,无法申请售后"); + + // ========== Cart 模块 1-011-002-000 ========== + ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在"); + + // ========== Price 相关 1-011-003-000 ============ + ErrorCode PRICE_CALCULATE_PAY_PRICE_ILLEGAL = new ErrorCode(1_011_003_000, "支付价格计算异常,原因:价格小于等于 0"); + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TEMPLATE_NOT_FOUND = new ErrorCode(1_011_003_001, "计算快递运费异常,找不到对应的运费模板"); + ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_002, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵"); + ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_003, "参与秒杀的商品,超过了秒杀总限购数量"); + ErrorCode PRICE_CALCULATE_POINT_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_004, "参与积分活动的商品,超过了积分活动商品总限购数量"); + ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_005, "计算快递运费异常,配送方式不匹配"); + ErrorCode PRICE_CALCULATE_COUPON_CAN_NOT_USE = new ErrorCode(1_011_003_006, "该优惠劵无法使用,原因:{}」"); + + // ========== 物流 Express 模块 1-011-004-000 ========== + ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在"); + ErrorCode EXPRESS_CODE_DUPLICATE = new ErrorCode(1_011_004_001, "已经存在该编码的快递公司"); + ErrorCode EXPRESS_CLIENT_NOT_PROVIDE = new ErrorCode(1_011_004_002, "需要接入快递服务商,比如【快递100】"); + ErrorCode EXPRESS_STATUS_NOT_ENABLE = new ErrorCode(1_011_004_003, "快递公司未启用"); + + ErrorCode EXPRESS_API_QUERY_ERROR = new ErrorCode(1_011_004_101, "快递查询接口异常"); + ErrorCode EXPRESS_API_QUERY_FAILED = new ErrorCode(1_011_004_102, "快递查询返回失败,原因:{}"); + + // ========== 物流 Template 模块 1-011-005-000 ========== + ErrorCode EXPRESS_TEMPLATE_NAME_DUPLICATE = new ErrorCode(1_011_005_000, "已经存在该运费模板名"); + ErrorCode EXPRESS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_011_005_001, "运费模板不存在"); + + // ========== 物流 PICK_UP 模块 1-011-006-000 ========== + ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在"); + ErrorCode PICK_UP_STORE_STAFF_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店店员不存在"); + + // ========== 分销用户 模块 1-011-007-000 ========== + ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1_011_007_000, "分销用户不存在"); + ErrorCode BROKERAGE_USER_FROZEN_PRICE_NOT_ENOUGH = new ErrorCode(1_011_007_001, "用户冻结佣金({})数量不足"); + ErrorCode BROKERAGE_BIND_SELF = new ErrorCode(1_011_007_002, "不能绑定自己"); + ErrorCode BROKERAGE_BIND_USER_NOT_ENABLED = new ErrorCode(1_011_007_003, "绑定用户没有推广资格"); + ErrorCode BROKERAGE_BIND_CONDITION_ADMIN = new ErrorCode(1_011_007_004, "仅可在后台绑定推广员"); + ErrorCode BROKERAGE_BIND_MODE_REGISTER = new ErrorCode(1_011_007_005, "只有在注册时可以绑定"); + ErrorCode BROKERAGE_BIND_OVERRIDE = new ErrorCode(1_011_007_006, "已绑定了推广人"); + ErrorCode BROKERAGE_BIND_LOOP = new ErrorCode(1_011_007_007, "下级不能绑定自己的上级"); + ErrorCode BROKERAGE_USER_LEVEL_NOT_SUPPORT = new ErrorCode(1_011_007_008, "目前只支持 level 小于等于 2"); + ErrorCode BROKERAGE_CREATE_USER_EXISTS = new ErrorCode(1_011_007_009, "分销用户已存在"); + + // ========== 分销提现 模块 1-011-008-000 ========== + ErrorCode BROKERAGE_WITHDRAW_NOT_EXISTS = new ErrorCode(1_011_008_000, "佣金提现记录不存在"); + ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1_011_008_001, "佣金提现记录状态不是审核中"); + ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1_011_008_002, "提现金额不能低于 {} 元"); + ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元"); + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/MessageTemplateConstants.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/MessageTemplateConstants.java new file mode 100644 index 0000000..337ca78 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/MessageTemplateConstants.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.tradeapi.enums; + +/** + * 通知模板枚举类 + * + * @author HUIHUI + */ +public interface MessageTemplateConstants { + + // ======================= 短信消息模版 ======================= + + String SMS_ORDER_DELIVERY = "order_delivery"; // 短信模版编号 + + // ======================= 小程序订阅消息模版 ======================= + + String WXA_ORDER_DELIVERY = "订单发货通知"; + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleOperateTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleOperateTypeEnum.java new file mode 100644 index 0000000..54183cb --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleOperateTypeEnum.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.tradeapi.enums.aftersale; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 售后操作类型的枚举 + * + * @author 陈賝 + * @since 2023/6/13 13:53 + */ +@RequiredArgsConstructor +@Getter +public enum AfterSaleOperateTypeEnum { + + MEMBER_CREATE(10, "会员申请退款"), + ADMIN_AGREE_APPLY(11, "商家同意退款"), + ADMIN_DISAGREE_APPLY(12, "商家拒绝退款"), + MEMBER_DELIVERY(20, "会员填写退货物流信息,快递公司:{deliveryName},快递单号:{logisticsNo}"), + ADMIN_AGREE_RECEIVE(21, "商家收货"), + ADMIN_DISAGREE_RECEIVE(22, "商家拒绝收货,原因:{reason}"), + ADMIN_REFUND(30, "商家退款"), + MEMBER_CANCEL(40, "会员取消退款"), + ; + + /** + * 操作类型 + */ + private final Integer type; + /** + * 操作描述 + */ + private final String content; + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleStatusEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleStatusEnum.java new file mode 100644 index 0000000..91834b3 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleStatusEnum.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.tradeapi.enums.aftersale; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Collection; + +import static cn.hutool.core.util.ArrayUtil.firstMatch; + +/** + * 售后状态的枚举 + * + * 状态流转 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum AfterSaleStatusEnum implements ArrayValuable { + + /** + * 【申请售后】 + */ + APPLY(10,"申请中", "会员申请退款"), // 有赞的状态提示:退款申请待商家处理 + /** + * 卖家通过售后;【商品待退货】 + */ + SELLER_AGREE(20, "卖家通过", "商家同意退款"), // 有赞的状态提示:请退货并填写物流信息 + /** + * 买家已退货,等待卖家收货;【商家待收货】 + */ + BUYER_DELIVERY(30,"待卖家收货", "会员填写退货物流信息"), // 有赞的状态提示:退货退款申请待商家处理 + /** + * 卖家已收货,等待平台退款;等待退款【等待退款】 + */ + WAIT_REFUND(40, "等待平台退款", "商家收货"), // 有赞的状态提示:无(有赞无该状态) + /** + * 完成退款【退款成功】 + */ + COMPLETE(50, "完成", "商家确认退款"), // 有赞的状态提示:退款成功 + /** + * 【买家取消】 + */ + BUYER_CANCEL(61, "买家取消售后", "会员取消退款"), // 有赞的状态提示:退款关闭 + /** + * 卖家拒绝售后;商家拒绝【商家拒绝】 + */ + SELLER_DISAGREE(62,"卖家拒绝", "商家拒绝退款"), // 有赞的状态提示:商家不同意退款申请 + /** + * 卖家拒绝收货,终止售后;【商家拒收货】 + */ + SELLER_REFUSE(63,"卖家拒绝收货", "商家拒绝收货"), // 有赞的状态提示:商家拒绝收货,不同意退款 + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 进行中的售后状态 + * + * 不包括已经结束的状态 + */ + public static final Collection APPLYING_STATUSES = Arrays.asList( + APPLY.getStatus(), + SELLER_AGREE.getStatus(), + BUYER_DELIVERY.getStatus(), + WAIT_REFUND.getStatus() + ); + + /** + * 状态 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + /** + * 操作内容 + * + * 目的:记录售后日志的内容 + */ + private final String content; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static AfterSaleStatusEnum valueOf(Integer status) { + return firstMatch(value -> value.getStatus().equals(status), values()); + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleTypeEnum.java new file mode 100644 index 0000000..e3bb43d --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleTypeEnum.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.tradeapi.enums.aftersale; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易售后 - 类型 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum AfterSaleTypeEnum implements ArrayValuable { + + IN_SALE(10, "售中退款"), // 交易完成前买家申请退款 + AFTER_SALE(20, "售后退款"); // 交易完成后买家申请退款 + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleWayEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleWayEnum.java new file mode 100644 index 0000000..f3e7016 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/aftersale/AfterSaleWayEnum.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.tradeapi.enums.aftersale; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易售后 - 方式 + * + * @author Sin + */ +@RequiredArgsConstructor +@Getter +public enum AfterSaleWayEnum implements ArrayValuable { + + REFUND(10, "仅退款"), + RETURN_AND_REFUND(20, "退货退款"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleWayEnum::getWay).toArray(Integer[]::new); + + /** + * 方式 + */ + private final Integer way; + /** + * 方式名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageBindModeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageBindModeEnum.java new file mode 100644 index 0000000..9d0353f --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageBindModeEnum.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.tradeapi.enums.brokerage; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 分销关系绑定模式枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum BrokerageBindModeEnum implements ArrayValuable { + + /** + * 只要用户没有推广人,随时都可以绑定分销关系 + */ + ANYTIME(1, "首次绑定"), + /** + * 仅新用户注册时才能绑定推广关系 + */ + REGISTER(2, "注册绑定"), + /** + * 每次扫码都覆盖 + */ + OVERRIDE(3, "覆盖绑定"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageBindModeEnum::getMode).toArray(Integer[]::new); + + /** + * 模式 + */ + private final Integer mode; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageEnabledConditionEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageEnabledConditionEnum.java new file mode 100644 index 0000000..5b58c2f --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageEnabledConditionEnum.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.tradeapi.enums.brokerage; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 分佣模式枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum BrokerageEnabledConditionEnum implements ArrayValuable { + + /** + * 所有用户都可以分销 + */ + ALL(1, "人人分销"), + /** + * 仅可后台手动设置推广员 + */ + ADMIN(2, "指定分销"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageEnabledConditionEnum::getCondition).toArray(Integer[]::new); + + /** + * 模式 + */ + private final Integer condition; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageRecordBizTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageRecordBizTypeEnum.java new file mode 100644 index 0000000..d10221a --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageRecordBizTypeEnum.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.tradeapi.enums.brokerage; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 佣金记录业务类型枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum BrokerageRecordBizTypeEnum implements ArrayValuable { + + ORDER(1, "获得推广佣金", "获得推广佣金 {}", true), + WITHDRAW(2, "提现申请", "提现申请扣除佣金 {}", false), + WITHDRAW_REJECT(3, "提现申请驳回", "提现申请驳回,返还佣金 {}", true), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageRecordBizTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 标题 + */ + private final String title; + /** + * 描述 + */ + private final String description; + /** + * 是否为增加佣金 + */ + private final boolean add; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageRecordStatusEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageRecordStatusEnum.java new file mode 100644 index 0000000..22cdf1d --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageRecordStatusEnum.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.tradeapi.enums.brokerage; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 佣金记录状态枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum BrokerageRecordStatusEnum implements ArrayValuable { + + WAIT_SETTLEMENT(0, "待结算"), + SETTLEMENT(1, "已结算"), + CANCEL(2, "已取消"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageRecordStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态 + */ + private final Integer status; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageWithdrawStatusEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageWithdrawStatusEnum.java new file mode 100644 index 0000000..6293f38 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageWithdrawStatusEnum.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.tradeapi.enums.brokerage; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +// TODO 芋艿:提现的打通,在纠结下; +/** + * 佣金提现状态枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum BrokerageWithdrawStatusEnum implements ArrayValuable { + + AUDITING(0, "审核中"), + AUDIT_SUCCESS(10, "审核通过"), + WITHDRAW_SUCCESS(11, "提现成功"), + AUDIT_FAIL(20, "审核不通过"), + WITHDRAW_FAIL(21, "提现失败"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态 + */ + private final Integer status; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageWithdrawTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageWithdrawTypeEnum.java new file mode 100644 index 0000000..87a234e --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/brokerage/BrokerageWithdrawTypeEnum.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.tradeapi.enums.brokerage; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 佣金提现类型枚举 + * + * @author owen + */ +@AllArgsConstructor +@Getter +public enum BrokerageWithdrawTypeEnum implements ArrayValuable { + + WALLET(1, "钱包"), + BANK(2, "银行卡"), + WECHAT(3, "微信"), // 手动打款 + ALIPAY(4, "支付宝"), + WECHAT_API(5, "微信零钱"), // 自动打款,通过微信转账 API + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 名字 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + /** + * 是否通过支付平台的 API 打款 + * + * @param type 类型 + * @return 是否 + */ + public static boolean isApi(Integer type) { + return WECHAT_API.getType().equals(type); + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/delivery/DeliveryExpressChargeModeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/delivery/DeliveryExpressChargeModeEnum.java new file mode 100644 index 0000000..be36fc0 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/delivery/DeliveryExpressChargeModeEnum.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.tradeapi.enums.delivery; + +import cn.hutool.core.util.ArrayUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 快递配送计费方式枚举 + * + * @author jason + */ +@AllArgsConstructor +@Getter +public enum DeliveryExpressChargeModeEnum implements ArrayValuable { + + COUNT(1, "按件"), + WEIGHT(2,"按重量"), + VOLUME(3, "按体积"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(DeliveryExpressChargeModeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 描述 + */ + private final String desc; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static DeliveryExpressChargeModeEnum valueOf(Integer value) { + return ArrayUtil.firstMatch(chargeMode -> chargeMode.getType().equals(value), DeliveryExpressChargeModeEnum.values()); + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/delivery/DeliveryTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/delivery/DeliveryTypeEnum.java new file mode 100644 index 0000000..121cf7d --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/delivery/DeliveryTypeEnum.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.tradeapi.enums.delivery; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 配送方式枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum DeliveryTypeEnum implements ArrayValuable { + + EXPRESS(1, "快递发货"), + PICK_UP(2, "用户自提"),; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(DeliveryTypeEnum::getType).toArray(Integer[]::new); + + /** + * 配送方式 + */ + private final Integer type; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/notify/TradeNotifyEnums.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/notify/TradeNotifyEnums.java new file mode 100644 index 0000000..d7c1eae --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/notify/TradeNotifyEnums.java @@ -0,0 +1,5 @@ +package com.tashow.cloud.tradeapi.enums.notify; + +// TODO @芋艿:这个枚举的作用? +public interface TradeNotifyEnums { +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderCancelTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderCancelTypeEnum.java new file mode 100644 index 0000000..99fec84 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderCancelTypeEnum.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.tradeapi.enums.order; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易订单 - 关闭类型 + * + * @author Sin + */ +@RequiredArgsConstructor +@Getter +public enum TradeOrderCancelTypeEnum implements ArrayValuable { + + PAY_TIMEOUT(10, "超时未支付"), + AFTER_SALE_CLOSE(20, "退款关闭"), + MEMBER_CANCEL(30, "买家取消"), + COMBINATION_CLOSE(40, "拼团关闭"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderCancelTypeEnum::getType).toArray(Integer[]::new); + + /** + * 关闭类型 + */ + private final Integer type; + /** + * 关闭类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderItemAfterSaleStatusEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderItemAfterSaleStatusEnum.java new file mode 100644 index 0000000..5a7f6a9 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderItemAfterSaleStatusEnum.java @@ -0,0 +1,49 @@ +package com.tashow.cloud.tradeapi.enums.order; + +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易订单项 - 售后状态 + * + * @author 芋道源码 + */ +@RequiredArgsConstructor +@Getter +public enum TradeOrderItemAfterSaleStatusEnum implements ArrayValuable { + + NONE(0, "未售后"), + APPLY(10, "售后中"), + SUCCESS(20, "售后成功"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderItemAfterSaleStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + /** + * 判断指定状态,是否正处于【未申请】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean isNone(Integer status) { + return ObjectUtil.equals(status, NONE.getStatus()); + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderOperateTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderOperateTypeEnum.java new file mode 100644 index 0000000..44cbd88 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderOperateTypeEnum.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.tradeapi.enums.order; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 订单操作类型的枚举 + * + * @author 陈賝 + * @since 2023/7/6 15:31 + */ +@RequiredArgsConstructor +@Getter +public enum TradeOrderOperateTypeEnum { + + MEMBER_CREATE(1, "用户下单"), + ADMIN_UPDATE_PRICE(2, "订单价格 {oldPayPrice} 修改,调整价格 {adjustPrice},实际支付金额为 {newPayPrice} 元"), + MEMBER_PAY(10, "用户付款成功"), + ADMIN_UPDATE_ADDRESS(11, "收货地址修改"), + ADMIN_DELIVERY(20, "已发货,快递公司:{expressName},快递单号:{logisticsNo}"), + MEMBER_RECEIVE(30, "用户已收货"), + SYSTEM_RECEIVE(31, "到期未收货,系统自动确认收货"), + ADMIN_PICK_UP_RECEIVE(32, "管理员自提收货"), + MEMBER_COMMENT(33, "用户评价"), + SYSTEM_COMMENT(34, "到期未评价,系统自动评价"), + MEMBER_CANCEL(40, "取消订单"), + SYSTEM_CANCEL(41, "到期未支付,系统自动取消订单"), + // 42 预留:管理员取消订单 + ADMIN_CANCEL_AFTER_SALE(43, "订单全部售后,管理员自动取消订单"), + MEMBER_DELETE(49, "删除订单"), + ; + + /** + * 操作类型 + */ + private final Integer type; + /** + * 操作描述 + */ + private final String content; + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderRefundStatusEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderRefundStatusEnum.java new file mode 100644 index 0000000..b7702e6 --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderRefundStatusEnum.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.tradeapi.enums.order; + +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易订单 - 退款状态 + * + * @author Sin + */ +@RequiredArgsConstructor +@Getter +public enum TradeOrderRefundStatusEnum implements ArrayValuable { + + NONE(0, "未退款"), + PART(10, "部分退款"), + ALL(20, "全部退款"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderRefundStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderStatusEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderStatusEnum.java new file mode 100644 index 0000000..4f667ea --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderStatusEnum.java @@ -0,0 +1,122 @@ +package com.tashow.cloud.tradeapi.enums.order; + +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import com.tashow.cloud.common.util.object.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易订单 - 状态 + * + * @author Sin + */ +@RequiredArgsConstructor +@Getter +public enum TradeOrderStatusEnum implements ArrayValuable { + + UNPAID(0, "待支付"), + UNDELIVERED(10, "待发货"), + DELIVERED(20, "已发货"), + COMPLETED(30, "已完成"), + CANCELED(40, "已取消"), + + WAITPAID(100, "等待付款"), + WAITCONFIRM(110, "等待确定"), + WAITSERVE(120, "等待服务"), + WAITACCEPT(130, "等待验收"), + SERVECANCELED(140, "取消服务"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderStatusEnum::getStatus).toArray(Integer[]::new); + + /** + * 状态值 + */ + private final Integer status; + /** + * 状态名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + // ========== 问:为什么写了很多 isXXX 和 haveXXX 的判断逻辑呢? ========== + // ========== 答:方便找到某一类判断,哪些业务正在使用 ========== + + /** + * 判断指定状态,是否正处于【未付款】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean isUnpaid(Integer status) { + return ObjectUtil.equal(UNPAID.getStatus(), status); + } + + /** + * 判断指定状态,是否正处于【待发货】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean isUndelivered(Integer status) { + return ObjectUtil.equal(UNDELIVERED.getStatus(), status); + } + + /** + * 判断指定状态,是否正处于【已发货】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean isDelivered(Integer status) { + return ObjectUtil.equals(status, DELIVERED.getStatus()); + } + + /** + * 判断指定状态,是否正处于【已取消】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean isCanceled(Integer status) { + return ObjectUtil.equals(status, CANCELED.getStatus()); + } + + /** + * 判断指定状态,是否正处于【已完成】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean isCompleted(Integer status) { + return ObjectUtil.equals(status, COMPLETED.getStatus()); + } + + /** + * 判断指定状态,是否有过【已付款】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean havePaid(Integer status) { + return ObjectUtils.equalsAny(status, UNDELIVERED.getStatus(), + DELIVERED.getStatus(), COMPLETED.getStatus()); + } + + /** + * 判断指定状态,是否有过【已发货】状态 + * + * @param status 指定状态 + * @return 是否 + */ + public static boolean haveDelivered(Integer status) { + return ObjectUtils.equalsAny(status, DELIVERED.getStatus(), COMPLETED.getStatus()); + } + +} diff --git a/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderTypeEnum.java b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderTypeEnum.java new file mode 100644 index 0000000..531f3cb --- /dev/null +++ b/tashow-feign/tashow-trade-api/src/main/java/com/tashow/cloud/tradeapi/enums/order/TradeOrderTypeEnum.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.tradeapi.enums.order; + +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.core.ArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 交易订单 - 类型 + * + * @author Sin + */ +@RequiredArgsConstructor +@Getter +public enum TradeOrderTypeEnum implements ArrayValuable { + + NORMAL(0, "普通订单"), + SECKILL(1, "秒杀订单"), + BARGAIN(2, "砍价订单"), + COMBINATION(3, "拼团订单"), + POINT(4, "积分商城"), + ; + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(TradeOrderTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 类型名 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + + public static boolean isNormal(Integer type) { + return ObjectUtil.equal(type, NORMAL.getType()); + } + + public static boolean isSeckill(Integer type) { + return ObjectUtil.equal(type, SECKILL.getType()); + } + + public static boolean isBargain(Integer type) { + return ObjectUtil.equal(type, BARGAIN.getType()); + } + + public static boolean isCombination(Integer type) { + return ObjectUtil.equal(type, COMBINATION.getType()); + } + + public static boolean isPoint(Integer type) { + return ObjectUtil.equal(type, POINT.getType()); + } + +} diff --git a/tashow-framework/tashow-framework-web/pom.xml b/tashow-framework/tashow-framework-web/pom.xml index cbe0fa2..0c14595 100644 --- a/tashow-framework/tashow-framework-web/pom.xml +++ b/tashow-framework/tashow-framework-web/pom.xml @@ -27,6 +27,15 @@ true + + com.github.xingfudeshi + knife4j-openapi3-jakarta-spring-boot-starter + + + org.springdoc + springdoc-openapi-starter-webmvc-api + + org.springframework.boot diff --git a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java index 31f77a3..914490f 100644 --- a/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java +++ b/tashow-framework/tashow-framework-web/src/main/java/com/tashow/cloud/web/web/core/handler/GlobalExceptionHandler.java @@ -324,7 +324,7 @@ public class GlobalExceptionHandler { * 处理 Table 不存在的异常情况 * * @param ex 异常 - * @return 如果是 Table 不存在的异常,则返回对应的 CommonResult + * @return 如果是 Table 不存在的异常 ,则返回对应的 CommonResult */ private CommonResult handleTableNotExists(Throwable ex) { String message = ExceptionUtil.getRootCauseMessage(ex); @@ -333,57 +333,57 @@ public class GlobalExceptionHandler { } // 1. 数据报表 if (message.contains("report_")) { - log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]"); + log.error("[报表模块 yudao-module-report - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]"); + "[报表模块 yudao-module-report - 表结构未导入]"); } // 2. 工作流 if (message.contains("bpm_")) { - log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]"); + log.error("[工作流模块 yudao-module-bpm - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]"); + "[工作流模块 yudao-module-bpm - 表结构未导入]"); } // 3. 微信公众号 if (message.contains("mp_")) { - log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); + log.error("[微信公众号 yudao-module-mp - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]"); + "[微信公众号 yudao-module-mp - 表结构未导入]"); } - // 4. 商城系统 + // 4. 订单系统 if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) { - log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); + log.error("[订单系统 tashow-module-trade"+StrUtil.subBetween(message,"Table","does")+"- 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); + "[订单系统表结构未导入]"); } // 5. ERP 系统 if (message.contains("erp_")) { - log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]"); + log.error("[ERP 系统 yudao-module-erp - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]"); + "[ERP 系统 yudao-module-erp - 表结构未导入]"); } // 6. CRM 系统 if (message.contains("crm_")) { - log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]"); + log.error("[CRM 系统 yudao-module-crm - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]"); + "[CRM 系统 yudao-module-crm - 表结构未导入]"); } // 7. 支付平台 if (message.contains("pay_")) { - log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]"); + log.error("[支付模块 yudao-module-pay - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]"); + "[支付模块 yudao-module-pay - 表结构未导入]"); } // 8. AI 大模型 if (message.contains("ai_")) { - log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); + log.error("[AI 大模型 yudao-module-ai - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); + "[AI 大模型 yudao-module-ai - 表结构未导入]"); } // 9. IOT 物联网 if (message.contains("iot_")) { - log.error("[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + log.error("[IOT 物联网 yudao-module-iot - 表结构未导入]"); return CommonResult.error(NOT_IMPLEMENTED.getCode(), - "[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); + "[IOT 物联网 yudao-module-iot - 表结构未导入]"); } return null; } diff --git a/tashow-gateway/pom.xml b/tashow-gateway/pom.xml index b7206a0..791f339 100644 --- a/tashow-gateway/pom.xml +++ b/tashow-gateway/pom.xml @@ -61,7 +61,6 @@ - ${project.artifactId} diff --git a/tashow-gateway/src/main/resources/application-local.yaml b/tashow-gateway/src/main/resources/application-local.yaml index 8756703..59b8b16 100644 --- a/tashow-gateway/src/main/resources/application-local.yaml +++ b/tashow-gateway/src/main/resources/application-local.yaml @@ -7,10 +7,10 @@ spring: username: nacos # Nacos 账号 password: nacos # Nacos 密码 discovery: # 【配置中心】配置项 - namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + namespace: dev # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP config: # 【注册中心】配置项 - namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + namespace: dev # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP # 日志文件配置 diff --git a/tashow-module/pom.xml b/tashow-module/pom.xml index ffdb6c5..a55637b 100644 --- a/tashow-module/pom.xml +++ b/tashow-module/pom.xml @@ -15,6 +15,10 @@ tashow-module-infra tashow-module-app tashow-module-product + tashow-module-ai + tashow-module-pay + tashow-module-trade + tashow-module-member diff --git a/tashow-module/tashow-module-ai/pom.xml b/tashow-module/tashow-module-ai/pom.xml new file mode 100644 index 0000000..685147e --- /dev/null +++ b/tashow-module/tashow-module-ai/pom.xml @@ -0,0 +1,96 @@ + + 4.0.0 + + com.tashow.cloud + tashow-module + ${revision} + + + tashow-module-ai + jar + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-infra-api + + + + com.tashow.cloud + tashow-framework-rpc + + + com.tashow.cloud + tashow-data-mybatis + + + com.tashow.cloud + tashow-framework-web + + + com.tashow.cloud + tashow-framework-env + + + com.tashow.cloud + tashow-framework-websocket + + + com.tashow.cloud + tashow-data-redis + + + com.tashow.cloud + tashow-data-excel + + + com.tashow.cloud + tashow-framework-security + + + org.springframework.boot + spring-boot-starter-actuator + + + + com.tashow.cloud + tashow-framework-monitor + + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/AiServerApplication.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/AiServerApplication.java new file mode 100644 index 0000000..c69a90e --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/AiServerApplication.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.ai; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 应用服务启动类 + */ +@SpringBootApplication +public class AiServerApplication { + + public static void main(String[] args) { + SpringApplication.run(AiServerApplication.class, args); + } +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleController.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleController.java new file mode 100644 index 0000000..467e677 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.ai.controller.admin.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.*; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleDO; +import com.tashow.cloud.ai.service.aisample.AiSampleService; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 样本库") +@RestController +@RequestMapping("/ai/sample") +@Validated +public class AiSampleController { + + @Resource + private AiSampleService aiSampleService; + + @PostMapping("/create") + @Operation(summary = "创建样本库-上传文件") + @PermitAll +// @PreAuthorize("@ss.hasPermission('ai:sample:create')") + public CommonResult> createAiSample(FileUploadReqVO uploadReqVO) { + return success(aiSampleService.createAiSample(uploadReqVO)); + } + + @PutMapping("/updates") + @Operation(summary = "更新样本库") +// @PreAuthorize("@ss.hasPermission('ai:sample:update')") + @PermitAll + public CommonResult updateAiSample(@Valid @RequestBody List updateReqVO) { + aiSampleService.updateAiSamples(updateReqVO); + return success(true); + } + + @PutMapping("/relate") + @Operation(summary = "添加关联标签") +// @PreAuthorize("@ss.hasPermission('ai:sample:update')") + @PermitAll + public CommonResult relate(@Valid @RequestBody AiSampleRelateTagVO relateTagVO) { + aiSampleService.relate(relateTagVO); + return success(true); + } + + @DeleteMapping("/deleteRelate") + @Operation(summary = "删除关联标签") +// @PreAuthorize("@ss.hasPermission('ai:sample:delete')") + @PermitAll + public CommonResult deleteRelate(@Valid @RequestBody AiSampleRelateTagVO relateTagVO) { + aiSampleService.deleteRelate(relateTagVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除样本库") + @Parameter(name = "id", description = "编号", required = true) +// @PreAuthorize("@ss.hasPermission('ai:sample:delete')") + @PermitAll + public CommonResult deleteAiSample(@RequestParam("id") String ids) { + aiSampleService.deleteAiSample(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得样本库") + @Parameter(name = "id", description = "编号", required = true, example = "1024") +// @PreAuthorize("@ss.hasPermission('ai:sample:query')") + @PermitAll + public CommonResult getAiSample(@RequestParam("id") Long id) { + AiSampleDO aiSample = aiSampleService.getAiSample(id); + return success(BeanUtils.toBean(aiSample, AiSampleRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得样本库分页") +// @PreAuthorize("@ss.hasPermission('ai:sample:query')") + @PermitAll + public CommonResult> getAiSamplePage(@Valid AiSamplePageReqVO pageReqVO) { + PageResult pageResult = aiSampleService.getAiSamplePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, AiSampleRespVO.class)); + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleTagController.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleTagController.java new file mode 100644 index 0000000..0fe0bbf --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleTagController.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.ai.controller.admin.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagListRespVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagSaveReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import com.tashow.cloud.ai.service.aisample.AiSampleTagService; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@Tag(name = "管理后台 - 样本标签库") +@RestController +@RequestMapping("/ai/sampleTag") +@Validated +public class AiSampleTagController { + + @Resource + private AiSampleTagService aiSampleTagService; + + @PostMapping("/create") + @Operation(summary = "创建样本标签库") +// @PreAuthorize("@ss.hasPermission('ai:sampleTag:create')") + @PermitAll + public CommonResult createAiSampleTag(@Valid @RequestBody AiSampleTagSaveReqVO createReqVO) { + return success(aiSampleTagService.createAiSampleTag(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新样本标签库") +// @PreAuthorize("@ss.hasPermission('ai:sampleTag:update')") + @PermitAll + public CommonResult updateAiSampleTag(@Valid @RequestBody AiSampleTagSaveReqVO updateReqVO) { + aiSampleTagService.updateAiSampleTag(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除样本标签库") + @Parameter(name = "id", description = "编号", required = true) +// @PreAuthorize("@ss.hasPermission('ai:sampleTag:delete')") + @PermitAll + public CommonResult deleteAiSampleTag(@RequestParam("id") Long id) { + aiSampleTagService.deleteAiSampleTag(id); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "获得样本标签库列表") +// @PreAuthorize("@ss.hasPermission('ai:sampleTag:query')") + @PermitAll + public CommonResult> getAiSampleTagList() { + return success(BeanUtils.toBean(aiSampleTagService.getAiSampleTagList(), AiSampleTagListRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得样本标签库分页") +// @PreAuthorize("@ss.hasPermission('ai:sampleTag:query')") + @PermitAll + public CommonResult> getAiSampleTagPage(@Valid AiSampleTagPageReqVO pageReqVO) { + PageResult pageResult = aiSampleTagService.getAiSampleTagPage(pageReqVO); + return success(pageResult); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleTagGroupController.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleTagGroupController.java new file mode 100644 index 0000000..54acc5d --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/AiSampleTagGroupController.java @@ -0,0 +1,68 @@ +package com.tashow.cloud.ai.controller.admin.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupRespVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupSaveReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO; +import com.tashow.cloud.ai.service.aisample.AiSampleTagGroupService; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@Tag(name = "管理后台 - 样本标签分组库") +@RestController +@RequestMapping("/ai/sampleTagGroup") +@Validated +public class AiSampleTagGroupController { + + @Resource + private AiSampleTagGroupService aiSampleTagGroupService; + + @PostMapping("/create") + @Operation(summary = "创建样本标签分组库") +// @PreAuthorize("@ss.hasPermission('ai:sampleTagGroup:create')") + @PermitAll + public CommonResult createAiSampleTagGroup(@Valid @RequestBody AiSampleTagGroupSaveReqVO createReqVO) { + return success(aiSampleTagGroupService.createAiSampleTagGroup(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新样本标签分组库") +// @PreAuthorize("@ss.hasPermission('ai:sampleTagGroup:update')") + @PermitAll + public CommonResult updateAiSampleTagGroup(@Valid @RequestBody AiSampleTagGroupSaveReqVO updateReqVO) { + aiSampleTagGroupService.updateAiSampleTagGroup(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除样本标签分组库") + @Parameter(name = "id", description = "编号", required = true) +// @PreAuthorize("@ss.hasPermission('ai:sampleTagGroup:delete')") + @PermitAll + public CommonResult deleteAiSampleTagGroup(@RequestParam("id") Long id) { + aiSampleTagGroupService.deleteAiSampleTagGroup(id); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "获得样本标签分组库列表") +// @PreAuthorize("@ss.hasPermission('ai:sampleTagGroup:query')") + @PermitAll + public CommonResult> getAiSampleTagGroupPage() { + List aiSampleTagGroupList = aiSampleTagGroupService.getAiSampleTagGroupList(); + return success(BeanUtils.toBean(aiSampleTagGroupList, AiSampleTagGroupRespVO.class)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleFileRespVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleFileRespVO.java new file mode 100644 index 0000000..4158115 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleFileRespVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 样本库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class AiSampleFileRespVO { + + @Schema(description = "文件地址") + private String fileUrl; + + @Schema(description = "文件名称") + private String fileName; +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSamplePageReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSamplePageReqVO.java new file mode 100644 index 0000000..15295e2 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSamplePageReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.tashow.cloud.common.pojo.PageParam; +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 AiSamplePageReqVO extends PageParam { + + @Schema(description = "标签ids", example = "25839") + private String tagIds; + + @Schema(description = "样本名称", example = "张三") + private String sampleName; + + @Schema(description = "样本格式", example = "1") + private String sampleMineType; + + @Schema(description = "排序", example = "正序:asc,倒序:desc") + private String sort; + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleRelateTagVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleRelateTagVO.java new file mode 100644 index 0000000..1902063 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleRelateTagVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 关联 Request VO") +@Data +public class AiSampleRelateTagVO { + + @Schema(description = "标签id") + private List tagId; + + @Schema(description = "样本ids") + private List sampleIds; +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleRespVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleRespVO.java new file mode 100644 index 0000000..54bf4aa --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleRespVO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 样本库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class AiSampleRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7701") + private Long id; + + @Schema(description = "标签列表") + private List tags; + + @Schema(description = "样本文件地址") + private String sampleFilePath; + + @Schema(description = "样本名称", example = "张三") + private String sampleName; + + @Schema(description = "样本时长") + private String sampleTime; + + @Schema(description = "样本格式", example = "1") + private String sampleMineType; + + @Schema(description = "样本大小") + private String sampleSize; + + @Schema(description = "样本注释", example = "随便") + private String remark; + + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleSaveReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleSaveReqVO.java new file mode 100644 index 0000000..e764e90 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleSaveReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 样本库新增/修改 Request VO") +@Data +public class AiSampleSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7701") + private Long id; + + @Schema(description = "样本名称", example = "张三") + private String sampleName; + + @Schema(description = "样本注释", example = "随便") + private String remark; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupPageReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupPageReqVO.java new file mode 100644 index 0000000..bc14d11 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupPageReqVO.java @@ -0,0 +1,27 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.tashow.cloud.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 样本标签分组库分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AiSampleTagGroupPageReqVO extends PageParam { + + @Schema(description = "分组名称", example = "张三") + private String groupName; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupRespVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupRespVO.java new file mode 100644 index 0000000..5b332fe --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupRespVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - 样本标签分组库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class AiSampleTagGroupRespVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "18784") + @ExcelProperty("主键") + private Long id; + + @Schema(description = "分组名称", example = "张三") + @ExcelProperty("分组名称") + private String groupName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupSaveReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupSaveReqVO.java new file mode 100644 index 0000000..ef4da7a --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagGroupSaveReqVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Schema(description = "管理后台 - 样本标签分组库新增/修改 Request VO") +@Data +public class AiSampleTagGroupSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "18784") + private Long id; + + @Schema(description = "分组名称", example = "张三") + private String groupName; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagListRespVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagListRespVO.java new file mode 100644 index 0000000..47c452a --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagListRespVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 样本标签库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class AiSampleTagListRespVO { + + @Schema(description = "标签id") + private Long id; + @Schema(description = "标签名称") + private String tagName; +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagPageReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagPageReqVO.java new file mode 100644 index 0000000..b1adf18 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagPageReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.tashow.cloud.common.pojo.PageParam; +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 AiSampleTagPageReqVO extends PageParam { + + @Schema(description = "分组id") + private Long groupId; + @Schema(description = "标签名称", example = "张三") + private String tagName; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagRespVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagRespVO.java new file mode 100644 index 0000000..db35832 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagRespVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 样本标签库 Response VO") +@Data +@ExcelIgnoreUnannotated +public class AiSampleTagRespVO { + + @Schema(description = "标签列表") + List tags; + @Schema(description = "分组列表") + List tagGroupDOS; +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagSaveReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagSaveReqVO.java new file mode 100644 index 0000000..4fba8a1 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/AiSampleTagSaveReqVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - 样本标签库新增/修改 Request VO") +@Data +public class AiSampleTagSaveReqVO { + + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32538") + private Long id; + + @Schema(description = "分组ids") + private List groupIds; + + @Schema(description = "标签名称", example = "张三") + private String tagName; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/FileUploadReqVO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/FileUploadReqVO.java new file mode 100644 index 0000000..233bfca --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/aisample/vo/FileUploadReqVO.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.ai.controller.admin.aisample.vo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +/** 管理后台 - 上传文件 Request VO */ +@Data +public class FileUploadReqVO { + + /** 文件附件 */ + @NotNull(message = "文件附件不能为空") + private MultipartFile[] files; + +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/package-info.java new file mode 100644 index 0000000..15f00f8 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/admin/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.controller.admin; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/DialogController.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/DialogController.java new file mode 100644 index 0000000..70ee840 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/DialogController.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.ai.controller.app.dialog; + + +import com.tashow.cloud.ai.controller.app.dialog.vo.DialogResp; +import com.tashow.cloud.ai.controller.app.dialog.vo.TranslateReqVo; +import com.tashow.cloud.ai.controller.app.dialog.vo.TranslateRespVo; +import com.tashow.cloud.ai.service.dialog.AiDialogService; +import com.tashow.cloud.common.pojo.CommonResult; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +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; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + +/** + * 翻译 + */ +@RestController +@RequestMapping("/ai/dialog") +@Validated +@Slf4j +public class DialogController { + + List message = List.of("渴了", "饿了", "想睡觉", "想出去玩", "想溜达", "情绪低落", "很开心", "很伤心", "想哭"); + + @Resource + private AiDialogService aiDialogService; + + + /** + * 获取对话消息列表 + */ + @GetMapping("/getDialog") + @PermitAll + public CommonResult msList() { + //获取当前登录用户 + Long userId = 1L; + return success(aiDialogService.getDialog(userId)); + } + + + /** + * 翻译 + */ + @PostMapping("/translate") + @PermitAll + public CommonResult translate(@Validated TranslateReqVo fileReqVo) { + return success(aiDialogService.translate(fileReqVo)); + } +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/AiDialogMessageRespVo.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/AiDialogMessageRespVo.java new file mode 100644 index 0000000..5c5b606 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/AiDialogMessageRespVo.java @@ -0,0 +1,76 @@ +package com.tashow.cloud.ai.controller.app.dialog.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * ai-对话消息 DO + * + * @author 芋道源码 + */ +@Schema(description = "api - 对话 Response VO") +@Data +public class AiDialogMessageRespVo { + + private Long id; + /** + * 对话id + */ + private Long dialogId; + /** + * 内容 + */ + private String contentText; + /** + * 文件时长 + */ + private Long contentDuration; + + /** + * 文本类型(1text,2image,3file,4audio) + */ + private Integer contentType; + /** + * 对话中的顺序 + */ + private Integer messageOrder; + /** + * 消息状态 1正常 0删除 + */ + private Integer messageStatus; + /** + * 宠物id + */ + private Long petId; + /** + * 宠物名称 + */ + private String petName; + /** + * 宠物头像 + */ + private String petAvatar; + /** + * 宠物类型 + */ + private String petType; + /** + * 翻译结果 + */ + private String transResult; + /** + * 翻译结果 + */ + private Integer transStatus; + /** + * 创建时间 + */ + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private String createTime; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/DialogResp.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/DialogResp.java new file mode 100644 index 0000000..ff5c266 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/DialogResp.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.ai.controller.app.dialog.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + + +@Schema(description = "api - 对话 Response VO") +@Data +public class DialogResp { + + @Schema(description = "对话id") + private Long dialogId; + @Schema(description = "对话标题") + private String title; + @Schema(description = "对话状态") + private String dialogStatus; + @Schema(description = "对话消息内容") + private List messages; +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/TranslateReqVo.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/TranslateReqVo.java new file mode 100644 index 0000000..5032589 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/TranslateReqVo.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.ai.controller.app.dialog.vo; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +/** + * 翻译接口请求vo + */ +@Data +public class TranslateReqVo { + + //对话id + private Long dialogId; + //消息id + private Long msgId; + + /** 文件附件 */ + private MultipartFile file; + + //文件时长 + private String contentDuration; + + +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/TranslateRespVo.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/TranslateRespVo.java new file mode 100644 index 0000000..0da9dfc --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/dialog/vo/TranslateRespVo.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.ai.controller.app.dialog.vo; + + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 翻译接口结果vo + */ +@Data +public class TranslateRespVo { + + //消息id + private Long id; + //对话id + private Long dialogId; + //消息顺序 + private Integer messageOrder; + //消息类型(1text,2file) + private Integer contentType; + //消息内容 若消息类型是file 则为文件地址 + private String contentText; + //消息内容 若消息类型是file 则为文件地址 + private String fileType; + //宠物类型 + private String petType; + //宠物名称 + private String petName; + //宠物头像 + private String petAvatar; + //宠物id + private Long petId; + //翻译状态 + private Integer transStatus; + //翻译结果 + private String transResult; + //文件时长 + private Long contentDuration; + //发送时间 + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private String createTime; + +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/package-info.java new file mode 100644 index 0000000..80d118b --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/app/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.controller.app; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/package-info.java new file mode 100644 index 0000000..4a424d2 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/controller/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.controller; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleDO.java new file mode 100644 index 0000000..ae22fe3 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleDO.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.ai.dal.dataobject.aisample; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.util.List; + +/** + * 样本库 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_sample") +@KeySequence("tz_ai_sample_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiSampleDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + + @TableField(exist = false) + private List tags; + + /** + * 样本文件id + */ + private String sampleFilePath; + /** + * 样本名称 + */ + private String sampleName; + /** + * 样本时长 + */ + private String sampleTime; + /** + * 样本格式 + */ + private String sampleMineType; + /** + * 样本大小 + */ + private Long sampleSize; + /** + * 样本注释 + */ + private String remark; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagDO.java new file mode 100644 index 0000000..721a716 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagDO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.ai.dal.dataobject.aisample; + +import lombok.*; +import com.baomidou.mybatisplus.annotation.*; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; + +/** + * 样本标签库 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_sample_tag") +@KeySequence("tz_ai_sample_tag_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiSampleTagDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + + /** + * 标签名称 + */ + private String tagName; + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagGroupDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagGroupDO.java new file mode 100644 index 0000000..ecf0b47 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagGroupDO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.ai.dal.dataobject.aisample; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +/** + * 样本标签分组库 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_sample_tag_group") +@KeySequence("tz_ai_sample_tag_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiSampleTagGroupDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 分组名称 + */ + private String groupName; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagGroupRelateDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagGroupRelateDO.java new file mode 100644 index 0000000..a2bcfc1 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagGroupRelateDO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.ai.dal.dataobject.aisample; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 样本标签-分组关联 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_sample_tag_group_relate") +@KeySequence("tz_ai_sample_tag_group_relate_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiSampleTagGroupRelateDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 样本标签id + */ + private Long sampleTagId; + /** + * 样本标签分组id + */ + private Long sampleTagGroupId; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagRelateDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagRelateDO.java new file mode 100644 index 0000000..ef5bcf0 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/aisample/AiSampleTagRelateDO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.ai.dal.dataobject.aisample; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 样本-标签关联 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_sample_tag_relate") +@KeySequence("tz_ai_sample_tag_relate_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiSampleTagRelateDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 样本id + */ + private Long sampleId; + /** + * 样本标签id + */ + private Long sampleTagId; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/dialog/AiDialogDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/dialog/AiDialogDO.java new file mode 100644 index 0000000..e923fef --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/dialog/AiDialogDO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.ai.dal.dataobject.dialog; + +import lombok.*; +import com.baomidou.mybatisplus.annotation.*; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; + +/** + * ai-对话 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_dialog") +@KeySequence("tz_ai_dialog_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiDialogDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 对话标题 + */ + private String title; + /** + * 用户id + */ + private Long userId; + /** + * 对话状态(1active, 2archived, 3deleted) + */ + private Integer dialogStatus; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/dialog/AiDialogMessageDO.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/dialog/AiDialogMessageDO.java new file mode 100644 index 0000000..cc26722 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/dialog/AiDialogMessageDO.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.ai.dal.dataobject.dialog; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * ai-对话消息 DO + * + * @author 芋道源码 + */ +@TableName("tz_ai_dialog_message") +@KeySequence("tz_ai_dialog_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AiDialogMessageDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 对话id + */ + private Long dialogId; + /** + * 内容 + */ + private String contentText; + /** + * 文件名 + */ + private String fileName; + /** + * 文件类型 + */ + private String fileType; + /** + * 文件时长 + */ + private Long contentDuration; + + /** + * 文本类型(1text,2image,3file,4audio) + */ + private Integer contentType; + /** + * 对话中的顺序 + */ + private Integer messageOrder; + /** + * 消息状态 1正常 0删除 + */ + private Integer messageStatus; + /** + * 宠物id + */ + private Long petId; + /** + * 宠物名称 + */ + private String petName; + /** + * 宠物头像 + */ + private String petAvatar; + /** + * 宠物类型 + */ + private String petType; + /** + * 原始结果 + */ + private String sourceResult; + /** + * 翻译结果 + */ + private String transResult; + /** + * 翻译结果 + */ + private Integer transStatus; + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/package-info.java new file mode 100644 index 0000000..0a55678 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/dataobject/package-info.java @@ -0,0 +1,2 @@ +package com.tashow.cloud.ai.dal.dataobject; +// 数据库对象 \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleMapper.java new file mode 100644 index 0000000..cfed0f5 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleMapper.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.ai.dal.mysql.aisample; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSamplePageReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleDO; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +/** + * 样本库 Mapper + */ +@Mapper +public interface AiSampleMapper extends BaseMapperX { + + default PageResult selectPage(AiSamplePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiSampleDO::getSampleName, reqVO.getSampleName()) + .orderByDesc(AiSampleDO::getId)); + } + + + @Select("") + IPage getAiSamplePage(Page objectPage, AiSamplePageReqVO pageReqVO); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagGroupMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagGroupMapper.java new file mode 100644 index 0000000..9f0404a --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagGroupMapper.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.ai.dal.mysql.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupPageReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 样本标签分组库 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiSampleTagGroupMapper extends BaseMapperX { + + default PageResult selectPage(AiSampleTagGroupPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiSampleTagGroupDO::getGroupName, reqVO.getGroupName()) + .betweenIfPresent(AiSampleTagGroupDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(AiSampleTagGroupDO::getId)); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagGroupRelateMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagGroupRelateMapper.java new file mode 100644 index 0000000..cdf2216 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagGroupRelateMapper.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.ai.dal.mysql.aisample; + +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupRelateDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 样本标签-分组关联 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiSampleTagGroupRelateMapper extends BaseMapperX { + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagMapper.java new file mode 100644 index 0000000..9056d3d --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagMapper.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.ai.dal.mysql.aisample; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +/** + * 样本标签库 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiSampleTagMapper extends BaseMapperX { + + default PageResult selectPage(AiSampleTagPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .orderByDesc(AiSampleTagDO::getId)); + } + + @Select("") + IPage getAiSampleTagPage(Page page, AiSampleTagPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagRelateMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagRelateMapper.java new file mode 100644 index 0000000..90e0336 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/aisample/AiSampleTagRelateMapper.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.ai.dal.mysql.aisample; + +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagRelateDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 样本-标签关联 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiSampleTagRelateMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/dialog/AiDialogMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/dialog/AiDialogMapper.java new file mode 100644 index 0000000..22c0cc2 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/dialog/AiDialogMapper.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.ai.dal.mysql.dialog; + +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * ai-对话 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiDialogMapper extends BaseMapperX { + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/dialog/AiDialogMessageMapper.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/dialog/AiDialogMessageMapper.java new file mode 100644 index 0000000..590ec3c --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/dialog/AiDialogMessageMapper.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.ai.dal.mysql.dialog; + +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogMessageDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import org.apache.ibatis.annotations.Mapper; + +/** + * ai-对话消息 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface AiDialogMessageMapper extends BaseMapperX { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/package-info.java new file mode 100644 index 0000000..dc66f15 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/mysql/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.dal.mysql; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/package-info.java new file mode 100644 index 0000000..91dc35a --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/dal/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.dal; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/package-info.java new file mode 100644 index 0000000..c73734f --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.framework; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/rpc/config/RpcConfiguration.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..4706ae2 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,10 @@ +package com.tashow.cloud.ai.framework.rpc.config; + +import com.tashow.cloud.infraapi.api.file.FileApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {FileApi.class}) +public class RpcConfiguration { +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/rpc/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/rpc/package-info.java new file mode 100644 index 0000000..a7f5d31 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/rpc/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.framework.rpc; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..1fb9c69 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.ai.framework.security.config; + +import com.tashow.cloud.infraapi.enums.ApiConstants; +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Infra 模块的 Security 配置 + */ +@Configuration(proxyBeanMethods = false, value = "infraSecurityConfiguration") +public class SecurityConfiguration { + + @Value("${spring.boot.admin.context-path:''}") + private String adminSeverContextPath; + + @Bean("infraAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // Spring Boot Admin Server 的安全配置 + registry.requestMatchers(adminSeverContextPath).permitAll() + .requestMatchers(adminSeverContextPath + "/**").permitAll(); + // 文件读取 + registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll(); + + // TODO 芋艿:这个每个项目都需要重复配置,得捉摸有没通用的方案 + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/security/core/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/security/core/package-info.java new file mode 100644 index 0000000..c1774ae --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.ai.framework.security.core; diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleService.java new file mode 100644 index 0000000..8e9d3c3 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleService.java @@ -0,0 +1,73 @@ +package com.tashow.cloud.ai.service.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.*; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleDO; +import com.tashow.cloud.common.pojo.PageResult; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 样本库 Service 接口 + * + * @author 芋道源码 + */ +public interface AiSampleService { + + /** + * 创建样本库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + List createAiSample(@Valid FileUploadReqVO createReqVO); + + /** + * 更新样本库 + * + * @param updateReqVO 更新信息 + */ + void updateAiSample(@Valid AiSampleSaveReqVO updateReqVO); + + /** + * 批量更新 + * @param updateReqVO + */ + void updateAiSamples(@Valid List updateReqVO); + + /** + * 关联标签 + * @param relateTagVO + */ + void relate(@Valid AiSampleRelateTagVO relateTagVO); + /** + * 删除样本库 + * + * @param id 编号 + */ + void deleteAiSample(String id); + + /** + * 删除样本库 + * @param relateTagVO 编号 + */ + void deleteRelate(AiSampleRelateTagVO relateTagVO); + + /** + * 获得样本库 + * + * @param id 编号 + * @return 样本库 + */ + AiSampleDO getAiSample(Long id); + + /** + * 获得样本库分页 + * + * @param pageReqVO 分页查询 + * @return 样本库分页 + */ + PageResult getAiSamplePage(AiSamplePageReqVO pageReqVO); + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleServiceImpl.java new file mode 100644 index 0000000..2c11b88 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleServiceImpl.java @@ -0,0 +1,180 @@ +package com.tashow.cloud.ai.service.aisample; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.tashow.cloud.ai.controller.admin.aisample.vo.*; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleDO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagRelateDO; +import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleMapper; +import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagMapper; +import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagRelateMapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infraapi.api.file.FileApi; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * 样本库 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AiSampleServiceImpl implements AiSampleService { + + @Resource + private AiSampleMapper aiSampleMapper; + @Resource + private AiSampleTagMapper aiSampleTagMapper; + @Resource + private AiSampleTagRelateMapper aiSampleTagRelateMapper; + @Resource + private FileApi fileApi; + @Value("file-server") + private String fileServer; + + @Override + @SneakyThrows + public List createAiSample(FileUploadReqVO uploadReqVO) { + //返回图片路径 + List urls = new ArrayList<>(); + /* 调用文件上传服务*/ + for (MultipartFile file : uploadReqVO.getFiles()) { + //返回上传结果 + String file1 = fileServer + fileApi.createFile(file.getBytes()); + //保存样本信息 + AiSampleDO aiSampleDO = new AiSampleDO(); + aiSampleDO.setSampleFilePath(file1); + aiSampleDO.setSampleName(file.getOriginalFilename()); + aiSampleDO.setSampleMineType(file.getContentType()); + aiSampleDO.setSampleSize(file.getSize()); + aiSampleMapper.insert(aiSampleDO); + urls.add(new AiSampleFileRespVO().setFileUrl(file1).setFileName(file.getOriginalFilename())); + } + // 返回 + return urls; + } + + @Override + public void updateAiSample(AiSampleSaveReqVO updateReqVO) { + // 校验存在 + validateAiSampleExists(updateReqVO.getId()); + // 更新 + AiSampleDO updateObj = BeanUtils.toBean(updateReqVO, AiSampleDO.class); + aiSampleMapper.updateById(updateObj); + } + + @Override + public void updateAiSamples(List updateReqVO) { + aiSampleMapper.updateBatch(BeanUtils.toBean(updateReqVO, AiSampleDO.class)); + } + + @Override + public void relate(AiSampleRelateTagVO relateTagVO) { + List tagRelateDOS = new ArrayList<>(); + for (Long sampleId : relateTagVO.getSampleIds()) { + for (Long tagId : relateTagVO.getTagId()) { + AiSampleTagRelateDO relateDO = aiSampleTagRelateMapper.selectOne( + new LambdaQueryWrapper() + .eq(AiSampleTagRelateDO::getSampleId, sampleId) + .eq(AiSampleTagRelateDO::getSampleTagId, tagId) ); + if (relateDO== null){ + relateDO = new AiSampleTagRelateDO(); + relateDO.setSampleId(sampleId); + relateDO.setSampleTagId(tagId); + tagRelateDOS.add(relateDO); + } + } + } + if (!tagRelateDOS.isEmpty()){ + aiSampleTagRelateMapper.insertBatch(tagRelateDOS); + } + } + + @Override + public void deleteRelate(AiSampleRelateTagVO relateTagVO) { + List tagRelateIds = new ArrayList<>(); + for (Long sampleId : relateTagVO.getSampleIds()) { + for (Long tagId : relateTagVO.getTagId()) { + AiSampleTagRelateDO relateDO = aiSampleTagRelateMapper.selectOne( + new LambdaQueryWrapper() + .eq(AiSampleTagRelateDO::getSampleId, sampleId) + .eq(AiSampleTagRelateDO::getSampleTagId, tagId) + ); + if (relateDO != null) { + tagRelateIds.add(relateDO.getId()); + } + } + + } + if (!tagRelateIds.isEmpty()) { + aiSampleTagRelateMapper.deleteBatchIds(tagRelateIds); + } + } + + @Override + public void deleteAiSample(String ids) { + // 删除 + aiSampleMapper.deleteByIds(Arrays.asList(ids.split(StrUtil.COMMA))); + } + + private void validateAiSampleExists(Long id) { + if (aiSampleMapper.selectById(id) == null) { +// throw exception(AI_SAMPLE_NOT_EXISTS); + } + } + + @Override + public AiSampleDO getAiSample(Long id) { + AiSampleDO aiSampleDO = aiSampleMapper.selectById(id); + aiSampleDO.setSampleFilePath(aiSampleDO.getSampleFilePath()); + //先获取关联的标签id + List tagRelateDOS = aiSampleTagRelateMapper.selectList(new LambdaQueryWrapper().eq(AiSampleTagRelateDO::getSampleId, id)); + List tagIds = tagRelateDOS.stream().map(AiSampleTagRelateDO::getSampleTagId).toList(); + aiSampleDO.setTags(aiSampleTagMapper.selectList(new LambdaQueryWrapper().in(AiSampleTagDO::getId, tagIds))); + return aiSampleDO; + } + + @Override + public PageResult getAiSamplePage(AiSamplePageReqVO pageReqVO) { +// PageResult aiSampleDOPageResult = aiSampleMapper.selectPage(pageReqVO); + IPage aiSampleDOPageResult = aiSampleMapper.getAiSamplePage(MyBatisUtils.buildPage(pageReqVO),pageReqVO); + //根据样本id获取关联的标签id + List sampleIds = aiSampleDOPageResult.getRecords().stream().map(AiSampleDO::getId).toList(); + List tagRelateDOS = aiSampleTagRelateMapper.selectList( + new LambdaQueryWrapper() + .in(!sampleIds.isEmpty(), AiSampleTagRelateDO::getSampleId, sampleIds)); + List tagIds = tagRelateDOS.stream().map(AiSampleTagRelateDO::getSampleTagId).toList(); + //获取标签信息 + List aiSampleTagDOS = aiSampleTagMapper.selectList( + new LambdaQueryWrapper() + .in(!tagIds.isEmpty(), AiSampleTagDO::getId, tagIds)); + + //封装标签信息 + for (AiSampleDO aiSampleDO : aiSampleDOPageResult.getRecords()) { + List list = tagRelateDOS.stream() + .filter(a -> ObjectUtil.equals(aiSampleDO.getId(), a.getSampleId())) + .toList(); + Object[] tagsId = list.stream().map(AiSampleTagRelateDO::getSampleTagId).toArray(); + List list1 = aiSampleTagDOS.stream().filter(a -> ArrayUtil.containsAny(tagsId, a.getId())).toList(); + aiSampleDO.setTags(list1); + aiSampleDO.setSampleFilePath(aiSampleDO.getSampleFilePath()); + } + return new PageResult<>(aiSampleDOPageResult.getRecords(), aiSampleDOPageResult.getTotal()); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupRelateService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupRelateService.java new file mode 100644 index 0000000..82081a0 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupRelateService.java @@ -0,0 +1,11 @@ +package com.tashow.cloud.ai.service.aisample; + +/** + * 样本标签-分组关联 Service 接口 + * + * @author 芋道源码 + */ +public interface AiSampleTagGroupRelateService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupRelateServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupRelateServiceImpl.java new file mode 100644 index 0000000..4b88f1e --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupRelateServiceImpl.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.ai.service.aisample; + +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * 样本标签-分组关联 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AiSampleTagGroupRelateServiceImpl implements AiSampleTagGroupRelateService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupService.java new file mode 100644 index 0000000..fa37ae6 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupService.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.ai.service.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupPageReqVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupRespVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupSaveReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO; +import com.tashow.cloud.common.pojo.PageResult; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 样本标签分组库 Service 接口 + * + * @author 芋道源码 + */ +public interface AiSampleTagGroupService { + + /** + * 创建样本标签分组库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createAiSampleTagGroup(@Valid AiSampleTagGroupSaveReqVO createReqVO); + + /** + * 更新样本标签分组库 + * + * @param updateReqVO 更新信息 + */ + void updateAiSampleTagGroup(@Valid AiSampleTagGroupSaveReqVO updateReqVO); + + /** + * 删除样本标签分组库 + * + * @param id 编号 + */ + void deleteAiSampleTagGroup(Long id); + + /** + * 获得样本标签分组库 + * + * @param id 编号 + * @return 样本标签分组库 + */ + AiSampleTagGroupDO getAiSampleTagGroup(Long id); + + /** + * 获得样本标签分组库分页 + * + * @param pageReqVO 分页查询 + * @return 样本标签分组库分页 + */ + PageResult getAiSampleTagGroupPage(AiSampleTagGroupPageReqVO pageReqVO); + + /** + * 获取标签分组列表 + * @return + */ + List getAiSampleTagGroupList(); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupServiceImpl.java new file mode 100644 index 0000000..b80e71a --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagGroupServiceImpl.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.ai.service.aisample; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupPageReqVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupSaveReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO; +import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagGroupMapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +/** + * 样本标签分组库 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AiSampleTagGroupServiceImpl implements AiSampleTagGroupService { + + @Resource + private AiSampleTagGroupMapper aiSampleTagGroupMapper; + + @Override + public Long createAiSampleTagGroup(AiSampleTagGroupSaveReqVO createReqVO) { + // 插入 + AiSampleTagGroupDO aiSampleTagGroup = BeanUtils.toBean(createReqVO, AiSampleTagGroupDO.class); + aiSampleTagGroupMapper.insert(aiSampleTagGroup); + // 返回 + return aiSampleTagGroup.getId(); + } + + @Override + public void updateAiSampleTagGroup(AiSampleTagGroupSaveReqVO updateReqVO) { + // 校验存在 + validateAiSampleTagGroupExists(updateReqVO.getId()); + // 更新 + AiSampleTagGroupDO updateObj = BeanUtils.toBean(updateReqVO, AiSampleTagGroupDO.class); + aiSampleTagGroupMapper.updateById(updateObj); + } + + @Override + public void deleteAiSampleTagGroup(Long id) { + // 校验存在 + validateAiSampleTagGroupExists(id); + // 删除 + aiSampleTagGroupMapper.deleteById(id); + } + + private void validateAiSampleTagGroupExists(Long id) { + if (aiSampleTagGroupMapper.selectById(id) == null) { +// throw exception(AI_SAMPLE_TAG_GROUP_NOT_EXISTS); + } + } + + @Override + public AiSampleTagGroupDO getAiSampleTagGroup(Long id) { + return aiSampleTagGroupMapper.selectById(id); + } + + @Override + public PageResult getAiSampleTagGroupPage(AiSampleTagGroupPageReqVO pageReqVO) { + return aiSampleTagGroupMapper.selectPage(pageReqVO); + } + + @Override + public List getAiSampleTagGroupList() { + return aiSampleTagGroupMapper.selectList(new LambdaQueryWrapper().orderByDesc(AiSampleTagGroupDO::getId)); + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagRelateService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagRelateService.java new file mode 100644 index 0000000..d0432d0 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagRelateService.java @@ -0,0 +1,11 @@ +package com.tashow.cloud.ai.service.aisample; + +/** + * 样本-标签关联 Service 接口 + * + * @author 芋道源码 + */ +public interface AiSampleTagRelateService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagRelateServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagRelateServiceImpl.java new file mode 100644 index 0000000..29f7360 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagRelateServiceImpl.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.ai.service.aisample; + +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * 样本-标签关联 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AiSampleTagRelateServiceImpl implements AiSampleTagRelateService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagService.java new file mode 100644 index 0000000..2e2a48c --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagService.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.ai.service.aisample; + +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagSaveReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import com.tashow.cloud.common.pojo.PageResult; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 样本标签库 Service 接口 + * + * @author 芋道源码 + */ +public interface AiSampleTagService { + + /** + * 创建样本标签库 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createAiSampleTag(@Valid AiSampleTagSaveReqVO createReqVO); + + /** + * 更新样本标签库 + * + * @param updateReqVO 更新信息 + */ + void updateAiSampleTag(@Valid AiSampleTagSaveReqVO updateReqVO); + + /** + * 删除样本标签库 + * + * @param id 编号 + */ + void deleteAiSampleTag(Long id); + + /** + * 获得样本标签库 + * + * @param id 编号 + * @return 样本标签库 + */ + AiSampleTagDO getAiSampleTag(Long id); + + /** + * 获得样本标签库分页 + * + * @param pageReqVO 分页查询 + * @return 样本标签库分页 + */ + PageResult getAiSampleTagPage(AiSampleTagPageReqVO pageReqVO); + + /** + * 获取样本标签列表 + * @return + */ + List getAiSampleTagList(); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagServiceImpl.java new file mode 100644 index 0000000..51c2369 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/aisample/AiSampleTagServiceImpl.java @@ -0,0 +1,103 @@ +package com.tashow.cloud.ai.service.aisample; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO; +import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagSaveReqVO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO; +import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupRelateDO; +import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagGroupRelateMapper; +import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagMapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.ArrayList; +import java.util.List; + +/** + * 样本标签库 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AiSampleTagServiceImpl implements AiSampleTagService { + + @Resource + private AiSampleTagMapper aiSampleTagMapper; + @Resource + private AiSampleTagGroupRelateMapper tagGroupRelateMapper; + + @Override + public Long createAiSampleTag(AiSampleTagSaveReqVO createReqVO) { + // 插入 + AiSampleTagDO aiSampleTag = BeanUtils.toBean(createReqVO, AiSampleTagDO.class); + aiSampleTagMapper.insert(aiSampleTag); + //插入关联 + List tagGroupRelateDOS = new ArrayList<>(); + for (Long groupId : createReqVO.getGroupIds()) { + AiSampleTagGroupRelateDO tagGroupRelateDO = new AiSampleTagGroupRelateDO(); + tagGroupRelateDO.setSampleTagId(createReqVO.getId()); + tagGroupRelateDO.setSampleTagGroupId(groupId); + tagGroupRelateDO.setSampleTagId(aiSampleTag.getId()); + tagGroupRelateDOS.add(tagGroupRelateDO); + } + tagGroupRelateMapper.insertBatch(tagGroupRelateDOS); + // 返回 + return aiSampleTag.getId(); + } + + @Override + public void updateAiSampleTag(AiSampleTagSaveReqVO updateReqVO) { + // 校验存在 + validateAiSampleTagExists(updateReqVO.getId()); + // 更新 + AiSampleTagDO updateObj = BeanUtils.toBean(updateReqVO, AiSampleTagDO.class); + aiSampleTagMapper.updateById(updateObj); + //删除所有关联 + tagGroupRelateMapper.delete(new LambdaQueryWrapper().eq(AiSampleTagGroupRelateDO::getSampleTagId, updateReqVO.getId())); + //插入关联 + List tagGroupRelateDOS = new ArrayList<>(); + for (Long groupId : updateReqVO.getGroupIds()) { + AiSampleTagGroupRelateDO tagGroupRelateDO = new AiSampleTagGroupRelateDO(); + tagGroupRelateDO.setSampleTagId(updateReqVO.getId()); + tagGroupRelateDO.setSampleTagGroupId(groupId); + tagGroupRelateDOS.add(tagGroupRelateDO); + } + tagGroupRelateMapper.insertBatch(tagGroupRelateDOS); + } + + @Override + public void deleteAiSampleTag(Long id) { + // 校验存在 + validateAiSampleTagExists(id); + // 删除 + aiSampleTagMapper.deleteById(id); + } + + private void validateAiSampleTagExists(Long id) { + if (aiSampleTagMapper.selectById(id) == null) { +// throw exception(AI_SAMPLE_TAG_NOT_EXISTS); + } + } + + @Override + public AiSampleTagDO getAiSampleTag(Long id) { + return aiSampleTagMapper.selectById(id); + } + + @Override + public PageResult getAiSampleTagPage(AiSampleTagPageReqVO pageReqVO) { + IPage prodPageList = aiSampleTagMapper.getAiSampleTagPage(MyBatisUtils.buildPage(pageReqVO),pageReqVO); + return new PageResult<>(prodPageList.getRecords(), prodPageList.getTotal()); + } + + @Override + public List getAiSampleTagList() { + return aiSampleTagMapper.selectList(new LambdaQueryWrapper().orderByDesc(AiSampleTagDO::getId)); + } +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogMessageService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogMessageService.java new file mode 100644 index 0000000..d190880 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogMessageService.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.ai.service.dialog; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogMessageDO; + +/** + * ai-对话消息 Service 接口 + * + * @author lwq + */ +public interface AiDialogMessageService extends IService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogMessageServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogMessageServiceImpl.java new file mode 100644 index 0000000..77d9a95 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogMessageServiceImpl.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.ai.service.dialog; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogMessageDO; +import com.tashow.cloud.ai.dal.mysql.dialog.AiDialogMessageMapper; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + + +/** + * ai-对话消息 Service 实现类 + * + * @author lwq + */ +@Service +@Validated +public class AiDialogMessageServiceImpl extends ServiceImpl implements AiDialogMessageService { + + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogService.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogService.java new file mode 100644 index 0000000..05b2a9b --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogService.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.ai.service.dialog; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.tashow.cloud.ai.controller.app.dialog.vo.DialogResp; +import com.tashow.cloud.ai.controller.app.dialog.vo.TranslateReqVo; +import com.tashow.cloud.ai.controller.app.dialog.vo.TranslateRespVo; +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogDO; + +/** + * ai-对话 Service 接口 + * + * @author lwq + */ +public interface AiDialogService extends IService { + + + DialogResp getDialog(Long userId); + + TranslateRespVo translate(TranslateReqVo fileReqVo); +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogServiceImpl.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogServiceImpl.java new file mode 100644 index 0000000..7f6b8b7 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/dialog/AiDialogServiceImpl.java @@ -0,0 +1,196 @@ +package com.tashow.cloud.ai.service.dialog; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.tashow.cloud.ai.controller.app.dialog.vo.AiDialogMessageRespVo; +import com.tashow.cloud.ai.controller.app.dialog.vo.DialogResp; +import com.tashow.cloud.ai.controller.app.dialog.vo.TranslateReqVo; +import com.tashow.cloud.ai.controller.app.dialog.vo.TranslateRespVo; +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogDO; +import com.tashow.cloud.ai.dal.dataobject.dialog.AiDialogMessageDO; +import com.tashow.cloud.ai.dal.mysql.dialog.AiDialogMapper; +import com.tashow.cloud.ai.dal.mysql.dialog.AiDialogMessageMapper; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.infraapi.api.file.FileApi; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.multipart.MultipartFile; + +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * ai-对话 Service 实现类 + * + * @author lwq + */ +@Service +@Validated +@Slf4j +public class AiDialogServiceImpl extends ServiceImpl implements AiDialogService { + + @Resource + private AiDialogMessageMapper aiDialogMessageMapper; + @Value("${translate-server}") + private String translateServer; + @Value("${file-server}") + private String fileServer; + @Resource + private FileApi fileApi; + + + @Override + public DialogResp getDialog(Long userId) { + AiDialogDO aiDialogDO = this.getOne(new LambdaQueryWrapper().eq(AiDialogDO::getUserId, userId)); + if (aiDialogDO == null) { + aiDialogDO = new AiDialogDO(); + aiDialogDO.setDialogStatus(1); + aiDialogDO.setTitle("xxx"); + aiDialogDO.setUserId(userId); + this.save(aiDialogDO); + } + List messageDOS = aiDialogMessageMapper.selectList(new LambdaQueryWrapper().eq(AiDialogMessageDO::getDialogId, aiDialogDO.getId())); + DialogResp resp = new DialogResp(); + resp.setDialogId(aiDialogDO.getId()); + resp.setTitle(aiDialogDO.getTitle()); + resp.setMessages(BeanUtils.toBean(messageDOS, AiDialogMessageRespVo.class)); + return resp; + } + + + @Override + @SneakyThrows + public TranslateRespVo translate(TranslateReqVo fileReqVo) { + AiDialogMessageDO messageDO = aiDialogMessageMapper.selectById(fileReqVo.getMsgId()); + if (messageDO == null) { + messageDO = new AiDialogMessageDO(); + } + String fileName = StrUtil.isBlank(messageDO.getFileName()) ? fileReqVo.getFile().getOriginalFilename() : messageDO.getFileName(); + //上传文件获取文件地址 + String fileUrl = StrUtil.isBlank(messageDO.getContentText()) ? fileServer + fileApi.createFile(fileName, "", fileReqVo.getFile().getBytes()) : messageDO.getContentText(); + //翻译结果 + translate(messageDO, fileUrl, fileName); + + //创建消息 持久化消息 + if (messageDO.getId() == null) { + messageDO.setFileName(fileName); + messageDO.setFileType(fileReqVo.getFile().getContentType()); + messageDO.setDialogId(fileReqVo.getDialogId()); + //前端无法转换时 后端进行转 + messageDO.setContentDuration( + StrUtil.isBlank(fileReqVo.getContentDuration()) || "Infinity".equals(fileReqVo.getContentDuration()) + ? 0 : Long.parseLong(fileReqVo.getContentDuration()) + ); + + messageDO.setContentText(fileUrl); + messageDO.setContentType(2); + + //获取当前最后的排序 + AiDialogMessageDO aiDialogMessageDO = aiDialogMessageMapper.selectOne(new LambdaQueryWrapper() + .eq(AiDialogMessageDO::getDialogId, fileReqVo.getDialogId()) + .orderByDesc(AiDialogMessageDO::getMessageOrder) + .last("limit 1") + ); + messageDO.setMessageOrder(aiDialogMessageDO == null ? 1 : aiDialogMessageDO.getMessageOrder() + 1); + messageDO.setCreateTime(LocalDateTime.now()); + } + int i = messageDO.getId() == null ? aiDialogMessageMapper.insert(messageDO) : aiDialogMessageMapper.updateById(messageDO); + TranslateRespVo bean = BeanUtils.toBean(messageDO, TranslateRespVo.class); + bean.setId(messageDO.getId()); + return bean; + } + + @SneakyThrows + private Long getFileDuration(MultipartFile file) { + AudioInputStream audioStream = AudioSystem.getAudioInputStream(file.getInputStream()); + return (long) (audioStream.getFrameLength() / audioStream.getFormat().getFrameRate()); + } + + + private AiDialogMessageDO translate(AiDialogMessageDO messageDO, String file, String fileName) { + //调用大模型接口 + String result = ""; + try { + result = HttpRequest.post(translateServer).form("audio_data", HttpUtil.downloadBytes(file), fileName).timeout(20000) //20秒超时时间 + .execute().body(); + } catch (Exception e) { + log.error("调用大模型翻译出错", e); + } + messageDO.setSourceResult(result); + //数据解析 + JSONObject translateResult = JSON.parseObject(result); + + if (translateResult.isEmpty() || !translateResult.containsKey("intent_result") || !translateResult.getBoolean("is_species_sound")) { + messageDO.setTransStatus(0); + messageDO.setTransResult(""); + return messageDO; + } + //标签 如 cat dog + String speciesLabels = translateResult.getString("species_labels"); + //解析翻译结果 + JSONObject probabilities = translateResult.getJSONObject("intent_result") + .getJSONObject("probabilities"); + + String resultKey = probabilities.entrySet().stream() + .filter(entry -> entry.getValue() instanceof Number) + .max(Comparator.comparingDouble(entry -> ((Number) entry.getValue()).doubleValue())) + .map(Map.Entry::getKey) + .orElse(null); + + //返回结果 + //宠物档案 todo + messageDO.setPetId(1l); + messageDO.setPetName(speciesLabels); + messageDO.setPetAvatar("https://img1.baidu.com/it/u=1224902049,3440357835&fm=253&app=138&f=JPEG?w=801&h=800"); + messageDO.setPetType(speciesLabels); + messageDO.setTransStatus(1); + messageDO.setTransResult(StrUtil.isBlank(resultKey) ? "" : resultKey.split(StrUtil.UNDERLINE)[1]); + return messageDO; + } + + + public static void main(String[] args) throws Exception { + InputStream inputStream = getInputStreamFromUrl("https://petshy.tashowz.com/admin-api/infra/file/29/get/857def513547ec33a105f71108c8ece329cb64dacc3a4779c94b0fcc3398cc32.webm"); + AudioInputStream audioStream = AudioSystem.getAudioInputStream(inputStream); + System.out.println((long) (audioStream.getFrameLength() / audioStream.getFormat().getFrameRate())); +// byte[] bytes = HttpUtil.downloadBytes("http://192.168.1.231:48080/admin-api/infra/file/29/get/c7351abf780f18600c104ec5662d843783ed8c309c01fb427046565217f51102.wav"); +// +//// File file = new File("http://192.168.1.231:48080/admin-api/infra/file/29/get/c7351abf780f18600c104ec5662d843783ed8c309c01fb427046565217f51102.wav"); +// String result = HttpRequest.post("http://43.139.42.137:8000/analyze/audio").form("audio_data", bytes, "aaa").timeout(20000) //20秒超时时间 +// .execute().body(); +// System.out.println(result); + } + + + public static InputStream getInputStreamFromUrl(String urlString) throws Exception { + URL url = new URL(urlString); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); // 可以是GET, POST等 + connection.setConnectTimeout(5000); // 设置连接超时时间 + connection.setReadTimeout(5000); // 设置读取超时时间 + int responseCode = connection.getResponseCode(); // 获取响应码 + if (responseCode == HttpURLConnection.HTTP_OK) { // 状态码200表示成功 + return connection.getInputStream(); + } else { + // 处理错误情况,例如抛出异常或返回null等 + throw new RuntimeException("Failed : HTTP error code : " + responseCode); + } + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/package-info.java b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/package-info.java new file mode 100644 index 0000000..72ec8fd --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/java/com/tashow/cloud/ai/service/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.ai.service; \ No newline at end of file diff --git a/tashow-module/tashow-module-ai/src/main/resources/application-local.yaml b/tashow-module/tashow-module-ai/src/main/resources/application-local.yaml new file mode 100644 index 0000000..fa98814 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/resources/application-local.yaml @@ -0,0 +1,18 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + # 添加字符编码配置,解决YAML解析时的字符编码问题 + encode: UTF-8 diff --git a/tashow-module/tashow-module-ai/src/main/resources/application.yaml b/tashow-module/tashow-module-ai/src/main/resources/application.yaml new file mode 100644 index 0000000..83088c9 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +server: + port: 48086 +spring: + application: + name: ai-server + profiles: + active: local + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【Nacos】的配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 diff --git a/tashow-module/tashow-module-ai/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-ai/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..df31ed4 --- /dev/null +++ b/tashow-module/tashow-module-ai/src/main/resources/logback-spring.xml @@ -0,0 +1,75 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java index c1a432d..19e9f02 100644 --- a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/controller/admin/file/FileController.java @@ -1,8 +1,5 @@ package com.tashow.cloud.infra.controller.admin.file; -import static com.tashow.cloud.common.pojo.CommonResult.success; -import static com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; - import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -24,11 +21,15 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; + /** 管理后台 - 文件存储 */ @RestController @RequestMapping("/infra/file") @Validated @Slf4j +@PermitAll public class FileController { @Resource private FileService fileService; @@ -87,6 +88,7 @@ public class FileController { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } + response.setContentLength(content.length); writeAttachment(response, path, content); } diff --git a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java index aa99e0a..422e27a 100644 --- a/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java +++ b/tashow-module/tashow-module-infra/src/main/java/com/tashow/cloud/infra/framework/file/core/client/local/LocalFileClient.java @@ -30,7 +30,7 @@ public class LocalFileClient extends AbstractFileClient { String filePath = getFilePath(path); FileUtil.writeBytes(content, filePath); // 拼接返回路径 - return super.formatFileUrl(config.getDomain(), path); + return super.formatFileUrl("", path); } @Override diff --git a/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml b/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml index ad7e60b..a781954 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml +++ b/tashow-module/tashow-module-infra/src/main/resources/application-local.yaml @@ -7,11 +7,11 @@ spring: username: nacos # Nacos 账号 password: nacos # Nacos 密码 discovery: # 【配置中心】配置项 - namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + namespace: dev # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP metadata: version: 1.0.0 # 服务实例的版本号,可用于灰度发布 config: # 【注册中心】配置项 - namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + namespace: dev # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP diff --git a/tashow-module/tashow-module-infra/src/main/resources/application.yaml b/tashow-module/tashow-module-infra/src/main/resources/application.yaml index e4e14f6..2fdefa0 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/application.yaml +++ b/tashow-module/tashow-module-infra/src/main/resources/application.yaml @@ -10,4 +10,4 @@ spring: - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 - optional:nacos:application.yaml # 加载【Nacos】的配置 - optional:nacos:tenant.yaml # 加载【Nacos】的配置 - - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm index 5aa3bae..e47b61e 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/controller.vm @@ -5,10 +5,6 @@ import ${jakartaPackage}.annotation.Resource; import org.springframework.validation.annotation.Validated; #if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Operation; - import ${jakartaPackage}.validation.constraints.*; import ${jakartaPackage}.validation.*; import ${jakartaPackage}.servlet.http.*; @@ -34,7 +30,9 @@ import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.bu #end import ${basePackage}.module.${table.moduleName}.service.${table.businessName}.${table.className}Service; -@Tag(name = "${sceneEnum.name} - ${table.classComment}") +/** + * ${sceneEnum.name} - ${table.classComment} + */ @RestController ##二级的 businessName 暂时不算在 HTTP 路径上,可以根据需要写 @RequestMapping("/${table.moduleName}/${simpleClassName_strikeCase}") @@ -44,8 +42,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { @Resource private ${table.className}Service ${classNameVar}Service; + /** + * 创建${table.classComment} + */ @PostMapping("/create") - @Operation(summary = "创建${table.classComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')") #end @@ -53,8 +53,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { return success(${classNameVar}Service.create${simpleClassName}(createReqVO)); } + /** + * 更新${table.classComment} + */ @PutMapping("/update") - @Operation(summary = "更新${table.classComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')") #end @@ -63,9 +65,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { return success(true); } + /** + * 删除${table.classComment} + */ @DeleteMapping("/delete") - @Operation(summary = "删除${table.classComment}") - @Parameter(name = "id", description = "编号", required = true) #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')") #end @@ -74,9 +77,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { return success(true); } + /** + * 获得${table.classComment} + */ @GetMapping("/get") - @Operation(summary = "获得${table.classComment}") - @Parameter(name = "id", description = "编号", required = true, example = "1024") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end @@ -86,8 +90,11 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { } #if ( $table.templateType != 2 ) + + /** + * 获得${table.classComment}分页 + */ @GetMapping("/page") - @Operation(summary = "获得${table.classComment}分页") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end @@ -98,8 +105,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { ## 特殊:树表专属逻辑(树不需要分页接口) #else + /** + * 获得${table.classComment}列表 + */ @GetMapping("/list") - @Operation(summary = "获得${table.classComment}列表") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end @@ -109,8 +118,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { } #end + /** + * 导出${table.classComment} Excel + */ @GetMapping("/export-excel") - @Operation(summary = "导出${table.classComment} Excel") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:export')") #end @@ -149,9 +160,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { ## 情况一:MASTER_ERP 时,需要分查询页子表 #if ( $table.templateType == 11 ) + /** + * 获得${subTable.classComment}分页 + */ @GetMapping("/${subSimpleClassName_strikeCase}/page") - @Operation(summary = "获得${subTable.classComment}分页") - @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end @@ -163,9 +175,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { ## 情况二:非 MASTER_ERP 时,需要列表查询子表 #else #if ( $subTable.subJoinMany ) + /** + * 获得${subTable.classComment}列表 + */ @GetMapping("/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}") - @Operation(summary = "获得${subTable.classComment}列表") - @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end @@ -174,9 +187,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { } #else + /** + * 获得${subTable.classComment} + */ @GetMapping("/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}") - @Operation(summary = "获得${subTable.classComment}") - @Parameter(name = "${subJoinColumn.javaField}", description = "${subJoinColumn.columnComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end @@ -188,8 +202,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { #end ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 #if ( $table.templateType == 11 ) + /** + * 创建${subTable.classComment} + */ @PostMapping("/${subSimpleClassName_strikeCase}/create") - @Operation(summary = "创建${subTable.classComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:create')") #end @@ -197,8 +213,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { return success(${classNameVar}Service.create${subSimpleClassName}(${subClassNameVar})); } + /** + * 更新${subTable.classComment} + */ @PutMapping("/${subSimpleClassName_strikeCase}/update") - @Operation(summary = "更新${subTable.classComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:update')") #end @@ -207,9 +225,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { return success(true); } + /** + * 删除${subTable.classComment} + */ @DeleteMapping("/${subSimpleClassName_strikeCase}/delete") - @Parameter(name = "id", description = "编号", required = true) - @Operation(summary = "删除${subTable.classComment}") #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:delete')") #end @@ -218,9 +237,10 @@ public class ${sceneEnum.prefixClass}${table.className}Controller { return success(true); } + /** + * 获得${subTable.classComment} + */ @GetMapping("/${subSimpleClassName_strikeCase}/get") - @Operation(summary = "获得${subTable.classComment}") - @Parameter(name = "id", description = "编号", required = true) #if ($sceneEnum.scene == 1) @PreAuthorize("@ss.hasPermission('${permissionPrefix}:query')") #end diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm index 46b6a25..4797c0b 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/listReqVO.vm @@ -2,7 +2,7 @@ package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePac import lombok.*; import java.util.*; -import io.swagger.v3.oas.annotations.media.Schema; + import ${PageParamClassName}; #foreach ($column in $columns) #if (${column.javaType} == "BigDecimal") @@ -22,18 +22,24 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; #end ## 字段模板 #macro(columnTpl $prefix $prefixStr) - @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + /** + * ${prefixStr}${column.columnComment}"#if ("$!column.example" != " + */, example = "${column.example}"#end) private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end; #end -@Schema(description = "${sceneEnum.name} - ${table.classComment}列表 Request VO") +/** + * ${sceneEnum.name} - ${table.classComment}列表 Request VO + */ @Data public class ${sceneEnum.prefixClass}${table.className}ListReqVO { #foreach ($column in $columns) #if (${column.listOperation})##查询操作 #if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候 - @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + /** + * ${column.columnComment}"#if ("$!column.example" != " + */, example = "${column.example}"#end) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private ${column.javaType}[] ${column.javaField}; #else##情况二,非 Between 的时间 diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm index 003bac9..f7841c0 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/pageReqVO.vm @@ -2,7 +2,7 @@ package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePac import lombok.*; import java.util.*; -import io.swagger.v3.oas.annotations.media.Schema; + import ${PageParamClassName}; #foreach ($column in $columns) #if (${column.javaType} == "BigDecimal") @@ -22,11 +22,15 @@ import static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; #end ## 字段模板 #macro(columnTpl $prefix $prefixStr) - @Schema(description = "${prefixStr}${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + /** + * ${prefixStr}${column.columnComment}"#if ("$!column.example" != " + */, example = "${column.example}"#end) private ${column.javaType}#if ("$!prefix" != "") ${prefix}${JavaField}#else ${column.javaField}#end; #end -@Schema(description = "${sceneEnum.name} - ${table.classComment}分页 Request VO") +/** + * ${sceneEnum.name} - ${table.classComment}分页 Request VO + */ @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @@ -35,7 +39,9 @@ public class ${sceneEnum.prefixClass}${table.className}PageReqVO extends PagePar #foreach ($column in $columns) #if (${column.listOperation})##查询操作 #if (${column.listOperationCondition} == "BETWEEN")## 情况一,Between 的时候 - @Schema(description = "${column.columnComment}"#if ("$!column.example" != ""), example = "${column.example}"#end) + /** + * ${column.columnComment}"#if ("$!column.example" != " + */, example = "${column.example}"#end) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private ${column.javaType}[] ${column.javaField}; #else##情况二,非 Between 的时间 diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm index 24c3519..e4fcf6f 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/respVO.vm @@ -1,6 +1,6 @@ package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; -import io.swagger.v3.oas.annotations.media.Schema; + import lombok.*; import java.util.*; ## 处理 BigDecimal 字段的引入 @@ -28,7 +28,9 @@ import ${DictConvertClassName}; #end #end -@Schema(description = "${sceneEnum.name} - ${table.classComment} Response VO") +/** + * ${sceneEnum.name} - ${table.classComment} Response VO + */ @Data @ExcelIgnoreUnannotated public class ${sceneEnum.prefixClass}${table.className}RespVO { @@ -37,7 +39,9 @@ public class ${sceneEnum.prefixClass}${table.className}RespVO { #foreach ($column in $columns) #if (${column.listOperationResult}) ## 1. 处理 Swagger 注解 - @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end) + /** + * ${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != " + */, example = "${column.example}"#end) ## 2. 处理 Excel 导出 #if ("$!column.dictType" != "")##处理枚举值 @ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class) diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm index b432c75..ba32472 100644 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm +++ b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/controller/vo/saveReqVO.vm @@ -1,6 +1,6 @@ package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo; -import io.swagger.v3.oas.annotations.media.Schema; + import lombok.*; import java.util.*; import ${jakartaPackage}.validation.constraints.*; @@ -24,7 +24,9 @@ import java.time.LocalDateTime; import ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO; #end -@Schema(description = "${sceneEnum.name} - ${table.classComment}新增/修改 Request VO") +/** + * ${sceneEnum.name} - ${table.classComment}新增/修改 Request VO + */ @Data public class ${sceneEnum.prefixClass}${table.className}SaveReqVO { @@ -32,7 +34,9 @@ public class ${sceneEnum.prefixClass}${table.className}SaveReqVO { #foreach ($column in $columns) #if (${column.createOperation} || ${column.updateOperation}) ## 1. 处理 Swagger 注解 - @Schema(description = "${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != ""), example = "${column.example}"#end) + /** + * ${column.columnComment}"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if ("$!column.example" != " + */, example = "${column.example}"#end) ## 2. 处理 Validator 参数校验 #if (!${column.nullable} && !${column.primaryKey}) #if (${column.javaType} == 'String') @@ -51,11 +55,15 @@ public class ${sceneEnum.prefixClass}${table.className}SaveReqVO { #foreach ($subTable in $subTables) #set ($index = $foreach.count - 1) #if ( $subTable.subJoinMany) - @Schema(description = "${subTable.classComment}列表") + /** + * ${subTable.classComment}列表 + */ private List<${subTable.className}DO> ${subClassNameVars.get($index)}s; #else - @Schema(description = "${subTable.classComment}") + /** + * ${subTable.classComment} + */ private ${subTable.className}DO ${subClassNameVars.get($index)}; #end diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/test/serviceTest.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/java/test/serviceTest.vm deleted file mode 100644 index bfd4600..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/java/test/serviceTest.vm +++ /dev/null @@ -1,168 +0,0 @@ -package ${basePackage}.module.${table.moduleName}.service.${table.businessName}; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; - -import ${jakartaPackage}.annotation.Resource; - -import ${baseFrameworkPackage}.test.core.ut.BaseDbUnitTest; - -import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*; -import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO; -import ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper; -import ${PageResultClassName}; - -import ${jakartaPackage}.annotation.Resource; -import org.springframework.context.annotation.Import; -import java.util.*; -import java.time.LocalDateTime; - -import static cn.hutool.core.util.RandomUtil.*; -import static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*; -import static ${baseFrameworkPackage}.test.core.util.AssertUtils.*; -import static ${baseFrameworkPackage}.test.core.util.RandomUtils.*; -import static ${LocalDateTimeUtilsClassName}.*; -import static ${ObjectUtilsClassName}.*; -import static ${DateUtilsClassName}.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -## 字段模板 -#macro(getPageCondition $VO) - // mock 数据 - ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到 - #foreach ($column in $columns) - #if (${column.listOperation}) - #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 - o.set$JavaField(null); - #end - #end - }); - ${classNameVar}Mapper.insert(db${simpleClassName}); - #foreach ($column in $columns) - #if (${column.listOperation}) - #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 - // 测试 ${column.javaField} 不匹配 - ${classNameVar}Mapper.insert(cloneIgnoreId(db${simpleClassName}, o -> o.set$JavaField(null))); - #end - #end - // 准备参数 - ${sceneEnum.prefixClass}${table.className}${VO} reqVO = new ${sceneEnum.prefixClass}${table.className}${VO}(); - #foreach ($column in $columns) - #if (${column.listOperation}) - #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写 - #if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况 - reqVO.set${JavaField}(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); - #else - reqVO.set$JavaField(null); - #end - #end - #end -#end -/** - * {@link ${table.className}ServiceImpl} 的单元测试类 - * - * @author ${table.author} - */ -@Import(${table.className}ServiceImpl.class) -public class ${table.className}ServiceImplTest extends BaseDbUnitTest { - - @Resource - private ${table.className}ServiceImpl ${classNameVar}Service; - - @Resource - private ${table.className}Mapper ${classNameVar}Mapper; - - @Test - public void testCreate${simpleClassName}_success() { - // 准备参数 - ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class).setId(null); - - // 调用 - ${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(createReqVO); - // 断言 - assertNotNull(${classNameVar}Id); - // 校验记录的属性是否正确 - ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id); - assertPojoEquals(createReqVO, ${classNameVar}, "id"); - } - - @Test - public void testUpdate${simpleClassName}_success() { - // mock 数据 - ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class); - ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据 - // 准备参数 - ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class, o -> { - o.setId(db${simpleClassName}.getId()); // 设置更新的 ID - }); - - // 调用 - ${classNameVar}Service.update${simpleClassName}(updateReqVO); - // 校验是否更新正确 - ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(updateReqVO.getId()); // 获取最新的 - assertPojoEquals(updateReqVO, ${classNameVar}); - } - - @Test - public void testUpdate${simpleClassName}_notExists() { - // 准备参数 - ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class); - - // 调用, 并断言异常 - assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(updateReqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); - } - - @Test - public void testDelete${simpleClassName}_success() { - // mock 数据 - ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class); - ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据 - // 准备参数 - ${primaryColumn.javaType} id = db${simpleClassName}.getId(); - - // 调用 - ${classNameVar}Service.delete${simpleClassName}(id); - // 校验数据不存在了 - assertNull(${classNameVar}Mapper.selectById(id)); - } - - @Test - public void testDelete${simpleClassName}_notExists() { - // 准备参数 - ${primaryColumn.javaType} id = random${primaryColumn.javaType}Id(); - - // 调用, 并断言异常 - assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS); - } - -## 特殊:树表专属逻辑(树不需要分页接口) -#if ( $table.templateType != 2 ) - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGet${simpleClassName}Page() { - #getPageCondition("PageReqVO") - - // 调用 - PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO); - // 断言 - assertEquals(1, pageResult.getTotal()); - assertEquals(1, pageResult.getList().size()); - assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0)); - } -#else - @Test - @Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解 - public void testGet${simpleClassName}List() { - #getPageCondition("ListReqVO") - - // 调用 - List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO); - // 断言 - assertEquals(1, list.size()); - assertPojoEquals(db${simpleClassName}, list.get(0)); - } -#end - -} \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/h2.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/h2.vm deleted file mode 100644 index a073fdb..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/h2.vm +++ /dev/null @@ -1,37 +0,0 @@ --- 将该建表 SQL 语句,添加到 yudao-module-${table.moduleName}-biz 模块的 test/resources/sql/create_tables.sql 文件里 -CREATE TABLE IF NOT EXISTS "${table.tableName.toLowerCase()}" ( -#foreach ($column in $columns) -#if (${column.javaType} == 'Long') - #set ($dataType='bigint') -#elseif (${column.javaType} == 'Integer') - #set ($dataType='int') -#elseif (${column.javaType} == 'Boolean') - #set ($dataType='bit') -#elseif (${column.javaType} == 'Date') - #set ($dataType='datetime') -#else - #set ($dataType='varchar') -#end - #if (${column.primaryKey})##处理主键 - "${column.javaField}"#if (${column.javaType} == 'String') ${dataType} NOT NULL#else ${dataType} NOT NULL GENERATED BY DEFAULT AS IDENTITY#end, - #else - #if (${column.columnName} == 'create_time') - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - #elseif (${column.columnName} == 'update_time') - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - #elseif (${column.columnName} == 'creator' || ${column.columnName} == 'updater') - "${column.columnName}" ${dataType} DEFAULT '', - #elseif (${column.columnName} == 'deleted') - "deleted" bit NOT NULL DEFAULT FALSE, - #elseif (${column.columnName} == 'tenant_id') - "tenant_id" bigint NOT NULL DEFAULT 0, - #else - "${column.columnName.toLowerCase()}" ${dataType}#if (${column.nullable} == false) NOT NULL#end, - #end - #end -#end - PRIMARY KEY ("${primaryColumn.columnName.toLowerCase()}") -) COMMENT '${table.tableComment}'; - --- 将该删表 SQL 语句,添加到 yudao-module-${table.moduleName}-biz 模块的 test/resources/sql/clean.sql 文件里 -DELETE FROM "${table.tableName}"; \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/sql.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/sql.vm deleted file mode 100644 index 41b107d..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/sql/sql.vm +++ /dev/null @@ -1,28 +0,0 @@ --- 菜单 SQL -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status, component_name -) -VALUES ( - '${table.classComment}管理', '', 2, 0, ${table.parentMenuId}, - '${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}' -); - --- 按钮父菜单ID --- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码 -SELECT @parentId := LAST_INSERT_ID(); - --- 按钮 SQL -#set ($functionNames = ['查询', '创建', '更新', '删除', '导出']) -#set ($functionOps = ['query', 'create', 'update', 'delete', 'export']) -#foreach ($functionName in $functionNames) -#set ($index = $foreach.count - 1) -INSERT INTO system_menu( - name, permission, type, sort, parent_id, - path, icon, component, status -) -VALUES ( - '${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId, - '', '', '', 0 -); -#end \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/api/api.js.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/api/api.js.vm deleted file mode 100644 index 835c019..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/api/api.js.vm +++ /dev/null @@ -1,141 +0,0 @@ -import request from '@/utils/request' -#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") - -// 创建${table.classComment} -export function create${simpleClassName}(data) { - return request({ - url: '${baseURL}/create', - method: 'post', - data: data - }) -} - -// 更新${table.classComment} -export function update${simpleClassName}(data) { - return request({ - url: '${baseURL}/update', - method: 'put', - data: data - }) -} - -// 删除${table.classComment} -export function delete${simpleClassName}(id) { - return request({ - url: '${baseURL}/delete?id=' + id, - method: 'delete' - }) -} - -// 获得${table.classComment} -export function get${simpleClassName}(id) { - return request({ - url: '${baseURL}/get?id=' + id, - method: 'get' - }) -} - -#if ( $table.templateType != 2 ) -// 获得${table.classComment}分页 -export function get${simpleClassName}Page(params) { - return request({ - url: '${baseURL}/page', - method: 'get', - params - }) -} -#else -// 获得${table.classComment}列表 -export function get${simpleClassName}List(params) { - return request({ - url: '${baseURL}/list', - method: 'get', - params - }) -} -#end -// 导出${table.classComment} Excel -export function export${simpleClassName}Excel(params) { - return request({ - url: '${baseURL}/export-excel', - method: 'get', - params, - responseType: 'blob' - }) -} -## 特殊:主子表专属逻辑 -#foreach ($subTable in $subTables) - #set ($index = $foreach.count - 1) - #set ($subSimpleClassName = $subSimpleClassNames.get($index)) - #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 - #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 - #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 - #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) - #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) - #set ($subClassNameVar = $subClassNameVars.get($index)) - -// ==================== 子表($subTable.classComment) ==================== - ## 情况一:MASTER_ERP 时,需要分查询页子表 - #if ($table.templateType == 11) - // 获得${subTable.classComment}分页 - export function get${subSimpleClassName}Page(params) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/page', - method: 'get', - params - }) - } - ## 情况二:非 MASTER_ERP 时,需要列表查询子表 - #else - #if ($subTable.subJoinMany) - // 获得${subTable.classComment}列表 - export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField}, - method: 'get' - }) - } - #else - // 获得${subTable.classComment} - export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField}, - method: 'get' - }) - } - #end - #end - ## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 - #if ($table.templateType == 11) - // 新增${subTable.classComment} - export function create${subSimpleClassName}(data) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/create', - method: 'post', - data - }) - } - // 修改${subTable.classComment} - export function update${subSimpleClassName}(data) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/update', - method: 'post', - data - }) - } - // 删除${subTable.classComment} - export function delete${subSimpleClassName}(id) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/delete?id=' + id, - method: 'delete' - }) - } - // 获得${subTable.classComment} - export function get${subSimpleClassName}(id) { - return request({ - url: '${baseURL}/${subSimpleClassName_strikeCase}/get?id=' + id, - method: 'get' - }) - } - #end -#end \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm deleted file mode 100644 index 99aa91a..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm +++ /dev/null @@ -1,205 +0,0 @@ -#set ($subTable = $subTables.get($subIndex))##当前表 -#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 -#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 - - - diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm deleted file mode 100644 index ca266be..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm +++ /dev/null @@ -1,2 +0,0 @@ -## 主表的 normal 和 inner 使用相同的 form 表单 -#parse("codegen/vue/views/components/form_sub_normal.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm deleted file mode 100644 index 48a404a..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm +++ /dev/null @@ -1,347 +0,0 @@ -#set ($subTable = $subTables.get($subIndex))##当前表 -#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 - - - diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm deleted file mode 100644 index 589736b..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm +++ /dev/null @@ -1,165 +0,0 @@ -#set ($subTable = $subTables.get($subIndex))##当前表 -#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 - - - diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm deleted file mode 100644 index 90b8e41..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm +++ /dev/null @@ -1,4 +0,0 @@ -## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: -## 1)inner 使用 list 不分页,erp 使用 page 分页 -## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 -#parse("codegen/vue/views/components/list_sub_erp.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/form.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/form.vue.vm deleted file mode 100644 index 634d05d..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/form.vue.vm +++ /dev/null @@ -1,320 +0,0 @@ - - - diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/index.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/index.vue.vm deleted file mode 100644 index 9c1e124..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue/views/index.vue.vm +++ /dev/null @@ -1,340 +0,0 @@ - - - diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm deleted file mode 100644 index c3044fb..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/api/api.ts.vm +++ /dev/null @@ -1,115 +0,0 @@ -import request from '@/config/axios' -#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") - -// ${table.classComment} VO -export interface ${simpleClassName}VO { -#foreach ($column in $columns) -#if ($column.createOperation || $column.updateOperation) -#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") - ${column.javaField}: number // ${column.columnComment} -#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime") - ${column.javaField}: Date // ${column.columnComment} -#else - ${column.javaField}: ${column.javaType.toLowerCase()} // ${column.columnComment} -#end -#end -#end -} - -// ${table.classComment} API -export const ${simpleClassName}Api = { -#if ( $table.templateType != 2 ) - // 查询${table.classComment}分页 - get${simpleClassName}Page: async (params: any) => { - return await request.get({ url: `${baseURL}/page`, params }) - }, -#else - // 查询${table.classComment}列表 - get${simpleClassName}List: async (params) => { - return await request.get({ url: `${baseURL}/list`, params }) - }, -#end - - // 查询${table.classComment}详情 - get${simpleClassName}: async (id: number) => { - return await request.get({ url: `${baseURL}/get?id=` + id }) - }, - - // 新增${table.classComment} - create${simpleClassName}: async (data: ${simpleClassName}VO) => { - return await request.post({ url: `${baseURL}/create`, data }) - }, - - // 修改${table.classComment} - update${simpleClassName}: async (data: ${simpleClassName}VO) => { - return await request.put({ url: `${baseURL}/update`, data }) - }, - - // 删除${table.classComment} - delete${simpleClassName}: async (id: number) => { - return await request.delete({ url: `${baseURL}/delete?id=` + id }) - }, - - // 导出${table.classComment} Excel - export${simpleClassName}: async (params) => { - return await request.download({ url: `${baseURL}/export-excel`, params }) - }, -## 特殊:主子表专属逻辑 -#foreach ($subTable in $subTables) -#set ($index = $foreach.count - 1) -#set ($subSimpleClassName = $subSimpleClassNames.get($index)) -#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段 -#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段 -#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 -#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index)) -#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) -#set ($subClassNameVar = $subClassNameVars.get($index)) - -// ==================== 子表($subTable.classComment) ==================== -## 情况一:MASTER_ERP 时,需要分查询页子表 -#if ( $table.templateType == 11 ) - - // 获得${subTable.classComment}分页 - get${subSimpleClassName}Page: async (params) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/page`, params }) - }, -## 情况二:非 MASTER_ERP 时,需要列表查询子表 -#else - #if ( $subTable.subJoinMany ) - - // 获得${subTable.classComment}列表 - get${subSimpleClassName}ListBy${SubJoinColumnName}: async (${subJoinColumn.javaField}) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) - }, - #else - - // 获得${subTable.classComment} - get${subSimpleClassName}By${SubJoinColumnName}: async (${subJoinColumn.javaField}) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} }) - }, - #end -#end -## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作 -#if ( $table.templateType == 11 ) - // 新增${subTable.classComment} - create${subSimpleClassName}: async (data) => { - return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data }) - }, - - // 修改${subTable.classComment} - update${subSimpleClassName}: async (data) => { - return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data }) - }, - - // 删除${subTable.classComment} - delete${subSimpleClassName}: async (id: number) => { - return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id }) - }, - - // 获得${subTable.classComment} - get${subSimpleClassName}: async (id: number) => { - return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id }) - }, -#end -#end -} diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm deleted file mode 100644 index 81cd977..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm +++ /dev/null @@ -1,204 +0,0 @@ -#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 -#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 - - \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm deleted file mode 100644 index d8542c3..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm +++ /dev/null @@ -1,2 +0,0 @@ -## 主表的 normal 和 inner 使用相同的 form 表单 -#parse("codegen/vue3/views/components/form_sub_normal.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm deleted file mode 100644 index 3fa1eff..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm +++ /dev/null @@ -1,360 +0,0 @@ -#set ($subTable = $subTables.get($subIndex))##当前表 -#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 - - \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm deleted file mode 100644 index 3f0710e..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm +++ /dev/null @@ -1,184 +0,0 @@ -#set ($subTable = $subTables.get($subIndex))##当前表 -#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) -#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 -#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 - - \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm deleted file mode 100644 index 3fe6488..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm +++ /dev/null @@ -1,4 +0,0 @@ -## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: -## 1)inner 使用 list 不分页,erp 使用 page 分页 -## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 -#parse("codegen/vue3/views/components/list_sub_erp.vue.vm") \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm deleted file mode 100644 index e37474b..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/form.vue.vm +++ /dev/null @@ -1,300 +0,0 @@ - - \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm deleted file mode 100644 index 399b58e..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3/views/index.vue.vm +++ /dev/null @@ -1,374 +0,0 @@ - - - \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/api/api.ts.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/api/api.ts.vm deleted file mode 100644 index b7f2651..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/api/api.ts.vm +++ /dev/null @@ -1,32 +0,0 @@ -import { defHttp } from '@/utils/http/axios' -#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") - -// 查询${table.classComment}列表 -export function get${simpleClassName}Page(params) { - return defHttp.get({ url: '${baseURL}/page', params }) -} - -// 查询${table.classComment}详情 -export function get${simpleClassName}(id: number) { - return defHttp.get({ url: `${baseURL}/get?id=${id}` }) -} - -// 新增${table.classComment} -export function create${simpleClassName}(data) { - return defHttp.post({ url: '${baseURL}/create', data }) -} - -// 修改${table.classComment} -export function update${simpleClassName}(data) { - return defHttp.put({ url: '${baseURL}/update', data }) -} - -// 删除${table.classComment} -export function delete${simpleClassName}(id: number) { - return defHttp.delete({ url: `${baseURL}/delete?id=${id}` }) -} - -// 导出${table.classComment} Excel -export function export${simpleClassName}(params) { - return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls') -} diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm deleted file mode 100644 index 56f4e82..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/data.ts.vm +++ /dev/null @@ -1,260 +0,0 @@ -import type {BasicColumn, FormSchema} from '@/components/Table' -import {useRender} from '@/components/Table' -import {DICT_TYPE, getDictOptions} from '@/utils/dict' - -export const columns: BasicColumn[] = [ -#foreach($column in $columns) -#if ($column.listOperationResult) - #set ($dictType=$column.dictType) - #set ($javaField = $column.javaField) - #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) - #set ($comment=$column.columnComment) -#if ($column.javaType == "LocalDateTime")## 时间类型 - { - title: '${comment}', - dataIndex: '${javaField}', - width: 180, - customRender: ({ text }) => { - return useRender.renderDate(text) - }, - }, -#elseif("" != $column.dictType)## 数据字典 - { - title: '${comment}', - dataIndex: '${javaField}', - width: 180, - customRender: ({ text }) => { - return useRender.renderDict(text, DICT_TYPE.$dictType.toUpperCase()) - }, - }, -#else - { - title: '${comment}', - dataIndex: '${javaField}', - width: 160, - }, -#end -#end -#end -] - -export const searchFormSchema: FormSchema[] = [ -#foreach($column in $columns) -#if ($column.listOperation) - #set ($dictType=$column.dictType) - #set ($javaType = $column.javaType) - #set ($javaField = $column.javaField) - #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) - #set ($comment=$column.columnComment) - #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") - #set ($dictMethod = "number") - #elseif ($javaType == "String") - #set ($dictMethod = "string") - #elseif ($javaType == "Boolean") - #set ($dictMethod = "boolean") - #end - { - label: '${comment}', - field: '${javaField}', - #if ($column.htmlType == "input") - component: 'Input', - #elseif ($column.htmlType == "select") - component: 'Select', - componentProps: { - #if ("" != $dictType)## 设置了 dictType 数据字典的情况 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else## 未设置 dictType 数据字典的情况 - options: [], - #end - }, - #elseif ($column.htmlType == "radio") - component: 'RadioButtonGroup', - componentProps: { - #if ("" != $dictType)## 设置了 dictType 数据字典的情况 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else## 未设置 dictType 数据字典的情况 - options: [], - #end - }, - #elseif($column.htmlType == "datetime") - component: 'RangePicker', - #end - colProps: { span: 8 }, - }, -#end -#end -] - -export const createFormSchema: FormSchema[] = [ - { - label: '编号', - field: 'id', - show: false, - component: 'Input', - }, -#foreach($column in $columns) -#if ($column.createOperation) - #set ($dictType = $column.dictType) - #set ($javaType = $column.javaType) - #set ($javaField = $column.javaField) - #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) - #set ($comment = $column.columnComment) - #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") - #set ($dictMethod = "number") - #elseif ($javaType == "String") - #set ($dictMethod = "string") - #elseif ($javaType == "Boolean") - #set ($dictMethod = "boolean") - #end -#if (!$column.primaryKey)## 忽略主键,不用在表单里 - { - label: '${comment}', - field: '${javaField}', - #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 - required: true, - #end - #if ($column.htmlType == "input") - component: 'Input', - #elseif($column.htmlType == "imageUpload")## 图片上传 - component: 'FileUpload', - componentProps: { - fileType: 'image', - maxCount: 1, - }, - #elseif($column.htmlType == "fileUpload")## 文件上传 - component: 'FileUpload', - componentProps: { - fileType: 'file', - maxCount: 1, - }, - #elseif($column.htmlType == "editor")## 文本编辑器 - component: 'Editor', - #elseif($column.htmlType == "select")## 下拉框 - component: 'Select', - componentProps: { - #if ("" != $dictType)## 有数据字典 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else##没数据字典 - options:[], - #end - }, - #elseif($column.htmlType == "checkbox")## 多选框 - component: 'Checkbox', - componentProps: { - #if ("" != $dictType)## 有数据字典 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else##没数据字典 - options:[], - #end - }, - #elseif($column.htmlType == "radio")## 单选框 - component: 'RadioButtonGroup', - componentProps: { - #if ("" != $dictType)## 有数据字典 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else##没数据字典 - options:[], - #end - }, - #elseif($column.htmlType == "datetime")## 时间框 - component: 'DatePicker', - componentProps: { - showTime: true, - format: 'YYYY-MM-DD HH:mm:ss', - valueFormat: 'x', - }, - #elseif($column.htmlType == "textarea")## 文本域 - component: 'InputTextArea', - #end - }, -#end -#end -#end -] - -export const updateFormSchema: FormSchema[] = [ - { - label: '编号', - field: 'id', - show: false, - component: 'Input', - }, -#foreach($column in $columns) -#if ($column.updateOperation) -#set ($dictType = $column.dictType) -#set ($javaType = $column.javaType) -#set ($javaField = $column.javaField) -#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) -#set ($comment = $column.columnComment) -#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") - #set ($dictMethod = "number") -#elseif ($javaType == "String") - #set ($dictMethod = "string") -#elseif ($javaType == "Boolean") - #set ($dictMethod = "boolean") -#end - #if (!$column.primaryKey)## 忽略主键,不用在表单里 - { - label: '${comment}', - field: '${javaField}', - #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 - required: true, - #end - #if ($column.htmlType == "input") - component: 'Input', - #elseif($column.htmlType == "imageUpload")## 图片上传 - component: 'FileUpload', - componentProps: { - fileType: 'image', - maxCount: 1, - }, - #elseif($column.htmlType == "fileUpload")## 文件上传 - component: 'FileUpload', - componentProps: { - fileType: 'file', - maxCount: 1, - }, - #elseif($column.htmlType == "editor")## 文本编辑器 - component: 'Editor', - #elseif($column.htmlType == "select")## 下拉框 - component: 'Select', - componentProps: { - #if ("" != $dictType)## 有数据字典 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else##没数据字典 - options:[], - #end - }, - #elseif($column.htmlType == "checkbox")## 多选框 - component: 'Checkbox', - componentProps: { - #if ("" != $dictType)## 有数据字典 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else##没数据字典 - options:[], - #end - }, - #elseif($column.htmlType == "radio")## 单选框 - component: 'RadioButtonGroup', - componentProps: { - #if ("" != $dictType)## 有数据字典 - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'), - #else##没数据字典 - options:[], - #end - }, - #elseif($column.htmlType == "datetime")## 时间框 - component: 'DatePicker', - componentProps: { - showTime: true, - format: 'YYYY-MM-DD HH:mm:ss', - valueFormat: 'x', - }, - #elseif($column.htmlType == "textarea")## 文本域 - component: 'InputTextArea', - #end - }, - #end -#end -#end -] \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/form.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/form.vue.vm deleted file mode 100644 index 1804365..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/form.vue.vm +++ /dev/null @@ -1,58 +0,0 @@ - - \ No newline at end of file diff --git a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/index.vue.vm b/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/index.vue.vm deleted file mode 100644 index 9e59b12..0000000 --- a/tashow-module/tashow-module-infra/src/main/resources/codegen/vue3_vben/views/index.vue.vm +++ /dev/null @@ -1,92 +0,0 @@ - - diff --git a/tashow-module/tashow-module-member/Dockerfile b/tashow-module/tashow-module-member/Dockerfile new file mode 100644 index 0000000..731a245 --- /dev/null +++ b/tashow-module/tashow-module-member/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 +## 感谢复旦核博士的建议!灰子哥,牛皮! +FROM eclipse-temurin:21-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /yudao-module-member-biz +WORKDIR /yudao-module-member-biz +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/yudao-module-member-biz.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48087 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/tashow-module/tashow-module-member/pom.xml b/tashow-module/tashow-module-member/pom.xml new file mode 100644 index 0000000..15d851d --- /dev/null +++ b/tashow-module/tashow-module-member/pom.xml @@ -0,0 +1,126 @@ + + + + com.tashow.cloud + tashow-module + ${revision} + + 4.0.0 + tashow-module-member + jar + + ${project.artifactId} + + member 模块,我们放会员业务。 + 例如说:会员中心等等 + + + + + + com.tashow.cloud + tashow-framework-env + + + com.tashow.cloud + tashow-framework-monitor + + + + com.tashow.cloud + tashow-member-api + ${revision} + + + com.tashow.cloud + tashow-system-api + ${revision} + + + com.tashow.cloud + tashow-infra-api + ${revision} + + + + + com.tashow.cloud + tashow-framework-tenant + + + + + com.tashow.cloud + tashow-framework-security + + + + org.springframework.boot + spring-boot-starter-validation + + + + + com.tashow.cloud + tashow-data-mybatis + + + + com.tashow.cloud + tashow-data-redis + + + + + com.tashow.cloud + tashow-framework-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-framework-mq + + + + + com.tashow.cloud + tashow-data-excel + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/MemberServerApplication.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/MemberServerApplication.java new file mode 100644 index 0000000..d9f285e --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/MemberServerApplication.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.member; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + */ +@SpringBootApplication +public class MemberServerApplication { + + public static void main(String[] args) { + SpringApplication.run(MemberServerApplication.class, args); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/address/MemberAddressApiImpl.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/address/MemberAddressApiImpl.java new file mode 100644 index 0000000..822c2a8 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/address/MemberAddressApiImpl.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.member.address; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.memberapi.api.address.MemberAddressApi; +import com.tashow.cloud.memberapi.api.address.dto.MemberAddressRespDTO; +import com.tashow.cloud.member.convert.address.AddressConvert; +import com.tashow.cloud.member.service.address.AddressService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +/** + * 用户收件地址 API 实现类 + */ +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class MemberAddressApiImpl implements MemberAddressApi { + + @Resource + private AddressService addressService; + + @Override + public CommonResult getAddress(Long id, Long userId) { + return success(AddressConvert.INSTANCE.convert02(addressService.getAddress(userId, id))); + } + + @Override + public CommonResult getDefaultAddress(Long userId) { + return success(AddressConvert.INSTANCE.convert02(addressService.getDefaultUserAddress(userId))); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/AddressController.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/AddressController.java new file mode 100644 index 0000000..dd69646 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/AddressController.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.member.controller.admin.address; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.member.controller.admin.address.vo.AddressRespVO; +import com.tashow.cloud.member.convert.address.AddressConvert; +import com.tashow.cloud.member.dal.dataobject.address.MemberAddressDO; +import com.tashow.cloud.member.service.address.AddressService; +import jakarta.annotation.Resource; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 管理后台 - 用户收件地址 +@RestController +@RequestMapping("/member/address") +@Validated +public class AddressController { + + @Resource + private AddressService addressService; + + @GetMapping("/list") + // 获得用户收件地址列表 + // userId: 用户编号,必填 + @PreAuthorize("@ss.hasPermission('member:user:query')") + public CommonResult> getAddressList(@RequestParam("userId") Long userId) { + List list = addressService.getAddressList(userId); + return success(AddressConvert.INSTANCE.convertList2(list)); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/package-info.java new file mode 100644 index 0000000..f798caf --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.member.controller.admin.address; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/vo/AddressBaseVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/vo/AddressBaseVO.java new file mode 100644 index 0000000..ed5e03e --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/vo/AddressBaseVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.member.controller.admin.address.vo; + +import lombok.*; + +import java.time.LocalDateTime; +import java.util.*; +import jakarta.validation.constraints.*; + +/** + * 用户收件地址 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class AddressBaseVO { + + // 收件人名称,必填,示例:张三 + @NotNull(message = "收件人名称不能为空") + private String name; + + // 手机号,必填 + @NotNull(message = "手机号不能为空") + private String mobile; + + // 地区编码,必填,示例:15716 + @NotNull(message = "地区编码不能为空") + private Long areaId; + + // 收件详细地址,必填 + @NotNull(message = "收件详细地址不能为空") + private String detailAddress; + + // 是否默认,必填,示例:2 + @NotNull(message = "是否默认不能为空") + private Boolean defaultStatus; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/vo/AddressRespVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/vo/AddressRespVO.java new file mode 100644 index 0000000..8966264 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/address/vo/AddressRespVO.java @@ -0,0 +1,18 @@ +package com.tashow.cloud.member.controller.admin.address.vo; + +import lombok.*; +import java.time.LocalDateTime; + +// 管理后台 - 用户收件地址 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AddressRespVO extends AddressBaseVO { + + // 收件地址编号,必填,示例:7380 + private Long id; + + // 创建时间,必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/MemberUserController.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/MemberUserController.java new file mode 100644 index 0000000..77e0785 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/MemberUserController.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.member.controller.admin.user; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserPageReqVO; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserRespVO; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserUpdateReqVO; +import com.tashow.cloud.member.convert.user.MemberUserConvert; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import com.tashow.cloud.member.service.user.MemberUserService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 管理后台 - 会员用户 +@RestController +@RequestMapping("/member/user") +@Validated +public class MemberUserController { + + @Resource + private MemberUserService memberUserService; + @Resource + + @PutMapping("/update") + // 更新会员用户 + @PreAuthorize("@ss.hasPermission('member:user:update')") + public CommonResult updateUser(@Valid @RequestBody MemberUserUpdateReqVO updateReqVO) { + memberUserService.updateUser(updateReqVO); + return success(true); + } + + + @GetMapping("/get") + // 获得会员用户 + // id: 编号,必填,示例:1024 + @PreAuthorize("@ss.hasPermission('member:user:query')") + public CommonResult getUser(@RequestParam("id") Long id) { + MemberUserDO user = memberUserService.getUser(id); + return success(MemberUserConvert.INSTANCE.convert03(user)); + } + + @GetMapping("/page") + // 获得会员用户分页 + @PreAuthorize("@ss.hasPermission('member:user:query')") + public CommonResult> getUserPage(@Valid MemberUserPageReqVO pageVO) { + PageResult pageResult = memberUserService.getUserPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 处理用户标签返显 + Set tagIds = pageResult.getList().stream() + .map(MemberUserDO::getTagIds) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + return success(MemberUserConvert.INSTANCE.convertPage(pageResult)); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserBaseVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserBaseVO.java new file mode 100644 index 0000000..cacdcf5 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserBaseVO.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.member.controller.admin.user.vo; + +import lombok.Data; +import org.hibernate.validator.constraints.URL; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; + + +/** + * 会员用户 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class MemberUserBaseVO { + + // 手机号,必填,示例:15601691300 + @NotNull(message = "手机号不能为空") + private String mobile; + + // 状态,必填,示例:2 + @NotNull(message = "状态不能为空") + private Byte status; + + // 用户昵称,必填,示例:李四 + @NotNull(message = "用户昵称不能为空") + private String nickname; + + // 头像,必填,示例:https://www.iocoder.cn/x.png + @URL(message = "头像必须是 URL 格式") + private String avatar; + + // 用户昵称,示例:李四 + private String name; + + // 用户性别,示例:1 + private Integer sex; + + // 所在地编号,示例:4371 + private Long areaId; + + // 所在地全程,示例:上海上海市普陀区 + private String areaName; + + // 出生日期,示例:2023-03-12 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDateTime birthday; + + // 会员备注,示例:我是小备注 + private String mark; + + // 会员标签,示例:[1, 2] + private List tagIds; + + // 会员等级编号,示例:1 + private Long levelId; + + // 用户分组编号,示例:1 + private Long groupId; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserPageReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserPageReqVO.java new file mode 100644 index 0000000..d4ced7e --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserPageReqVO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.member.controller.admin.user.vo; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 会员用户分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MemberUserPageReqVO extends PageParam { + + // 手机号,示例:15601691300 + private String mobile; + + // 用户昵称,示例:李四 + private String nickname; + + // 最后登录时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] loginDate; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + // 会员标签编号列表,示例:[1, 2] + private List tagIds; + + // 会员等级编号,示例:1 + private Long levelId; + + // 用户分组编号,示例:1 + private Long groupId; + + // TODO 芋艿:注册用户类型; + + // TODO 芋艿:登录用户类型; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserRespVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserRespVO.java new file mode 100644 index 0000000..770211e --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserRespVO.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.member.controller.admin.user.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +// 管理后台 - 会员用户 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MemberUserRespVO extends MemberUserBaseVO { + + // 编号,必填,示例:23788 + private Long id; + + // 注册 IP,必填,示例:127.0.0.1 + private String registerIp; + + // 最后登录IP,必填,示例:127.0.0.1 + private String loginIp; + + // 最后登录时间,必填 + private LocalDateTime loginDate; + + // 创建时间,必填 + private LocalDateTime createTime; + + // ========== 其它信息 ========== + + // 积分,必填,示例:100 + private Integer point; + + // 总积分,必填,示例:2000 + private Integer totalPoint; + + // 会员标签,示例:[红色, 快乐] + private List tagNames; + + // 会员等级,示例:黄金会员 + private String levelName; + + // 用户分组,示例:购物达人 + private String groupName; + + // 用户经验值,必填,示例:200 + private Integer experience; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java new file mode 100644 index 0000000..73c79e2 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.member.controller.admin.user.vo; + +import lombok.Data; +import lombok.ToString; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +// 管理后台 - 用户修改等级 Request VO +@Data +@ToString(callSuper = true) +public class MemberUserUpdateLevelReqVO { + + // 用户编号,必填,示例:23788 + @NotNull(message = "用户编号不能为空") + private Long id; + + /** + * 取消用户等级时,值为空 + */ + // 用户等级编号,示例:1 + private Long levelId; + + // 修改原因,必填,示例:推广需要 + @NotBlank(message = "修改原因不能为空") + private String reason; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java new file mode 100644 index 0000000..bb04c78 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.member.controller.admin.user.vo; + +import lombok.Data; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +// 管理后台 - 用户修改积分 Request VO +@Data +@ToString(callSuper = true) +public class MemberUserUpdatePointReqVO { + + // 用户编号,必填,示例:23788 + @NotNull(message = "用户编号不能为空") + private Long id; + + // 变动积分,正数为增加,负数为减少,必填,示例:100 + @NotNull(message = "变动积分不能为空") + private Integer point; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdateReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdateReqVO.java new file mode 100644 index 0000000..c48ceff --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/admin/user/vo/MemberUserUpdateReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.member.controller.admin.user.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import jakarta.validation.constraints.NotNull; + +// 管理后台 - 会员用户更新 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MemberUserUpdateReqVO extends MemberUserBaseVO { + + // 编号,必填,示例:23788 + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/AppAddressController.http b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/AppAddressController.http new file mode 100644 index 0000000..a0582e6 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/AppAddressController.http @@ -0,0 +1,54 @@ +### 请求 /create 接口 => 成功 +POST {{appApi}}//member/address/create +Content-Type: application/json +tenant-id: {{appTenantId}} +Authorization: Bearer {{appToken}} + +{ + "name": "yunai", + "mobile": "15601691300", + "areaId": "610632", + "postCode": "200000", + "detailAddress": "芋道源码 233 号 666 室", + "defaulted": true +} + +### 请求 /update 接口 => 成功 +PUT {{appApi}}//member/address/update +Content-Type: application/json +tenant-id: {{appTenantId}} +Authorization: Bearer {{appToken}} + +{ + "id": "1", + "name": "yunai888", + "mobile": "15601691300", + "areaId": "610632", + "postCode": "200000", + "detailAddress": "芋道源码 233 号 666 室", + "defaulted": false +} + +### 请求 /delete 接口 => 成功 +DELETE {{appApi}}//member/address/delete?id=2 +Content-Type: application/json +tenant-id: {{appTenantId}} +Authorization: Bearer {{appToken}} + +### 请求 /get 接口 => 成功 +GET {{appApi}}//member/address/get?id=1 +Content-Type: application/json +tenant-id: {{appTenantId}} +Authorization: Bearer {{appToken}} + +### 请求 /get-default 接口 => 成功 +GET {{appApi}}//member/address/get-default +Content-Type: application/json +tenant-id: {{appTenantId}} +Authorization: Bearer {{appToken}} + +### 请求 /list 接口 => 成功 +GET {{appApi}}//member/address/list +Content-Type: application/json +tenant-id: {{appTenantId}} +Authorization: Bearer {{appToken}} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/AppAddressController.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/AppAddressController.java new file mode 100644 index 0000000..798963b --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/AppAddressController.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.member.controller.app.address; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressCreateReqVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressRespVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressUpdateReqVO; +import com.tashow.cloud.member.convert.address.AddressConvert; +import com.tashow.cloud.member.dal.dataobject.address.MemberAddressDO; +import com.tashow.cloud.member.service.address.AddressService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +/** + * 用户 APP - 用户收件地址 + */ +@RestController +@RequestMapping("/member/address") +@Validated +public class AppAddressController { + + @Resource + private AddressService addressService; + + /** + * 创建用户收件地址 + * @param createReqVO + * @return + */ + @PostMapping("/create") + public CommonResult createAddress(@Valid @RequestBody AppAddressCreateReqVO createReqVO) { + return success(addressService.createAddress(getLoginUserId(), createReqVO)); + } + + /** + * 更新用户收件地址 + * @param updateReqVO + * @return + */ + @PutMapping("/update") + public CommonResult updateAddress(@Valid @RequestBody AppAddressUpdateReqVO updateReqVO) { + addressService.updateAddress(getLoginUserId(), updateReqVO); + return success(true); + } + + /** + * 删除用户收件地址 + * @param id 编号 + * @return + */ + @DeleteMapping("/delete") + public CommonResult deleteAddress(@RequestParam("id") Long id) { + addressService.deleteAddress(getLoginUserId(), id); + return success(true); + } + + /** + * 获得用户收件地址 + * @param id 编号 + * @return + */ + @GetMapping("/get") + public CommonResult getAddress(@RequestParam("id") Long id) { + MemberAddressDO address = addressService.getAddress(getLoginUserId(), id); + return success(AddressConvert.INSTANCE.convert(address)); + } + + /** + * 获得默认的用户收件地址 + * @return + */ + @GetMapping("/get-default") + public CommonResult getDefaultUserAddress() { + MemberAddressDO address = addressService.getDefaultUserAddress(getLoginUserId()); + return success(AddressConvert.INSTANCE.convert(address)); + } + + /** + * 获得用户收件地址列表 + * @return + */ + @GetMapping("/list") + public CommonResult> getAddressList() { + List list = addressService.getAddressList(getLoginUserId()); + return success(AddressConvert.INSTANCE.convertList(list)); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressBaseVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressBaseVO.java new file mode 100644 index 0000000..54b1a80 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressBaseVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.member.controller.app.address.vo; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** +* 用户收件地址 Base VO,提供给添加、修改、详细的子 VO 使用 +*/ +@Data +public class AppAddressBaseVO { + + //收件人名称 + @NotNull(message = "收件人名称不能为空") + private String name; + + //手机号 + @NotNull(message = "手机号不能为空") + private String mobile; + + //地区编号 + @NotNull(message = "地区编号不能为空") + private Long areaId; + + //收件详细地址 + @NotNull(message = "收件详细地址不能为空") + private String detailAddress; + + //是否默认地址 + @NotNull(message = "是否默认地址不能为空") + private Boolean defaultStatus; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressCreateReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressCreateReqVO.java new file mode 100644 index 0000000..e72746a --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressCreateReqVO.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.member.controller.app.address.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 用户 APP - 用户收件地址创建 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppAddressCreateReqVO extends AppAddressBaseVO { + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressRespVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressRespVO.java new file mode 100644 index 0000000..af9b187 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressRespVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.member.controller.app.address.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 用户 APP - 用户收件地址 Response VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppAddressRespVO extends AppAddressBaseVO { + + //编号 + private Long id; + + //地区名字 + private String areaName; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressUpdateReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressUpdateReqVO.java new file mode 100644 index 0000000..07d118e --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/address/vo/AppAddressUpdateReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.member.controller.app.address.vo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 用户 APP - 用户收件地址更新 Request VO + */ +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppAddressUpdateReqVO extends AppAddressBaseVO { + + //编号 + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/AppAuthController.http b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/AppAuthController.http new file mode 100644 index 0000000..391aa92 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/AppAuthController.http @@ -0,0 +1,67 @@ +### 请求 /login 接口 => 成功 +POST {{appApi}}/member/auth/login +Content-Type: application/json +tenant-id: {{appTenantId}} + +{ + "mobile": "15601691388", + "password": "admin123" +} + +### 请求 /send-sms-code 接口 => 成功 +POST {{appApi}}/member/auth/send-sms-code +Content-Type: application/json +tenant-id: {{appTenantId}} + +{ + "mobile": "15601691388", + "scene": 1 +} + +### 请求 /sms-login 接口 => 成功 +POST {{appApi}}/member/auth/sms-login +Content-Type: application/json +tenant-id: {{appTenantId}} +terminal: 30 + +{ + "mobile": "15601691388", + "code": 9999 +} + +### 请求 /social-login 接口 => 成功 +POST {{appApi}}/member/auth/social-login +Content-Type: application/json +tenant-id: {{appTenantId}} + +{ + "type": 34, + "code": "0e1oc9000CTjFQ1oim200bhtb61oc90g", + "state": "default" +} + +### 请求 /weixin-mini-app-login 接口 => 成功 +POST {{appApi}}/member/auth/weixin-mini-app-login +Content-Type: application/json +tenant-id: {{appTenantId}} + +{ + "phoneCode": "618e6412e0c728f5b8fc7164497463d0158a923c9e7fd86af8bba393b9decbc5", + "loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR" +} + +### 请求 /logout 接口 => 成功 +POST {{appApi}}/member/auth/logout +Content-Type: application/json +Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66 +tenant-id: {{appTenantId}} + +### 请求 /auth/refresh-token 接口 => 成功 +POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70 +Content-Type: application/json +tenant-id: {{appTenantId}} + +### 请求 /auth/create-weixin-jsapi-signature 接口 => 成功 +POST {{appApi}}/member/auth/create-weixin-jsapi-signature?url=http://www.iocoder.cn +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/AppAuthController.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/AppAuthController.java new file mode 100644 index 0000000..cf9702a --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/AppAuthController.java @@ -0,0 +1,169 @@ +package com.tashow.cloud.member.controller.app.auth; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.member.controller.app.auth.vo.*; +import com.tashow.cloud.member.convert.auth.AuthConvert; +import com.tashow.cloud.member.service.auth.MemberAuthService; +import com.tashow.cloud.security.security.config.SecurityProperties; +import com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxJsapiSignatureRespDTO; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +/** + * 用户 APP - 认证 + */ +@RestController +@RequestMapping("/member/auth") +@Validated +@Slf4j +public class AppAuthController { + + @Resource + private MemberAuthService authService; + + @Resource + private SocialClientApi socialClientApi; + + @Resource + private SecurityProperties securityProperties; + + /** + * 使用手机 + 密码登录 + * @param reqVO + * @return + */ + @PostMapping("/login") + @PermitAll + public CommonResult login(@RequestBody @Valid AppAuthLoginReqVO reqVO) { + return success(authService.login(reqVO)); + } + + /** + * 登出系统 + * @param request + * @return + */ + @PostMapping("/logout") + @PermitAll + public CommonResult logout(HttpServletRequest request) { + String token = SecurityFrameworkUtils.obtainAuthorization(request, + securityProperties.getTokenHeader(), securityProperties.getTokenParameter()); + if (StrUtil.isNotBlank(token)) { + authService.logout(token); + } + return success(true); + } + + /** + * 刷新令牌 + * @param refreshToken 刷新令牌 + * @return + */ + @PostMapping("/refresh-token") + @PermitAll + public CommonResult refreshToken(@RequestParam("refreshToken") String refreshToken) { + return success(authService.refreshToken(refreshToken)); + } + + // ========== 短信登录相关 ========== + + /** + * 使用手机 + 验证码登录 + * @param reqVO + * @return + */ + @PostMapping("/sms-login") + @PermitAll + public CommonResult smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) { + return success(authService.smsLogin(reqVO)); + } + + + /** + * 发送手机验证码 + * @param reqVO + * @return + */ + @PostMapping("/send-sms-code") + @PermitAll + public CommonResult sendSmsCode(@RequestBody @Valid AppAuthSmsSendReqVO reqVO) { + authService.sendSmsCode(getLoginUserId(), reqVO); + return success(true); + } + + /** + * 校验手机验证码 + * @param reqVO + * @return + */ + @PostMapping("/validate-sms-code") + @PermitAll + public CommonResult validateSmsCode(@RequestBody @Valid AppAuthSmsValidateReqVO reqVO) { + authService.validateSmsCode(getLoginUserId(), reqVO); + return success(true); + } + + // ========== 社交登录相关 ========== + + /** + * 社交授权的跳转 + * @param type 社交类型 + * @param redirectUri 回调路径 + * @return + */ + @GetMapping("/social-auth-redirect") + @PermitAll + public CommonResult socialAuthRedirect(@RequestParam("type") Integer type, + @RequestParam("redirectUri") String redirectUri) { + return success(authService.getSocialAuthorizeUrl(type, redirectUri)); + } + + /** + * 社交快捷登录,使用 code 授权码 + * @param reqVO + * @return + */ + @PostMapping("/social-login") + @PermitAll + public CommonResult socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) { + return success(authService.socialLogin(reqVO)); + } + + /** + * 微信小程序的一键登录 + * @param reqVO + * @return + */ + @PostMapping("/weixin-mini-app-login") + @PermitAll + public CommonResult weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) { + return success(authService.weixinMiniAppLogin(reqVO)); + } + + /** + * 创建微信 JS SDK 初始化所需的签名 + * @param url + * @return + */ + @PostMapping("/create-weixin-jsapi-signature") + @PermitAll + public CommonResult createWeixinMpJsapiSignature(@RequestParam("url") String url) { + SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature( + UserTypeEnum.MEMBER.getValue(), url).getCheckedData(); + return success(AuthConvert.INSTANCE.convert(signature)); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java new file mode 100644 index 0000000..8ee979f --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +/** + * 用户 APP - 校验验证码 Request VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthCheckCodeReqVO { + + //手机号 + @NotBlank(message = "手机号不能为空") + @Mobile + private String mobile; + + //手机验证码 + @NotBlank(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + + //发送场景,对应 SmsSceneEnum 枚举 + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + private Integer scene; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthLoginReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthLoginReqVO.java new file mode 100644 index 0000000..0930d21 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthLoginReqVO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +/** + * 用户 APP - 手机 + 密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthLoginReqVO { + + //手机号 + @NotEmpty(message = "手机号不能为空") + @Mobile + private String mobile; + + //密码 + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + // ========== 绑定社交登录时,需要传递如下参数 ========== + + //社交平台的类型,参见 SocialTypeEnum 枚举值 + @InEnum(SocialTypeEnum.class) + private Integer socialType; + + //授权码 + private String socialCode; + + //state + private String socialState; + + @AssertTrue(message = "授权码不能为空") + public boolean isSocialCodeValid() { + return socialType == null || StrUtil.isNotEmpty(socialCode); + } + + @AssertTrue(message = "授权 state 不能为空") + public boolean isSocialState() { + return socialType == null || StrUtil.isNotEmpty(socialState); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthLoginRespVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthLoginRespVO.java new file mode 100644 index 0000000..2757c01 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthLoginRespVO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 用户 APP - 登录 Response VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthLoginRespVO { + + //用户编号 + private Long userId; + + //访问令牌 + private String accessToken; + + //刷新令牌 + private String refreshToken; + + //过期时间 + private LocalDateTime expiresTime; + + /** + * 社交用户 + * 仅社交登录、社交绑定时会返回 + * 为什么需要返回?微信公众号、微信小程序支付需要传递 openid 给支付接口 + */ + private String openid; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java new file mode 100644 index 0000000..779e325 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java @@ -0,0 +1,58 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +/** + * 用户 APP - 手机 + 验证码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthSmsLoginReqVO { + + //手机号 + @NotEmpty(message = "手机号不能为空") + @Mobile + private String mobile; + + //手机验证码 + @NotEmpty(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + + // ========== 绑定社交登录时,需要传递如下参数 ========== + + //社交平台的类型,参见 SocialTypeEnum 枚举值 + @InEnum(SocialTypeEnum.class) + private Integer socialType; + + //授权码 + private String socialCode; + + //state + private String socialState; + + @AssertTrue(message = "授权码不能为空") + public boolean isSocialCodeValid() { + return socialType == null || StrUtil.isNotEmpty(socialCode); + } + + @AssertTrue(message = "授权 state 不能为空") + public boolean isSocialState() { + return socialType == null || StrUtil.isNotEmpty(socialState); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java new file mode 100644 index 0000000..b7a5994 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * 用户 APP - 发送手机验证码 Request VO + */ +@Data +@Accessors(chain = true) +public class AppAuthSmsSendReqVO { + + //手机号 + @Mobile + private String mobile; + + //发送场景,对应 SmsSceneEnum 枚举 + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + private Integer scene; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsValidateReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsValidateReqVO.java new file mode 100644 index 0000000..20680d5 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSmsValidateReqVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.Length; + +//用户 APP - 校验手机验证码 Request VO +@Data +@Accessors(chain = true) +public class AppAuthSmsValidateReqVO { + + //手机号 + @Mobile + private String mobile; + + //发送场景,对应 SmsSceneEnum 枚举 + @NotNull(message = "发送场景不能为空") + @InEnum(SmsSceneEnum.class) + private Integer scene; + + //手机验证码 + @NotEmpty(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java new file mode 100644 index 0000000..4fb3b04 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户 APP - 社交快捷登录 Request VO,使用 code 授权码 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthSocialLoginReqVO { + + //社交平台的类型,参见 SocialTypeEnum 枚举值 + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer type; + + //授权码 + @NotEmpty(message = "授权码不能为空") + private String code; + + //state + @NotEmpty(message = "state 不能为空") + private String state; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java new file mode 100644 index 0000000..8d9630b --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import jakarta.validation.constraints.NotEmpty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户 APP - 微信小程序手机登录 Request VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppAuthWeixinMiniAppLoginReqVO { + + /** + * 手机 code,小程序通过 wx.getPhoneNumber 方法获得 + */ + @NotEmpty(message = "手机 code 不能为空") + private String phoneCode; + + /** + * 登录 code,小程序通过 wx.login 方法获得 + */ + @NotEmpty(message = "登录 code 不能为空") + private String loginCode; + + /** + * state + */ + @NotEmpty(message = "state 不能为空") + private String state; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java new file mode 100644 index 0000000..9902e71 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.member.controller.app.auth.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户 APP - 微信公众号 JSAPI 签名 Response VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AuthWeixinJsapiSignatureRespVO { + + //微信公众号的 appId + private String appId; + + //匿名串 + private String nonceStr; + + //时间戳 + private Long timestamp; + + //URL + private String url; + + //签名 + private String signature; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/AppMemberUserController.http b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/AppMemberUserController.http new file mode 100644 index 0000000..8ffb70c --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/AppMemberUserController.http @@ -0,0 +1,4 @@ +### 请求 /member/user/profile/get 接口 => 没有权限 +GET {{appApi}}/member/user/get +Authorization: Bearer test245 +tenant-id: {{appTenantId}} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/AppMemberUserController.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/AppMemberUserController.java new file mode 100644 index 0000000..6db042a --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/AppMemberUserController.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.member.controller.app.user; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.member.controller.app.user.vo.*; +import com.tashow.cloud.member.convert.user.MemberUserConvert; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import com.tashow.cloud.member.service.user.MemberUserService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +// 用户 APP - 用户个人中心 +@RestController +@RequestMapping("/member/user") +@Validated +@Slf4j +public class AppMemberUserController { + + @Resource + private MemberUserService userService; + + @GetMapping("/get") + // 获得基本信息 + public CommonResult getUserInfo() { + MemberUserDO user = userService.getUser(getLoginUserId()); + return success(MemberUserConvert.INSTANCE.convert(user)); + } + + @PutMapping("/update") + // 修改基本信息 + public CommonResult updateUser(@RequestBody @Valid AppMemberUserUpdateReqVO reqVO) { + userService.updateUser(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/update-mobile") + // 修改用户手机 + public CommonResult updateUserMobile(@RequestBody @Valid AppMemberUserUpdateMobileReqVO reqVO) { + userService.updateUserMobile(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/update-mobile-by-weixin") + // 基于微信小程序的授权码,修改用户手机 + public CommonResult updateUserMobileByWeixin(@RequestBody @Valid AppMemberUserUpdateMobileByWeixinReqVO reqVO) { + userService.updateUserMobileByWeixin(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/update-password") + // 修改用户密码 + // 用户修改密码时使用 + public CommonResult updateUserPassword(@RequestBody @Valid AppMemberUserUpdatePasswordReqVO reqVO) { + userService.updateUserPassword(getLoginUserId(), reqVO); + return success(true); + } + + @PutMapping("/reset-password") + // 重置密码 + // 用户忘记密码时使用 + @PermitAll + public CommonResult resetUserPassword(@RequestBody @Valid AppMemberUserResetPasswordReqVO reqVO) { + userService.resetUserPassword(reqVO); + return success(true); + } + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserInfoRespVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserInfoRespVO.java new file mode 100644 index 0000000..b51609c --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserInfoRespVO.java @@ -0,0 +1,51 @@ +package com.tashow.cloud.member.controller.app.user.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户 APP - 用户个人信息 Response VO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppMemberUserInfoRespVO { + + //用户编号 + private Long id; + + //用户昵称 + private String nickname; + //用户头像 + private String avatar; + //用户手机号 + private String mobile; + //用户性别 + private Integer sex; + //积分 + private Integer point; + //经验值 + private Integer experience; + /// 用户等级 + private Level level; + //是否成为推广员 + private Boolean brokerageEnabled; + + /** + * 用户 App - 会员等级 + */ + @Data + public static class Level { + //等级编号 + private Long id; + //等级名称 + private String name; + // 等级 + private Integer level; + //等级图标 + private String icon; + + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java new file mode 100644 index 0000000..57ef94d --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.member.controller.app.user.vo; + + +import com.tashow.cloud.common.validation.Mobile; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +// 用户 APP - 重置密码 Request VO +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppMemberUserResetPasswordReqVO { + + // 新密码, 必需, 示例: buzhidao + @NotEmpty(message = "新密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + // 手机验证码, 必需, 示例: 1024 + @NotEmpty(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + + // 手机号, 必需, 示例: 15878962356 + @NotBlank(message = "手机号不能为空") + @Mobile + private String mobile; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateMobileByWeixinReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateMobileByWeixinReqVO.java new file mode 100644 index 0000000..ee8a40b --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateMobileByWeixinReqVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.member.controller.app.user.vo; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +// 用户 APP - 基于微信小程序的授权码,修改手机 Request VO +@Data +public class AppMemberUserUpdateMobileByWeixinReqVO { + + // 手机 code,小程序通过 wx.getPhoneNumber 方法获得, 必需, 示例: hello + @NotEmpty(message = "手机 code 不能为空") + private String code; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateMobileReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateMobileReqVO.java new file mode 100644 index 0000000..209bd29 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateMobileReqVO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.member.controller.app.user.vo; + +import com.tashow.cloud.common.validation.Mobile; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +// 用户 APP - 修改手机 Request VO +@Data +public class AppMemberUserUpdateMobileReqVO { + + // 手机验证码, 必需, 示例: 1024 + @NotEmpty(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + + // 手机号, 必需, 示例: 15823654487 + @NotBlank(message = "手机号不能为空") + @Length(min = 8, max = 11, message = "手机号码长度为 8-11 位") + @Mobile + private String mobile; + + // 原手机验证码, 示例: 1024 + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String oldCode; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java new file mode 100644 index 0000000..a813f8b --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.member.controller.app.user.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.validator.constraints.Length; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; + +// 用户 APP - 修改密码 Request VO +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AppMemberUserUpdatePasswordReqVO { + + // 新密码, 必需, 示例: buzhidao + @NotEmpty(message = "新密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + // 手机验证码, 必需, 示例: 1024 + @NotEmpty(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateReqVO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateReqVO.java new file mode 100644 index 0000000..9e00fad --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/app/user/vo/AppMemberUserUpdateReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.member.controller.app.user.vo; + +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +// 用户 App - 会员用户更新 Request VO +@Data +public class AppMemberUserUpdateReqVO { + + // 用户昵称, 必需, 示例: 李四 + private String nickname; + + // 头像, 必需, 示例: https://www.iocoder.cn/x.png + @URL(message = "头像必须是 URL 格式") + private String avatar; + + // 性别, 必需, 示例: 1 + private Integer sex; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/package-info.java new file mode 100644 index 0000000..f11a3af --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package com.tashow.cloud.member.controller; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/address/AddressConvert.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/address/AddressConvert.java new file mode 100644 index 0000000..23132df --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/address/AddressConvert.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.member.convert.address; + +import com.tashow.cloud.common.util.ip.AreaUtils; +import com.tashow.cloud.memberapi.api.address.dto.MemberAddressRespDTO; +import com.tashow.cloud.member.controller.admin.address.vo.AddressRespVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressCreateReqVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressRespVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressUpdateReqVO; +import com.tashow.cloud.member.dal.dataobject.address.MemberAddressDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 用户收件地址 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface AddressConvert { + + AddressConvert INSTANCE = Mappers.getMapper(AddressConvert.class); + + MemberAddressDO convert(AppAddressCreateReqVO bean); + + MemberAddressDO convert(AppAddressUpdateReqVO bean); + + @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName") + AppAddressRespVO convert(MemberAddressDO bean); + + List convertList(List list); + + MemberAddressRespDTO convert02(MemberAddressDO bean); + + @Named("convertAreaIdToAreaName") + default String convertAreaIdToAreaName(Integer areaId) { + return AreaUtils.format(areaId); + } + + List convertList2(List list); + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/auth/AuthConvert.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/auth/AuthConvert.java new file mode 100644 index 0000000..19f4972 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/auth/AuthConvert.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.member.convert.auth; + +import com.tashow.cloud.member.controller.app.auth.vo.*; +import com.tashow.cloud.member.controller.app.user.vo.AppMemberUserResetPasswordReqVO; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenRespDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeSendReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeValidateReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserUnbindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxJsapiSignatureRespDTO; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AuthConvert { + + AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class); + + SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO); + SocialUserUnbindReqDTO convert(Long userId, Integer userType); + + SmsCodeSendReqDTO convert(AppAuthSmsSendReqVO reqVO); + SmsCodeUseReqDTO convert(AppMemberUserResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp); + SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp); + + AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean, String openid); + + SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean); + + SocialWxJsapiSignatureRespDTO convert(SocialWxJsapiSignatureRespDTO bean); + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/package-info.java new file mode 100644 index 0000000..6450242 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package com.tashow.cloud.member.convert; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/user/MemberUserConvert.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/user/MemberUserConvert.java new file mode 100644 index 0000000..7929412 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/user/MemberUserConvert.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.member.convert.user; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.memberapi.api.user.dto.MemberUserRespDTO; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserRespVO; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserUpdateReqVO; +import com.tashow.cloud.member.controller.app.user.vo.AppMemberUserInfoRespVO; +import com.tashow.cloud.member.convert.address.AddressConvert; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.util.List; + + +@Mapper(uses = {AddressConvert.class}) +public interface MemberUserConvert { + + MemberUserConvert INSTANCE = Mappers.getMapper(MemberUserConvert.class); + + AppMemberUserInfoRespVO convert(MemberUserDO bean); + + + @Mappings({ + @Mapping(source = "bean.id", target = "id"), + }) + + MemberUserRespDTO convert2(MemberUserDO bean); + + List convertList2(List list); + + MemberUserDO convert(MemberUserUpdateReqVO bean); + + PageResult convertPage(PageResult page); + + @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName") + MemberUserRespVO convert03(MemberUserDO bean); + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md new file mode 100644 index 0000000..8153487 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/dataobject/address/MemberAddressDO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/dataobject/address/MemberAddressDO.java new file mode 100644 index 0000000..7529b2f --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/dataobject/address/MemberAddressDO.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.member.dal.dataobject.address; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +/** + * 用户收件地址 DO + * + */ +@TableName("member_address") +@KeySequence("member_address_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MemberAddressDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + */ + private Long userId; + /** + * 收件人名称 + */ + private String name; + /** + * 手机号 + */ + private String mobile; + /** + * 地区编号 + */ + private Long areaId; + /** + * 收件详细地址 + */ + private String detailAddress; + /** + * 是否默认 + * + * true - 默认收件地址 + */ + private Boolean defaultStatus; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/dataobject/user/MemberUserDO.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/dataobject/user/MemberUserDO.java new file mode 100644 index 0000000..c697015 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/dataobject/user/MemberUserDO.java @@ -0,0 +1,139 @@ +package com.tashow.cloud.member.dal.dataobject.user; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.TerminalEnum; +import com.tashow.cloud.mybatis.mybatis.core.type.LongListTypeHandler; +import com.tashow.cloud.systemapi.enums.common.SexEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import lombok.*; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 会员用户 DO + * + * uk_mobile 索引:基于 {@link #mobile} 字段 + * + */ +@TableName(value = "member_user", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MemberUserDO extends TenantBaseDO { + + // ========== 账号信息 ========== + + /** + * 用户ID + */ + @TableId + private Long id; + /** + * 手机 + */ + private String mobile; + /** + * 加密后的密码 + * + * 因为目前使用 {@link BCryptPasswordEncoder} 加密器,所以无需自己处理 salt 盐 + */ + private String password; + /** + * 帐号状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 注册 IP + */ + private String registerIp; + /** + * 注册终端 + * 枚举 {@link TerminalEnum} + */ + private Integer registerTerminal; + /** + * 最后登录IP + */ + private String loginIp; + /** + * 最后登录时间 + */ + private LocalDateTime loginDate; + + // ========== 基础信息 ========== + + /** + * 用户昵称 + */ + private String nickname; + /** + * 用户头像 + */ + private String avatar; + + /** + * 真实名字 + */ + private String name; + /** + * 性别 + * + * 枚举 {@link SexEnum} + */ + private Integer sex; + /** + * 出生日期 + */ + private LocalDateTime birthday; + /** + * 所在地 + * + * 关联 {@link Area#getId()} 字段 + */ + private Integer areaId; + /** + * 用户备注 + */ + private String mark; + + // ========== 其它信息 ========== + + /** + * 积分 + */ + private Integer point; + // TODO 疯狂:增加一个 totalPoint;个人信息接口要返回 + + /** + * 会员标签列表,以逗号分隔 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List tagIds; + + /** + * 会员级别编号 + * + * 关联 {@link MemberLevelDO#getId()} 字段 + */ + private Long levelId; + /** + * 会员经验 + */ + private Integer experience; + /** + * 用户分组编号 + * + * 关联 {@link MemberGroupDO#getId()} 字段 + */ + private Long groupId; + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/mysql/address/MemberAddressMapper.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/mysql/address/MemberAddressMapper.java new file mode 100644 index 0000000..fb6910e --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/mysql/address/MemberAddressMapper.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.member.dal.mysql.address; + +import com.tashow.cloud.member.dal.dataobject.address.MemberAddressDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MemberAddressMapper extends BaseMapperX { + + default MemberAddressDO selectByIdAndUserId(Long id, Long userId) { + return selectOne(MemberAddressDO::getId, id, MemberAddressDO::getUserId, userId); + } + + default List selectListByUserIdAndDefaulted(Long userId, Boolean defaulted) { + return selectList(new LambdaQueryWrapperX().eq(MemberAddressDO::getUserId, userId) + .eqIfPresent(MemberAddressDO::getDefaultStatus, defaulted)); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/mysql/user/MemberUserMapper.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/mysql/user/MemberUserMapper.java new file mode 100644 index 0000000..d3de5ae --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/mysql/user/MemberUserMapper.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.member.dal.mysql.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserPageReqVO; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 会员 User Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface MemberUserMapper extends BaseMapperX { + + default MemberUserDO selectByMobile(String mobile) { + return selectOne(MemberUserDO::getMobile, mobile); + } + + default List selectListByNicknameLike(String nickname) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(MemberUserDO::getNickname, nickname)); + } + + default PageResult selectPage(MemberUserPageReqVO reqVO) { + // 处理 tagIds 过滤条件 + String tagIdSql = ""; + if (CollUtil.isNotEmpty(reqVO.getTagIds())) { + tagIdSql = reqVO.getTagIds().stream() + .map(tagId -> "FIND_IN_SET(" + tagId + ", tag_ids)") + .collect(Collectors.joining(" OR ")); + } + // 分页查询 + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(MemberUserDO::getMobile, reqVO.getMobile()) + .betweenIfPresent(MemberUserDO::getLoginDate, reqVO.getLoginDate()) + .likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname()) + .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime()) + .eqIfPresent(MemberUserDO::getLevelId, reqVO.getLevelId()) + .eqIfPresent(MemberUserDO::getGroupId, reqVO.getGroupId()) + .apply(StrUtil.isNotEmpty(tagIdSql), tagIdSql) + .orderByDesc(MemberUserDO::getId)); + } + + default Long selectCountByGroupId(Long groupId) { + return selectCount(MemberUserDO::getGroupId, groupId); + } + + default Long selectCountByLevelId(Long levelId) { + return selectCount(MemberUserDO::getLevelId, levelId); + } + + default Long selectCountByTagId(Long tagId) { + return selectCount(new LambdaQueryWrapperX() + .apply("FIND_IN_SET({0}, tag_ids)", tagId)); + } + + /** + * 更新用户积分(增加) + * + * @param id 用户编号 + * @param incrCount 增加积分(正数) + */ + default void updatePointIncr(Long id, Integer incrCount) { + Assert.isTrue(incrCount > 0); + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" point = point + " + incrCount) + .eq(MemberUserDO::getId, id); + update(null, lambdaUpdateWrapper); + } + + /** + * 更新用户积分(减少) + * + * @param id 用户编号 + * @param incrCount 增加积分(负数) + * @return 更新行数 + */ + default int updatePointDecr(Long id, Integer incrCount) { + Assert.isTrue(incrCount < 0); + LambdaUpdateWrapper lambdaUpdateWrapper = new LambdaUpdateWrapper() + .setSql(" point = point + " + incrCount) // 负数,所以使用 + 号 + .eq(MemberUserDO::getId, id); + return update(null, lambdaUpdateWrapper); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/package-info.java new file mode 100644 index 0000000..215b6cb --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/package-info.java @@ -0,0 +1,9 @@ +/** + * DAL = Data Access Layer 数据访问层 + * 1. data object:数据对象 + * 2. redis:Redis 的 CRUD 操作 + * 3. mysql:MySQL 的 CRUD 操作 + * + * 其中,MySQL 的表以 member_ 作为前缀 + */ +package com.tashow.cloud.member.dal; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/redis/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/redis/package-info.java new file mode 100644 index 0000000..cccf26d --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/dal/redis/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位,后续有类后,可以删除,避免 package 无法提交到 Git 上 + */ +package com.tashow.cloud.member.dal.redis; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/package-info.java new file mode 100644 index 0000000..86ec634 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 member 模块的 framework 封装 + * + * @author 芋道源码 + */ +package com.tashow.cloud.member.framework; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/rpc/config/RpcConfiguration.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..d4c1d68 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,13 @@ +package com.tashow.cloud.member.framework.rpc.config; + +import com.tashow.cloud.systemapi.api.logger.LoginLogApi; +import com.tashow.cloud.systemapi.api.sms.SmsCodeApi; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.SocialUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {SmsCodeApi.class, LoginLogApi.class, SocialUserApi.class, SocialClientApi.class}) +public class RpcConfiguration { +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/rpc/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/rpc/package-info.java new file mode 100644 index 0000000..8061aa6 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.member.framework.rpc; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..035fc98 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.member.framework.security.config; + +import com.tashow.cloud.memberapi.enums.ApiConstants; +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Member 模块的 Security 配置 + */ +@Configuration("memberSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("memberAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Swagger 接口文档 + registry.requestMatchers("/v3/api-docs/**").permitAll() + .requestMatchers("/webjars/**").permitAll() + .requestMatchers("/swagger-ui").permitAll() + .requestMatchers("/swagger-ui/**").permitAll(); + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/security/core/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/security/core/package-info.java new file mode 100644 index 0000000..f049497 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.member.framework.security.core; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/consumer/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/consumer/package-info.java new file mode 100644 index 0000000..08f2a13 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/consumer/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的消费者 + */ +package com.tashow.cloud.member.mq.consumer; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/message/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/message/package-info.java new file mode 100644 index 0000000..e76e5ad --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/message/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的消息 + */ +package com.tashow.cloud.member.mq.message; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/producer/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/producer/package-info.java new file mode 100644 index 0000000..c38d756 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/producer/package-info.java @@ -0,0 +1,4 @@ +/** + * 消息队列的生产者 + */ +package com.tashow.cloud.member.mq.producer; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/producer/user/MemberUserProducer.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/producer/user/MemberUserProducer.java new file mode 100644 index 0000000..ae43c1c --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/mq/producer/user/MemberUserProducer.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.member.mq.producer.user; + +import com.tashow.cloud.memberapi.message.user.MemberUserCreateMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 会员用户 Producer + * + * @author owen + */ +@Slf4j +@Component +public class MemberUserProducer { + + @Resource + private ApplicationContext applicationContext; + + /** + * 发送 {@link MemberUserCreateMessage} 消息 + * + * @param userId 用户编号 + */ + public void sendUserCreateMessage(Long userId) { + applicationContext.publishEvent(new MemberUserCreateMessage().setUserId(userId)); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/package-info.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/package-info.java new file mode 100644 index 0000000..4ed8d94 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/package-info.java @@ -0,0 +1,8 @@ +/** + * member 模块,我们放会员业务。 + * 例如说:会员中心等等 + * + * 1. Controller URL:以 /member/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 member_ 开头,方便在数据库中区分 + */ +package com.tashow.cloud.member; diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/address/AddressService.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/address/AddressService.java new file mode 100644 index 0000000..cbd2236 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/address/AddressService.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.member.service.address; + +import com.tashow.cloud.member.controller.app.address.vo.AppAddressCreateReqVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressUpdateReqVO; +import com.tashow.cloud.member.dal.dataobject.address.MemberAddressDO; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 用户收件地址 Service 接口 + * + * @author 芋道源码 + */ +public interface AddressService { + + /** + * 创建用户收件地址 + * + * + * @param userId 用户编号 + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createAddress(Long userId, @Valid AppAddressCreateReqVO createReqVO); + + /** + * 更新用户收件地址 + * + * @param userId 用户编号 + * @param updateReqVO 更新信息 + */ + void updateAddress(Long userId, @Valid AppAddressUpdateReqVO updateReqVO); + + /** + * 删除用户收件地址 + * + * @param userId 用户编号 + * @param id 编号 + */ + void deleteAddress(Long userId, Long id); + + /** + * 获得用户收件地址 + * + * @param id 编号 + * @return 用户收件地址 + */ + MemberAddressDO getAddress(Long userId, Long id); + + /** + * 获得用户收件地址列表 + * + * @param userId 用户编号 + * @return 用户收件地址列表 + */ + List getAddressList(Long userId); + + /** + * 获得用户默认的收件地址 + * + * @param userId 用户编号 + * @return 用户收件地址 + */ + MemberAddressDO getDefaultUserAddress(Long userId); + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/address/AddressServiceImpl.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/address/AddressServiceImpl.java new file mode 100644 index 0000000..a5893c2 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/address/AddressServiceImpl.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.member.service.address; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressCreateReqVO; +import com.tashow.cloud.member.controller.app.address.vo.AppAddressUpdateReqVO; +import com.tashow.cloud.member.convert.address.AddressConvert; +import com.tashow.cloud.member.dal.dataobject.address.MemberAddressDO; +import com.tashow.cloud.member.dal.mysql.address.MemberAddressMapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.memberapi.enums.ErrorCodeConstants.ADDRESS_NOT_EXISTS; + +/** + * 用户收件地址 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AddressServiceImpl implements AddressService { + + @Resource + private MemberAddressMapper memberAddressMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createAddress(Long userId, AppAddressCreateReqVO createReqVO) { + // 如果添加的是默认收件地址,则将原默认地址修改为非默认 + if (Boolean.TRUE.equals(createReqVO.getDefaultStatus())) { + List addresses = memberAddressMapper.selectListByUserIdAndDefaulted(userId, true); + addresses.forEach(address -> memberAddressMapper.updateById(new MemberAddressDO().setId(address.getId()).setDefaultStatus(false))); + } + + // 插入 + MemberAddressDO address = AddressConvert.INSTANCE.convert(createReqVO); + address.setUserId(userId); + memberAddressMapper.insert(address); + // 返回 + return address.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateAddress(Long userId, AppAddressUpdateReqVO updateReqVO) { + // 校验存在,校验是否能够操作 + validAddressExists(userId, updateReqVO.getId()); + + // 如果修改的是默认收件地址,则将原默认地址修改为非默认 + if (Boolean.TRUE.equals(updateReqVO.getDefaultStatus())) { + List addresses = memberAddressMapper.selectListByUserIdAndDefaulted(userId, true); + addresses.stream().filter(u -> !u.getId().equals(updateReqVO.getId())) // 排除自己 + .forEach(address -> memberAddressMapper.updateById(new MemberAddressDO().setId(address.getId()).setDefaultStatus(false))); + } + + // 更新 + MemberAddressDO updateObj = AddressConvert.INSTANCE.convert(updateReqVO); + memberAddressMapper.updateById(updateObj); + } + + @Override + public void deleteAddress(Long userId, Long id) { + // 校验存在,校验是否能够操作 + validAddressExists(userId, id); + // 删除 + memberAddressMapper.deleteById(id); + } + + private void validAddressExists(Long userId, Long id) { + MemberAddressDO addressDO = getAddress(userId, id); + if (addressDO == null) { + throw exception(ADDRESS_NOT_EXISTS); + } + } + + @Override + public MemberAddressDO getAddress(Long userId, Long id) { + return memberAddressMapper.selectByIdAndUserId(id, userId); + } + + @Override + public List getAddressList(Long userId) { + return memberAddressMapper.selectListByUserIdAndDefaulted(userId, null); + } + + @Override + public MemberAddressDO getDefaultUserAddress(Long userId) { + List addresses = memberAddressMapper.selectListByUserIdAndDefaulted(userId, true); + return CollUtil.getFirst(addresses); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/auth/MemberAuthService.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/auth/MemberAuthService.java new file mode 100644 index 0000000..303a4d7 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/auth/MemberAuthService.java @@ -0,0 +1,88 @@ +package com.tashow.cloud.member.service.auth; + + +import com.tashow.cloud.member.controller.app.auth.vo.*; +import jakarta.validation.Valid; + +/** + * 会员的认证 Service 接口 + * + * 提供用户的账号密码登录、token 的校验等认证相关的功能 + * + * @author 芋道源码 + */ +public interface MemberAuthService { + + /** + * 手机 + 密码登录 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO); + + /** + * 基于 token 退出登录 + * + * @param token token + */ + void logout(String token); + + /** + * 手机 + 验证码登陆 + * + * @param reqVO 登陆信息 + * @return 登录结果 + */ + AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO); + + /** + * 社交登录,使用 code 授权码 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AppAuthLoginRespVO socialLogin(@Valid AppAuthSocialLoginReqVO reqVO); + + /** + * 微信小程序的一键登录 + * + * @param reqVO 登录信息 + * @return 登录结果 + */ + AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO); + + /** + * 获得社交认证 URL + * + * @param type 社交平台类型 + * @param redirectUri 跳转地址 + * @return 认证 URL + */ + String getSocialAuthorizeUrl(Integer type, String redirectUri); + + /** + * 给用户发送短信验证码 + * + * @param userId 用户编号 + * @param reqVO 发送信息 + */ + void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO); + + /** + * 校验短信验证码是否正确 + * + * @param userId 用户编号 + * @param reqVO 校验信息 + */ + void validateSmsCode(Long userId, AppAuthSmsValidateReqVO reqVO); + + /** + * 刷新访问令牌 + * + * @param refreshToken 刷新令牌 + * @return 登录结果 + */ + AppAuthLoginRespVO refreshToken(String refreshToken); + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/auth/MemberAuthServiceImpl.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/auth/MemberAuthServiceImpl.java new file mode 100644 index 0000000..6561b40 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/auth/MemberAuthServiceImpl.java @@ -0,0 +1,287 @@ +package com.tashow.cloud.member.service.auth; + +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.TerminalEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.util.monitor.TracerUtils; +import com.tashow.cloud.common.util.servlet.ServletUtils; +import com.tashow.cloud.member.controller.app.auth.vo.*; +import com.tashow.cloud.member.convert.auth.AuthConvert; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import com.tashow.cloud.member.service.user.MemberUserService; +import com.tashow.cloud.systemapi.api.logger.LoginLogApi; +import com.tashow.cloud.systemapi.api.logger.dto.LoginLogCreateReqDTO; +import com.tashow.cloud.systemapi.api.oauth2.OAuth2TokenApi; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO; +import com.tashow.cloud.systemapi.api.oauth2.dto.OAuth2AccessTokenRespDTO; +import com.tashow.cloud.systemapi.api.sms.SmsCodeApi; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.SocialUserApi; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserBindReqDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialUserRespDTO; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxPhoneNumberInfoRespDTO; +import com.tashow.cloud.systemapi.enums.logger.LoginLogTypeEnum; +import com.tashow.cloud.systemapi.enums.logger.LoginResultEnum; +import com.tashow.cloud.systemapi.enums.oauth2.OAuth2ClientConstants; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import com.tashow.cloud.systemapi.enums.social.SocialTypeEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; +import static com.tashow.cloud.memberapi.enums.ErrorCodeConstants.AUTH_MOBILE_USED; +import static com.tashow.cloud.memberapi.enums.ErrorCodeConstants.AUTH_SOCIAL_USER_NOT_FOUND; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.*; +import static com.tashow.cloud.web.web.core.util.WebFrameworkUtils.getTerminal; + +/** + * 会员的认证 Service 接口 + * + * @author 芋道源码 + */ +@Service +@Slf4j +public class MemberAuthServiceImpl implements MemberAuthService { + + @Resource + private MemberUserService userService; + @Resource + private SmsCodeApi smsCodeApi; + @Resource + private LoginLogApi loginLogApi; + @Resource + private SocialUserApi socialUserApi; + @Resource + private SocialClientApi socialClientApi; + @Resource + private OAuth2TokenApi oauth2TokenApi; + + @Override + public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) { + // 使用手机 + 密码,进行登录。 + MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword()); + + // 如果 socialType 非空,说明需要绑定社交用户 + String openid = null; + if (reqVO.getSocialType() != null) { + openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), + reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())).getCheckedData(); + } + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, openid); + } + + @Override + @Transactional + public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) { + // 校验验证码 + String userIp = getClientIP(); + smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp)).checkError(); + + // 获得获得注册用户 + MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp, getTerminal()); + Assert.notNull(user, "获取用户失败,结果为空"); + + // 校验是否禁用 + if (CommonStatusEnum.isDisable(user.getStatus())) { + createLoginLog(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, LoginResultEnum.USER_DISABLED); + throw exception(AUTH_LOGIN_USER_DISABLED); + } + + // 如果 socialType 非空,说明需要绑定社交用户 + String openid = null; + if (reqVO.getSocialType() != null) { + openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), + reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())).getCheckedData(); + } + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, openid); + } + + @Override + @Transactional + public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) { + // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号 + SocialUserRespDTO socialUser = socialUserApi.getSocialUserByCode(UserTypeEnum.MEMBER.getValue(), reqVO.getType(), + reqVO.getCode(), reqVO.getState()).getCheckedData(); + if (socialUser == null) { + throw exception(AUTH_SOCIAL_USER_NOT_FOUND); + } + + // 情况一:已绑定,直接读取用户信息 + MemberUserDO user; + if (socialUser.getUserId() != null) { + user = userService.getUser(socialUser.getUserId()); + // 情况二:未绑定,注册用户 + 绑定用户 + } else { + user = userService.createUser(socialUser.getNickname(), socialUser.getAvatar(), getClientIP(), getTerminal()); + socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), + reqVO.getType(), reqVO.getCode(), reqVO.getState())).checkError(); + } + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, socialUser.getOpenid()); + } + + @Override + public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) { + // 获得对应的手机号信息 + SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo( + UserTypeEnum.MEMBER.getValue(), reqVO.getPhoneCode()).getCheckedData(); + Assert.notNull(phoneNumberInfo, "获得手机信息失败,结果为空"); + + // 获得获得注册用户 + MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), + getClientIP(), TerminalEnum.WECHAT_MINI_PROGRAM.getTerminal()); + Assert.notNull(user, "获取用户失败,结果为空"); + + // 绑定社交用户 + String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), + SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), reqVO.getState())).getCheckedData(); + + // 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid); + } + + private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, + LoginLogTypeEnum logType, String openid) { + // 插入登陆日志 + createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS); + // 创建 Token 令牌 + OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO() + .setUserId(user.getId()).setUserType(getUserType().getValue()) + .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT)).getCheckedData(); + // 构建返回结果 + return AuthConvert.INSTANCE.convert(accessTokenRespDTO, openid); + } + + @Override + public String getSocialAuthorizeUrl(Integer type, String redirectUri) { + return socialClientApi.getAuthorizeUrl(type, UserTypeEnum.MEMBER.getValue(), redirectUri).getCheckedData(); + } + + private MemberUserDO login0(String mobile, String password) { + final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE; + // 校验账号是否存在 + MemberUserDO user = userService.getUserByMobile(mobile); + if (user == null) { + createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + if (!userService.isPasswordMatch(password, user.getPassword())) { + createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); + throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + } + // 校验是否禁用 + if (CommonStatusEnum.isDisable(user.getStatus())) { + createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED); + throw exception(AUTH_LOGIN_USER_DISABLED); + } + return user; + } + + private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) { + // 插入登录日志 + LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); + reqDTO.setLogType(logType.getType()); + reqDTO.setTraceId(TracerUtils.getTraceId()); + reqDTO.setUserId(userId); + reqDTO.setUserType(getUserType().getValue()); + reqDTO.setUsername(mobile); + reqDTO.setUserAgent(ServletUtils.getUserAgent()); + reqDTO.setUserIp(getClientIP()); + reqDTO.setResult(loginResult.getResult()); + loginLogApi.createLoginLog(reqDTO).checkError(); + // 更新最后登录时间 + if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { + userService.updateUserLogin(userId, getClientIP()); + } + } + + @Override + public void logout(String token) { + // 删除访问令牌 + OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token).getCheckedData(); + if (accessTokenRespDTO == null) { + return; + } + // 删除成功,则记录登出日志 + createLogoutLog(accessTokenRespDTO.getUserId()); + } + + @Override + public void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO) { + // 情况 1:如果是修改手机场景,需要校验新手机号是否已经注册,说明不能使用该手机了 + if (Objects.equals(reqVO.getScene(), SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene())) { + MemberUserDO user = userService.getUserByMobile(reqVO.getMobile()); + if (user != null && !Objects.equals(user.getId(), userId)) { + throw exception(AUTH_MOBILE_USED); + } + } + // 情况 2:如果是重置密码场景,需要校验手机号是存在的 + if (Objects.equals(reqVO.getScene(), SmsSceneEnum.MEMBER_RESET_PASSWORD.getScene())) { + MemberUserDO user = userService.getUserByMobile(reqVO.getMobile()); + if (user == null) { + throw exception(USER_MOBILE_NOT_EXISTS); + } + } + // 情况 3:如果是修改密码场景,需要查询手机号,无需前端传递 + if (Objects.equals(reqVO.getScene(), SmsSceneEnum.MEMBER_UPDATE_PASSWORD.getScene())) { + MemberUserDO user = userService.getUser(userId); + // TODO 芋艿:后续 member user 手机非强绑定,这块需要做下调整; + reqVO.setMobile(user.getMobile()); + } + + // 执行发送 + smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP())).checkError(); + } + + @Override + public void validateSmsCode(Long userId, AppAuthSmsValidateReqVO reqVO) { + smsCodeApi.validateSmsCode(AuthConvert.INSTANCE.convert(reqVO)); + } + + @Override + public AppAuthLoginRespVO refreshToken(String refreshToken) { + OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, + OAuth2ClientConstants.CLIENT_ID_DEFAULT).getCheckedData(); + return AuthConvert.INSTANCE.convert(accessTokenDO, null); + } + + private void createLogoutLog(Long userId) { + LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO(); + reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType()); + reqDTO.setTraceId(TracerUtils.getTraceId()); + reqDTO.setUserId(userId); + reqDTO.setUserType(getUserType().getValue()); + reqDTO.setUsername(getMobile(userId)); + reqDTO.setUserAgent(ServletUtils.getUserAgent()); + reqDTO.setUserIp(getClientIP()); + reqDTO.setResult(LoginResultEnum.SUCCESS.getResult()); + loginLogApi.createLoginLog(reqDTO).checkError(); + } + + private String getMobile(Long userId) { + if (userId == null) { + return null; + } + MemberUserDO user = userService.getUser(userId); + return user != null ? user.getMobile() : null; + } + + private UserTypeEnum getUserType() { + return UserTypeEnum.MEMBER; + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/user/MemberUserService.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/user/MemberUserService.java new file mode 100644 index 0000000..77c748c --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/user/MemberUserService.java @@ -0,0 +1,190 @@ +package com.tashow.cloud.member.service.user; + +import com.tashow.cloud.common.enums.TerminalEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserPageReqVO; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserUpdateReqVO; +import com.tashow.cloud.member.controller.app.user.vo.*; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 会员用户 Service 接口 + * + * @author 芋道源码 + */ +public interface MemberUserService { + + /** + * 通过手机查询用户 + * + * @param mobile 手机 + * @return 用户对象 + */ + MemberUserDO getUserByMobile(String mobile); + + /** + * 基于用户昵称,模糊匹配用户列表 + * + * @param nickname 用户昵称,模糊匹配 + * @return 用户信息的列表 + */ + List getUserListByNickname(String nickname); + + /** + * 基于手机号创建用户。 + * 如果用户已经存在,则直接进行返回 + * + * @param mobile 手机号 + * @param registerIp 注册 IP + * @param terminal 终端 {@link TerminalEnum} + * @return 用户对象 + */ + MemberUserDO createUserIfAbsent(@Mobile String mobile, String registerIp, Integer terminal); + + /** + * 创建用户 + * 目的:三方登录时,如果未绑定用户时,自动创建对应用户 + * + * @param nickname 昵称 + * @param avtar 头像 + * @param registerIp 注册 IP + * @param terminal 终端 {@link TerminalEnum} + * @return 用户对象 + */ + MemberUserDO createUser(String nickname, String avtar, String registerIp, Integer terminal); + + /** + * 更新用户的最后登陆信息 + * + * @param id 用户编号 + * @param loginIp 登陆 IP + */ + void updateUserLogin(Long id, String loginIp); + + /** + * 通过用户 ID 查询用户 + * + * @param id 用户ID + * @return 用户对象信息 + */ + MemberUserDO getUser(Long id); + + /** + * 通过用户 ID 查询用户们 + * + * @param ids 用户 ID + * @return 用户对象信息数组 + */ + List getUserList(Collection ids); + + /** + * 【会员】修改基本信息 + * + * @param userId 用户编号 + * @param reqVO 基本信息 + */ + void updateUser(Long userId, AppMemberUserUpdateReqVO reqVO); + + /** + * 【会员】修改手机,基于手机验证码 + * + * @param userId 用户编号 + * @param reqVO 请求信息 + */ + void updateUserMobile(Long userId, AppMemberUserUpdateMobileReqVO reqVO); + + /** + * 【会员】修改手机,基于微信小程序的授权码 + * + * @param userId 用户编号 + * @param reqVO 请求信息 + */ + void updateUserMobileByWeixin(Long userId, AppMemberUserUpdateMobileByWeixinReqVO reqVO); + + /** + * 【会员】修改密码 + * + * @param userId 用户编号 + * @param reqVO 请求信息 + */ + void updateUserPassword(Long userId, AppMemberUserUpdatePasswordReqVO reqVO); + + /** + * 【会员】忘记密码 + * + * @param reqVO 请求信息 + */ + void resetUserPassword(AppMemberUserResetPasswordReqVO reqVO); + + /** + * 判断密码是否匹配 + * + * @param rawPassword 未加密的密码 + * @param encodedPassword 加密后的密码 + * @return 是否匹配 + */ + boolean isPasswordMatch(String rawPassword, String encodedPassword); + + /** + * 【管理员】更新会员用户 + * + * @param updateReqVO 更新信息 + */ + void updateUser(@Valid MemberUserUpdateReqVO updateReqVO); + + /** + * 【管理员】获得会员用户分页 + * + * @param pageReqVO 分页查询 + * @return 会员用户分页 + */ + PageResult getUserPage(MemberUserPageReqVO pageReqVO); + + /** + * 更新用户的等级和经验 + * + * @param id 用户编号 + * @param levelId 用户等级 + * @param experience 用户经验 + */ + void updateUserLevel(Long id, Long levelId, Integer experience); + + /** + * 获得指定用户分组下的用户数量 + * + * @param groupId 用户分组编号 + * @return 用户数量 + */ + Long getUserCountByGroupId(Long groupId); + + /** + * 获得指定用户等级下的用户数量 + * + * @param levelId 用户等级编号 + * @return 用户数量 + */ + Long getUserCountByLevelId(Long levelId); + + /** + * 获得指定会员标签下的用户数量 + * + * @param tagId 用户标签编号 + * @return 用户数量 + */ + Long getUserCountByTagId(Long tagId); + + /** + * 更新用户的积分 + * + * @param userId 用户编号 + * @param point 积分数量 + * @return 更新结果 + */ + boolean updateUserPoint(Long userId, Integer point); + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/user/MemberUserServiceImpl.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/user/MemberUserServiceImpl.java new file mode 100644 index 0000000..7aed156 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/service/user/MemberUserServiceImpl.java @@ -0,0 +1,323 @@ +package com.tashow.cloud.member.service.user; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserPageReqVO; +import com.tashow.cloud.member.controller.admin.user.vo.MemberUserUpdateReqVO; +import com.tashow.cloud.member.controller.app.user.vo.*; +import com.tashow.cloud.member.convert.auth.AuthConvert; +import com.tashow.cloud.member.convert.user.MemberUserConvert; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import com.tashow.cloud.member.dal.mysql.user.MemberUserMapper; +import com.tashow.cloud.member.mq.producer.user.MemberUserProducer; +import com.tashow.cloud.systemapi.api.sms.SmsCodeApi; +import com.tashow.cloud.systemapi.api.sms.dto.code.SmsCodeUseReqDTO; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxPhoneNumberInfoRespDTO; +import com.tashow.cloud.systemapi.enums.sms.SmsSceneEnum; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; +import static com.tashow.cloud.memberapi.enums.ErrorCodeConstants.USER_MOBILE_USED; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.USER_MOBILE_NOT_EXISTS; +import static com.tashow.cloud.systemapi.enums.ErrorCodeConstants.USER_NOT_EXISTS; + + +/** + * 会员 User Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Valid +@Slf4j +public class MemberUserServiceImpl implements MemberUserService { + + @Resource + private MemberUserMapper memberUserMapper; + + @Resource + private SmsCodeApi smsCodeApi; + + @Resource + private SocialClientApi socialClientApi; + + @Resource + private PasswordEncoder passwordEncoder; + + @Resource + private MemberUserProducer memberUserProducer; + + @Override + public MemberUserDO getUserByMobile(String mobile) { + return memberUserMapper.selectByMobile(mobile); + } + + @Override + public List getUserListByNickname(String nickname) { + return memberUserMapper.selectListByNicknameLike(nickname); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public MemberUserDO createUserIfAbsent(String mobile, String registerIp, Integer terminal) { + // 用户已经存在 + MemberUserDO user = memberUserMapper.selectByMobile(mobile); + if (user != null) { + return user; + } + // 用户不存在,则进行创建 + return createUser(mobile, null, null, registerIp, terminal); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public MemberUserDO createUser(String nickname, String avtar, String registerIp, Integer terminal) { + return createUser(null, nickname, avtar, registerIp, terminal); + } + + private MemberUserDO createUser(String mobile, String nickname, String avtar, + String registerIp, Integer terminal) { + // 生成密码 + String password = IdUtil.fastSimpleUUID(); + // 插入用户 + MemberUserDO user = new MemberUserDO(); + user.setMobile(mobile); + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 + user.setPassword(encodePassword(password)); // 加密密码 + user.setRegisterIp(registerIp).setRegisterTerminal(terminal); + user.setNickname(nickname).setAvatar(avtar); // 基础信息 + if (StrUtil.isEmpty(nickname)) { + // 昵称为空时,随机一个名字,避免一些依赖 nickname 的逻辑报错,或者有点丑。例如说,短信发送有昵称时~ + user.setNickname("用户" + RandomUtil.randomNumbers(6)); + } + memberUserMapper.insert(user); + + // 发送 MQ 消息:用户创建 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + memberUserProducer.sendUserCreateMessage(user.getId()); + } + + }); + return user; + } + + @Override + public void updateUserLogin(Long id, String loginIp) { + memberUserMapper.updateById(new MemberUserDO().setId(id) + .setLoginIp(loginIp).setLoginDate(LocalDateTime.now())); + } + + @Override + public MemberUserDO getUser(Long id) { + return memberUserMapper.selectById(id); + } + + @Override + public List getUserList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return ListUtil.empty(); + } + return memberUserMapper.selectBatchIds(ids); + } + + @Override + public void updateUser(Long userId, AppMemberUserUpdateReqVO reqVO) { + MemberUserDO updateObj = BeanUtils.toBean(reqVO, MemberUserDO.class).setId(userId); + memberUserMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateUserMobile(Long userId, AppMemberUserUpdateMobileReqVO reqVO) { + // 1.1 检测用户是否存在 + MemberUserDO user = validateUserExists(userId); + // 1.2 校验新手机是否已经被绑定 + validateMobileUnique(null, reqVO.getMobile()); + + // 2.1 校验旧手机和旧验证码 + // 补充说明:从安全性来说,老手机也校验 oldCode 验证码会更安全。但是由于 uni-app 商城界面暂时没做,所以这里不强制校验 + if (StrUtil.isNotEmpty(reqVO.getOldCode())) { + smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(user.getMobile()).setCode(reqVO.getOldCode()) + .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP())).checkError(); + } + // 2.2 使用新验证码 + smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode()) + .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP())).checkError(); + + // 3. 更新用户手机 + memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build()); + } + + @Override + public void updateUserMobileByWeixin(Long userId, AppMemberUserUpdateMobileByWeixinReqVO reqVO) { + // 1.1 获得对应的手机号信息 + SocialWxPhoneNumberInfoRespDTO phoneNumberInfo = socialClientApi.getWxMaPhoneNumberInfo( + UserTypeEnum.MEMBER.getValue(), reqVO.getCode()).getCheckedData(); + Assert.notNull(phoneNumberInfo, "获得手机信息失败,结果为空"); + // 1.2 校验新手机是否已经被绑定 + validateMobileUnique(userId, phoneNumberInfo.getPhoneNumber()); + + // 2. 更新用户手机 + memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(phoneNumberInfo.getPhoneNumber()).build()); + } + + @Override + public void updateUserPassword(Long userId, AppMemberUserUpdatePasswordReqVO reqVO) { + // 检测用户是否存在 + MemberUserDO user = validateUserExists(userId); + // 校验验证码 + smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(user.getMobile()).setCode(reqVO.getCode()) + .setScene(SmsSceneEnum.MEMBER_UPDATE_PASSWORD.getScene()).setUsedIp(getClientIP())).checkError(); + + // 更新用户密码 + memberUserMapper.updateById(MemberUserDO.builder().id(userId) + .password(passwordEncoder.encode(reqVO.getPassword())).build()); + } + + @Override + public void resetUserPassword(AppMemberUserResetPasswordReqVO reqVO) { + // 检验用户是否存在 + MemberUserDO user = validateUserExists(reqVO.getMobile()); + + // 使用验证码 + smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_RESET_PASSWORD, + getClientIP())).checkError(); + + // 更新密码 + memberUserMapper.updateById(MemberUserDO.builder().id(user.getId()) + .password(passwordEncoder.encode(reqVO.getPassword())).build()); + } + + private MemberUserDO validateUserExists(String mobile) { + MemberUserDO user = memberUserMapper.selectByMobile(mobile); + if (user == null) { + throw exception(USER_MOBILE_NOT_EXISTS); + } + return user; + } + + @Override + public boolean isPasswordMatch(String rawPassword, String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 对密码进行加密 + * + * @param password 密码 + * @return 加密后的密码 + */ + private String encodePassword(String password) { + return passwordEncoder.encode(password); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateUser(MemberUserUpdateReqVO updateReqVO) { + // 校验存在 + validateUserExists(updateReqVO.getId()); + // 校验手机唯一 + validateMobileUnique(updateReqVO.getId(), updateReqVO.getMobile()); + + // 更新 + MemberUserDO updateObj = MemberUserConvert.INSTANCE.convert(updateReqVO); + memberUserMapper.updateById(updateObj); + } + + @VisibleForTesting + MemberUserDO validateUserExists(Long id) { + if (id == null) { + return null; + } + MemberUserDO user = memberUserMapper.selectById(id); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + return user; + } + + @VisibleForTesting + void validateMobileUnique(Long id, String mobile) { + if (StrUtil.isBlank(mobile)) { + return; + } + MemberUserDO user = memberUserMapper.selectByMobile(mobile); + if (user == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的用户 + if (id == null) { + throw exception(USER_MOBILE_USED, mobile); + } + if (!user.getId().equals(id)) { + throw exception(USER_MOBILE_USED, mobile); + } + } + + @Override + public PageResult getUserPage(MemberUserPageReqVO pageReqVO) { + return memberUserMapper.selectPage(pageReqVO); + } + + @Override + public void updateUserLevel(Long id, Long levelId, Integer experience) { + // 0 代表无等级:防止UpdateById时,会被过滤掉的问题 + levelId = ObjectUtil.defaultIfNull(levelId, 0L); + memberUserMapper.updateById(new MemberUserDO() + .setId(id) + .setLevelId(levelId).setExperience(experience) + ); + } + + @Override + public Long getUserCountByGroupId(Long groupId) { + return memberUserMapper.selectCountByGroupId(groupId); + } + + @Override + public Long getUserCountByLevelId(Long levelId) { + return memberUserMapper.selectCountByLevelId(levelId); + } + + @Override + public Long getUserCountByTagId(Long tagId) { + return memberUserMapper.selectCountByTagId(tagId); + } + + @Override + public boolean updateUserPoint(Long id, Integer point) { + if (point > 0) { + memberUserMapper.updatePointIncr(id, point); + } else if (point < 0) { + return memberUserMapper.updatePointDecr(id, point) > 0; + } + return true; + } + +} diff --git a/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/user/MemberUserApiImpl.java b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/user/MemberUserApiImpl.java new file mode 100644 index 0000000..5ae28e6 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/java/com/tashow/cloud/member/user/MemberUserApiImpl.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.member.user; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.memberapi.api.user.MemberUserApi; +import com.tashow.cloud.memberapi.api.user.dto.MemberUserRespDTO; +import com.tashow.cloud.member.convert.user.MemberUserConvert; +import com.tashow.cloud.member.dal.dataobject.user.MemberUserDO; +import com.tashow.cloud.member.service.user.MemberUserService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.memberapi.enums.ErrorCodeConstants.USER_MOBILE_NOT_EXISTS; + +/** + * 会员用户的 API 实现类 + * + * @author 芋道源码 + */ +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class MemberUserApiImpl implements MemberUserApi { + + @Resource + private MemberUserService userService; + + @Override + public CommonResult getUser(Long id) { + MemberUserDO user = userService.getUser(id); + return success(MemberUserConvert.INSTANCE.convert2(user)); + } + + @Override + public CommonResult> getUserList(Collection ids) { + return success(MemberUserConvert.INSTANCE.convertList2(userService.getUserList(ids))); + } + + @Override + public CommonResult> getUserListByNickname(String nickname) { + return success(MemberUserConvert.INSTANCE.convertList2(userService.getUserListByNickname(nickname))); + } + + @Override + public CommonResult getUserByMobile(String mobile) { + return success(MemberUserConvert.INSTANCE.convert2(userService.getUserByMobile(mobile))); + } + + @Override + public CommonResult validateUser(Long id) { + MemberUserDO user = userService.getUser(id); + if (user == null) { + throw exception(USER_MOBILE_NOT_EXISTS); + } + return success(true); + } + +} diff --git a/tashow-module/tashow-module-member/src/main/resources/application-local.yaml b/tashow-module/tashow-module-member/src/main/resources/application-local.yaml new file mode 100644 index 0000000..80b237a --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/resources/application-local.yaml @@ -0,0 +1,19 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + username: # Nacos 账号 + password: # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + + + diff --git a/tashow-module/tashow-module-member/src/main/resources/application.yaml b/tashow-module/tashow-module-member/src/main/resources/application.yaml new file mode 100644 index 0000000..f98b23c --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/resources/application.yaml @@ -0,0 +1,18 @@ +server: + port: 48084 +spring: + application: + name: member-server + + profiles: + active: local + + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + - optional:nacos:application.yaml # 加载【Nacos】的配置 diff --git a/tashow-module/tashow-module-member/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-member/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8c6b0f7 --- /dev/null +++ b/tashow-module/tashow-module-member/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-module/tashow-module-member/src/test/resources/application-unit-test.yaml b/tashow-module/tashow-module-member/src/test/resources/application-unit-test.yaml new file mode 100644 index 0000000..c1f6573 --- /dev/null +++ b/tashow-module/tashow-module-member/src/test/resources/application-unit-test.yaml @@ -0,0 +1,48 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + info: + base-package: cn.iocoder.yudao.module diff --git a/tashow-module/tashow-module-member/src/test/resources/logback.xml b/tashow-module/tashow-module-member/src/test/resources/logback.xml new file mode 100644 index 0000000..daf756b --- /dev/null +++ b/tashow-module/tashow-module-member/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tashow-module/tashow-module-member/src/test/resources/sql/clean.sql b/tashow-module/tashow-module-member/src/test/resources/sql/clean.sql new file mode 100644 index 0000000..f972e04 --- /dev/null +++ b/tashow-module/tashow-module-member/src/test/resources/sql/clean.sql @@ -0,0 +1,5 @@ +DELETE FROM "member_user"; +DELETE FROM "member_address"; +DELETE FROM "member_tag"; +DELETE FROM "member_level"; +DELETE FROM "member_group"; \ No newline at end of file diff --git a/tashow-module/tashow-module-member/src/test/resources/sql/create_tables.sql b/tashow-module/tashow-module-member/src/test/resources/sql/create_tables.sql new file mode 100644 index 0000000..782a818 --- /dev/null +++ b/tashow-module/tashow-module-member/src/test/resources/sql/create_tables.sql @@ -0,0 +1,113 @@ +CREATE TABLE IF NOT EXISTS "member_user" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号', + "nickname" varchar(30) NOT NULL DEFAULT '' COMMENT '用户昵称', + "name" varchar(30) NULL COMMENT '真实名字', + sex tinyint null comment '性别', + birthday datetime null comment '出生日期', + area_id int null comment '所在地', + mark varchar(255) null comment '用户备注', + point int default 0 null comment '积分', + "avatar" varchar(255) NOT NULL DEFAULT '' COMMENT '头像', + "status" tinyint NOT NULL COMMENT '状态', + "mobile" varchar(11) NOT NULL COMMENT '手机号', + "password" varchar(100) NOT NULL DEFAULT '' COMMENT '密码', + "register_ip" varchar(32) NOT NULL COMMENT '注册 IP', + "login_ip" varchar(50) NULL DEFAULT '' COMMENT '最后登录IP', + "login_date" datetime NULL DEFAULT NULL COMMENT '最后登录时间', + "tag_ids" varchar(255) NULL DEFAULT NULL COMMENT '用户标签编号列表,以逗号分隔', + "level_id" bigint NULL DEFAULT NULL COMMENT '等级编号', + "experience" bigint NULL DEFAULT NULL COMMENT '经验', + "group_id" bigint NULL DEFAULT NULL COMMENT '用户分组编号', + "creator" varchar(64) NULL DEFAULT '' COMMENT '创建者', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + "updater" varchar(64) NULL DEFAULT '' COMMENT '更新者', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + "deleted" bit(1) NOT NULL DEFAULT '0' COMMENT '是否删除', + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT '会员表'; + +CREATE TABLE IF NOT EXISTS "member_address" ( + "id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint(20) NOT NULL, + "name" varchar(10) NOT NULL, + "mobile" varchar(20) NOT NULL, + "area_id" bigint(20) NOT NULL, + "detail_address" varchar(250) NOT NULL, + "default_status" bit NOT NULL, + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "creator" varchar(64) DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "updater" varchar(64) DEFAULT '', + PRIMARY KEY ("id") +) COMMENT '用户收件地址'; + +CREATE TABLE IF NOT EXISTS "member_tag" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL default '0', + PRIMARY KEY ("id") +) COMMENT '会员标签'; + +CREATE TABLE IF NOT EXISTS "member_level" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar NOT NULL, + "experience" int NOT NULL, + "level" int NOT NULL, + "discount_percent" int NOT NULL, + "icon" varchar NOT NULL, + "background_url" varchar NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + "status" tinyint NOT NULL DEFAULT '0', + PRIMARY KEY ("id") +) COMMENT '会员等级'; + +CREATE TABLE IF NOT EXISTS "member_group" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar NOT NULL, + "remark" varchar NOT NULL, + "status" tinyint NOT NULL DEFAULT '0', + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT '用户分组'; +CREATE TABLE IF NOT EXISTS "member_brokerage_record" +( + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "biz_id" varchar NOT NULL, + "biz_type" varchar NOT NULL, + "title" varchar NOT NULL, + "price" int NOT NULL, + "total_price" int NOT NULL, + "description" varchar NOT NULL, + "status" varchar NOT NULL, + "frozen_days" int NOT NULL, + "unfreeze_time" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT '佣金记录'; diff --git a/tashow-module/tashow-module-pay/Dockerfile b/tashow-module/tashow-module-pay/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-pay/pom.xml b/tashow-module/tashow-module-pay/pom.xml new file mode 100644 index 0000000..b6dc356 --- /dev/null +++ b/tashow-module/tashow-module-pay/pom.xml @@ -0,0 +1,101 @@ + + + + com.tashow.cloud + tashow-module + ${revision} + + 4.0.0 + tashow-module-pay + jar + + ${project.artifactId} + + pay 模块,我们放支付业务,提供业务的支付能力。 + 例如说:商户、应用、支付、退款等等 + + + + + + com.tashow.cloud + tashow-framework-env + + + + + com.tashow.cloud + tashow-pay-api + + + + + com.tashow.cloud + tashow-framework-security + + + + + com.tashow.cloud + tashow-data-mybatis + + + + com.tashow.cloud + tashow-data-redis + + + + + com.tashow.cloud + tashow-framework-rpc + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + + com.tashow.cloud + tashow-data-excel + + + com.tashow.cloud + tashow-framework-tenant + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/PayServerApplication.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/PayServerApplication.java new file mode 100644 index 0000000..fe1defd --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/PayServerApplication.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.pay; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + * + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + * + * @author 芋道源码 + */ +@SpringBootApplication +public class PayServerApplication { + + public static void main(String[] args) { + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + + SpringApplication.run(PayServerApplication.class, args); + + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + // 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章 + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/order/PayOrderApiImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/order/PayOrderApiImpl.java new file mode 100644 index 0000000..398f4dd --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/order/PayOrderApiImpl.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.pay.api.order; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.pay.convert.order.PayOrderConvert; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.payapi.api.order.PayOrderApi; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class PayOrderApiImpl implements PayOrderApi { + + @Resource + private PayOrderService payOrderService; + + @Override + public CommonResult createOrder(PayOrderCreateReqDTO reqDTO) { + return success(payOrderService.createOrder(reqDTO)); + } + + @Override + public CommonResult getOrder(Long id) { + PayOrderDO order = payOrderService.getOrder(id); + return success(PayOrderConvert.INSTANCE.convert2(order)); + } + + @Override + public CommonResult updatePayOrderPrice(Long id, Integer payPrice) { + payOrderService.updatePayOrderPrice(id, payPrice); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/refund/PayRefundApiImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/refund/PayRefundApiImpl.java new file mode 100644 index 0000000..141317c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/refund/PayRefundApiImpl.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.pay.api.refund; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.pay.convert.refund.PayRefundConvert; +import com.tashow.cloud.pay.service.refund.PayRefundService; +import com.tashow.cloud.payapi.api.refund.PayRefundApi; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundRespDTO; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class PayRefundApiImpl implements PayRefundApi { + + @Resource + private PayRefundService payRefundService; + + @Override + public CommonResult createRefund(PayRefundCreateReqDTO reqDTO) { + return success(payRefundService.createPayRefund(reqDTO)); + } + + @Override + public CommonResult getRefund(Long id) { + return success(PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id))); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/transfer/PayTransferApiImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/transfer/PayTransferApiImpl.java new file mode 100644 index 0000000..7b92849 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/api/transfer/PayTransferApiImpl.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.pay.api.transfer; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import com.tashow.cloud.pay.service.transfer.PayTransferService; +import com.tashow.cloud.payapi.api.transfer.PayTransferApi; +import com.tashow.cloud.payapi.api.transfer.dto.PayTransferCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +/** + * 转账单 API 实现类 + * + * @author jason + */ +@RestController // 提供 RESTful API 接口,给 Feign 调用 +@Validated +public class PayTransferApiImpl implements PayTransferApi { + + @Resource + private PayTransferService payTransferService; + + @Override + public CommonResult createTransfer(PayTransferCreateReqDTO reqDTO) { + return success(payTransferService.createTransfer(reqDTO)); + } + + @Override + public CommonResult getTransfer(Long id) { + PayTransferDO transfer = payTransferService.getTransfer(id); + return success(BeanUtils.toBean(transfer, PayTransferRespDTO.class)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/PayAppController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/PayAppController.java new file mode 100644 index 0000000..aee0e67 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/PayAppController.java @@ -0,0 +1,106 @@ +package com.tashow.cloud.pay.controller.admin.app; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.app.vo.*; +import com.tashow.cloud.pay.convert.app.PayAppConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; + + +@Slf4j +// 管理后台 - 支付应用信息 +@RestController +@RequestMapping("/pay/app") +@Validated +public class PayAppController { + + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + + @PostMapping("/create") + // 创建支付应用信息 + @PreAuthorize("@ss.hasPermission('pay:app:create')") + public CommonResult createApp(@Valid @RequestBody PayAppCreateReqVO createReqVO) { + return success(appService.createApp(createReqVO)); + } + + @PutMapping("/update") + // 更新支付应用信息 + @PreAuthorize("@ss.hasPermission('pay:app:update')") + public CommonResult updateApp(@Valid @RequestBody PayAppUpdateReqVO updateReqVO) { + appService.updateApp(updateReqVO); + return success(true); + } + + @PutMapping("/update-status") + // 更新支付应用状态 + @PreAuthorize("@ss.hasPermission('pay:app:update')") + public CommonResult updateAppStatus(@Valid @RequestBody PayAppUpdateStatusReqVO updateReqVO) { + appService.updateAppStatus(updateReqVO.getId(), updateReqVO.getStatus()); + return success(true); + } + + @DeleteMapping("/delete") + // 删除支付应用信息 + // id: 编号,必填 + @PreAuthorize("@ss.hasPermission('pay:app:delete')") + public CommonResult deleteApp(@RequestParam("id") Long id) { + appService.deleteApp(id); + return success(true); + } + + @GetMapping("/get") + // 获得支付应用信息 + // id: 编号,必填,示例:1024 + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult getApp(@RequestParam("id") Long id) { + PayAppDO app = appService.getApp(id); + return success(PayAppConvert.INSTANCE.convert(app)); + } + + @GetMapping("/page") + // 获得支付应用信息分页 + @PreAuthorize("@ss.hasPermission('pay:app:query')") + public CommonResult> getAppPage(@Valid PayAppPageReqVO pageVO) { + // 得到应用分页列表 + PageResult pageResult = appService.getAppPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 得到所有的应用编号,查出所有的渠道,并移除未启用的渠道 + List channels = channelService.getChannelListByAppIds( + convertList(pageResult.getList(), PayAppDO::getId)); + channels.removeIf(channel -> !CommonStatusEnum.ENABLE.getStatus().equals(channel.getStatus())); + + // 拼接后返回 + return success(PayAppConvert.INSTANCE.convertPage(pageResult, channels)); + } + + @GetMapping("/list") + // 获得应用列表 + @PreAuthorize("@ss.hasPermission('pay:merchant:query')") + public CommonResult> getAppList() { + List appListDO = appService.getAppList(); + return success(PayAppConvert.INSTANCE.convertList(appListDO)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppBaseVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppBaseVO.java new file mode 100644 index 0000000..b0f6a49 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppBaseVO.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +/** + * 支付应用信息 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class PayAppBaseVO { + + // 应用标识,必填,示例:yudao + @NotEmpty(message = "应用标识不能为空") + private String appKey; + + // 应用名,必填,示例:小豆 + @NotNull(message = "应用名不能为空") + private String name; + + // 开启状态,必填,示例:0 + @NotNull(message = "开启状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + // 备注,示例:我是一个测试应用 + private String remark; + + // 支付结果的回调地址,必填,示例:http://127.0.0.1:48080/pay-callback + @NotNull(message = "支付结果的回调地址不能为空") + @URL(message = "支付结果的回调地址必须为 URL 格式") + private String orderNotifyUrl; + + // 退款结果的回调地址,必填,示例:http://127.0.0.1:48080/refund-callback + @NotNull(message = "退款结果的回调地址不能为空") + @URL(message = "退款结果的回调地址必须为 URL 格式") + private String refundNotifyUrl; + + // 转账结果的回调地址,示例:http://127.0.0.1:48080/transfer-callback + @URL(message = "转账结果的回调地址必须为 URL 格式") + private String transferNotifyUrl; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppCreateReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppCreateReqVO.java new file mode 100644 index 0000000..79a0c85 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppCreateReqVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 支付应用信息创建 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppCreateReqVO extends PayAppBaseVO { + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppPageItemRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppPageItemRespVO.java new file mode 100644 index 0000000..9316d0f --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppPageItemRespVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.Set; + +// 管理后台 - 支付应用信息分页查询 Response VO,相比于支付信息,还会多出应用渠道的开关信息 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppPageItemRespVO extends PayAppBaseVO { + + // 应用编号,必填,示例:1024 + private Long id; + + // 创建时间,必填 + private LocalDateTime createTime; + + // 已配置的支付渠道编码,必填,示例:[alipay_pc, alipay_wap] + private Set channelCodes; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppPageReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppPageReqVO.java new file mode 100644 index 0000000..c0117dc --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppPageReqVO.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 支付应用信息分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppPageReqVO extends PageParam { + + // 应用名,示例:小豆 + private String name; + + // 应用标识,示例:yudao + private String appKey; + + // 开启状态,示例:0 + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + // 创建时间 + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppRespVO.java new file mode 100644 index 0000000..9937aad --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppRespVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 支付应用信息 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppRespVO extends PayAppBaseVO { + + // 应用编号,必填,示例:1024 + private Long id; + + // 应用标识,必填,示例:yudao + private String appKey; + + // 创建时间,必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppUpdateReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppUpdateReqVO.java new file mode 100644 index 0000000..12ee0f5 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppUpdateReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 支付应用信息更新 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayAppUpdateReqVO extends PayAppBaseVO { + + // 应用编号,必填,示例:1024 + @NotNull(message = "应用编号不能为空") + private Long id; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java new file mode 100644 index 0000000..a5ccdb8 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/app/vo/PayAppUpdateStatusReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.pay.controller.admin.app.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 应用更新状态 Request VO +@Data +public class PayAppUpdateStatusReqVO { + + // 应用编号,必填,示例:1024 + @NotNull(message = "应用编号不能为空") + private Long id; + + // 状态,见 SysCommonStatusEnum 枚举,必填,示例:1 + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/PayChannelController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/PayChannelController.java new file mode 100644 index 0000000..0e8c171 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/PayChannelController.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.pay.controller.admin.channel; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelCreateReqVO; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelRespVO; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; +import com.tashow.cloud.pay.convert.channel.PayChannelConvert; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Set; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + + +// 管理后台 - 支付渠道 +@RestController +@RequestMapping("/pay/channel") +@Validated +public class PayChannelController { + + @Resource + private PayChannelService channelService; + + @PostMapping("/create") + // 创建支付渠道 + @PreAuthorize("@ss.hasPermission('pay:channel:create')") + public CommonResult createChannel(@Valid @RequestBody PayChannelCreateReqVO createReqVO) { + return success(channelService.createChannel(createReqVO)); + } + + @PutMapping("/update") + // 更新支付渠道 + @PreAuthorize("@ss.hasPermission('pay:channel:update')") + public CommonResult updateChannel(@Valid @RequestBody PayChannelUpdateReqVO updateReqVO) { + channelService.updateChannel(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + // 删除支付渠道 + // id: 编号,必填 + @PreAuthorize("@ss.hasPermission('pay:channel:delete')") + public CommonResult deleteChannel(@RequestParam("id") Long id) { + channelService.deleteChannel(id); + return success(true); + } + + @GetMapping("/get") + // 获得支付渠道 + // id: 编号,必填,示例:1024 + @PreAuthorize("@ss.hasPermission('pay:channel:query')") + public CommonResult getChannel(@RequestParam(value = "id", required = false) Long id, + @RequestParam(value = "appId", required = false) Long appId, + @RequestParam(value = "code", required = false) String code) { + PayChannelDO channel = null; + if (id != null) { + channel = channelService.getChannel(id); + } else if (appId != null && code != null) { + channel = channelService.getChannelByAppIdAndCode(appId, code); + } + return success(PayChannelConvert.INSTANCE.convert(channel)); + } + + @GetMapping("/get-enable-code-list") + // 获得指定应用的开启的支付渠道编码列表 + // appId: 应用编号,必填,示例:1 + public CommonResult> getEnableChannelCodeList(@RequestParam("appId") Long appId) { + List channels = channelService.getEnableChannelList(appId); + return success(convertSet(channels, PayChannelDO::getCode)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelBaseVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelBaseVO.java new file mode 100644 index 0000000..7f5bdf3 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelBaseVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.pay.controller.admin.channel.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** +* 支付渠道 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayChannelBaseVO { + + // 开启状态,必填,示例:1 + @NotNull(message = "开启状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + + // 备注,示例:我是小备注 + private String remark; + + // 渠道费率,单位:百分比,必填,示例:10 + @NotNull(message = "渠道费率,单位:百分比不能为空") + private Double feeRate; + + // 应用编号,必填,示例:1024 + @NotNull(message = "应用编号不能为空") + private Long appId; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java new file mode 100644 index 0000000..1ad96be --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelCreateReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.pay.controller.admin.channel.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 支付渠道 创建 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelCreateReqVO extends PayChannelBaseVO { + + // 渠道编码,必填,示例:alipay_pc + @NotNull(message = "渠道编码不能为空") + private String code; + + // 渠道配置的 json 字符串 + @NotBlank(message = "渠道配置不能为空") + private String config; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelRespVO.java new file mode 100644 index 0000000..9b9c068 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelRespVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.pay.controller.admin.channel.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 支付渠道 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelRespVO extends PayChannelBaseVO { + + // 商户编号,必填,示例:1024 + private Long id; + + // 创建时间,必填,示例:1024 + private LocalDateTime createTime; + + // 渠道编码,必填,示例:alipay_pc + private String code; + + // 配置,必填 + private String config; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java new file mode 100644 index 0000000..0867eab --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/channel/vo/PayChannelUpdateReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.pay.controller.admin.channel.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 支付渠道 更新 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayChannelUpdateReqVO extends PayChannelBaseVO { + + // 商户编号,必填 + @NotNull(message = "商户编号不能为空") + private Long id; + + // 渠道配置的json字符串 + @NotBlank(message = "渠道配置不能为空") + private String config; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/PayNotifyController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/PayNotifyController.java new file mode 100644 index 0000000..16c76ed --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/PayNotifyController.java @@ -0,0 +1,161 @@ +package com.tashow.cloud.pay.controller.admin.notify; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskRespVO; +import com.tashow.cloud.pay.convert.notify.PayNotifyTaskConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyLogDO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyTaskDO; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import com.tashow.cloud.pay.service.notify.PayNotifyService; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.pay.service.refund.PayRefundService; +import com.tashow.cloud.pay.service.transfer.PayTransferService; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.payapi.enums.ErrorCodeConstants.CHANNEL_NOT_FOUND; + + +// 管理后台 - 回调通知 +@RestController +@RequestMapping("/pay/notify") +@Validated +@Slf4j +public class PayNotifyController { + + @Resource + private PayOrderService orderService; + @Resource + private PayRefundService refundService; + @Resource + private PayTransferService payTransferService; + @Resource + private PayNotifyService notifyService; + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + + @PostMapping(value = "/order/{channelId}") + // 支付渠道的统一【支付】回调 + @PermitAll + public String notifyOrder(@PathVariable("channelId") Long channelId, + @RequestParam(required = false) Map params, + @RequestBody(required = false) String body) { + log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body); + // 1. 校验支付渠道是否存在 + PayClient payClient = channelService.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(CHANNEL_NOT_FOUND); + } + + // 2. 解析通知数据 + PayOrderRespDTO notify = payClient.parseOrderNotify(params, body); + orderService.notifyOrder(channelId, notify); + return "success"; + } + + @PostMapping(value = "/refund/{channelId}") + // 支付渠道的统一【退款】回调 + @PermitAll + public String notifyRefund(@PathVariable("channelId") Long channelId, + @RequestParam(required = false) Map params, + @RequestBody(required = false) String body) { + log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body); + // 1. 校验支付渠道是否存在 + PayClient payClient = channelService.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(CHANNEL_NOT_FOUND); + } + + // 2. 解析通知数据 + PayRefundRespDTO notify = payClient.parseRefundNotify(params, body); + refundService.notifyRefund(channelId, notify); + return "success"; + } + + /** + * 支付渠道的统一【转账】回调 + * @param channelId + * @param params + * @param body + * @return + */ + @PostMapping(value = "/transfer/{channelId}") + @PermitAll + public String notifyTransfer(@PathVariable("channelId") Long channelId, + @RequestParam(required = false) Map params, + @RequestBody(required = false) String body) { + log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body); + // 1. 校验支付渠道是否存在 + PayClient payClient = channelService.getPayClient(channelId); + if (payClient == null) { + log.error("[notifyTransfer][渠道编号({}) 找不到对应的支付客户端]", channelId); + throw exception(CHANNEL_NOT_FOUND); + } + + // 2. 解析通知数据 + PayTransferRespDTO notify = payClient.parseTransferNotify(params, body); + payTransferService.notifyTransfer(channelId, notify); + return "success"; + } + + /** + * 获得回调通知的明细 + * @param id 编号 + * @return + */ + @GetMapping("/get-detail") + @PreAuthorize("@ss.hasPermission('pay:notify:query')") + public CommonResult getNotifyTaskDetail(@RequestParam("id") Long id) { + PayNotifyTaskDO task = notifyService.getNotifyTask(id); + if (task == null) { + return success(null); + } + // 拼接返回 + PayAppDO app = appService.getApp(task.getAppId()); + List logs = notifyService.getNotifyLogList(id); + return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs)); + } + + /** + * 获得回调通知分页 + * @param pageVO + * @return + */ + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('pay:notify:query')") + public CommonResult> getNotifyTaskPage(@Valid PayNotifyTaskPageReqVO pageVO) { + PageResult pageResult = notifyService.getNotifyTaskPage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + // 拼接返回 + Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId)); + return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java new file mode 100644 index 0000000..376266d --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.pay.controller.admin.notify.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 回调通知 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class PayNotifyTaskBaseVO { + + // 应用编号,必填,示例:10636 + private Long appId; + + // 通知类型,必填,示例:2 + private Byte type; + + // 数据编号,必填,示例:6722 + private Long dataId; + + // 通知状态,必填,示例:1 + private Byte status; + + // 商户订单编号,必填,示例:26697 + private String merchantOrderId; + + // 下一次通知时间,必填 + private LocalDateTime nextNotifyTime; + + // 最后一次执行时间,必填 + private LocalDateTime lastExecuteTime; + + // 当前通知次数,必填 + private Byte notifyTimes; + + // 最大可通知次数,必填 + private Byte maxNotifyTimes; + + // 异步通知地址,必填,示例:https://www.iocoder.cn + private String notifyUrl; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java new file mode 100644 index 0000000..f96ca1a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java @@ -0,0 +1,54 @@ + +package com.tashow.cloud.pay.controller.admin.notify.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +// 管理后台 - 回调通知的明细 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO { + + // 任务编号,必填,示例:3380 + private Long id; + + // 创建时间,必填 + private LocalDateTime createTime; + + // 更新时间,必填 + private LocalDateTime updateTime; + + // 应用名称,示例:wx_pay + private String appName; + + // 回调日志列表 + private List logs; + + // 管理后台 - 回调日志 + @Data + public static class Log { + + // 日志编号,必填,示例:8848 + private Long id; + + // 通知状态,必填,示例:1 + private Byte status; + + // 当前通知次数,必填 + private Byte notifyTimes; + + // HTTP 响应结果,必填 + private String response; + + // 创建时间,必填 + private LocalDateTime createTime; + + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java new file mode 100644 index 0000000..bf1408c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.pay.controller.admin.notify.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 回调通知分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayNotifyTaskPageReqVO extends PageParam { + + // 应用编号,示例:10636 + private Long appId; + + // 通知类型,示例:2 + private Integer type; + + // 数据编号,示例:6722 + private Long dataId; + + // 通知状态,示例:1 + private Integer status; + + // 商户订单编号,示例:26697 + private String merchantOrderId; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java new file mode 100644 index 0000000..0d29f82 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.pay.controller.admin.notify.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 回调通知 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO { + + // 任务编号,必填,示例:3380 + private Long id; + + // 创建时间,必填 + private LocalDateTime createTime; + + // 应用名称,示例:wx_pay + private String appName; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/PayOrderController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/PayOrderController.java new file mode 100644 index 0000000..b10161a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/PayOrderController.java @@ -0,0 +1,130 @@ +package com.tashow.cloud.pay.controller.admin.order; + +import cn.hutool.core.collection.CollectionUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.pay.controller.admin.order.vo.*; +import com.tashow.cloud.pay.convert.order.PayOrderConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderExtensionDO; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +// 管理后台 - 支付订单 +@RestController +@RequestMapping("/pay/order") +@Validated +public class PayOrderController { + + @Resource + private PayOrderService orderService; + @Resource + private PayAppService appService; + + @GetMapping("/get") + // 获得支付订单 + // id: 编号,必填,示例:1024 + // sync: 是否同步,示例:true + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult getOrder(@RequestParam("id") Long id, + @RequestParam(value = "sync", required = false) Boolean sync) { + PayOrderDO order = orderService.getOrder(id); + // sync 仅在等待支付 + if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) { + orderService.syncOrderQuietly(order.getId()); + // 重新查询,因为同步后,可能会有变化 + order = orderService.getOrder(id); + } + return success(BeanUtils.toBean(order, PayOrderRespVO.class)); + } + + @GetMapping("/get-detail") + // 获得支付订单详情 + // id: 编号,必填,示例:1024 + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult getOrderDetail(@RequestParam("id") Long id) { + PayOrderDO order = orderService.getOrder(id); + if (order == null) { + return success(null); + } + + // 拼接返回 + PayAppDO app = appService.getApp(order.getAppId()); + PayOrderExtensionDO orderExtension = orderService.getOrderExtension(order.getExtensionId()); + return success(PayOrderConvert.INSTANCE.convert(order, orderExtension, app)); + } + + @PostMapping("/submit") + // 提交支付订单 + public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { +// // 1. 钱包支付事,需要额外传 user_id 和 user_type +// if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { +// Map channelExtras = reqVO.getChannelExtras() == null ? +// Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); +// channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); +// channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); +// reqVO.setChannelExtras(channelExtras); +// } + + // 2. 提交支付 + PayOrderSubmitRespVO respVO = orderService.submitOrder(reqVO, getClientIP()); + return success(respVO); + } + + @GetMapping("/page") + // 获得支付订单分页 + @PreAuthorize("@ss.hasPermission('pay:order:query')") + public CommonResult> getOrderPage(@Valid PayOrderPageReqVO pageVO) { + PageResult pageResult = orderService.getOrderPage(pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 拼接返回 + Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayOrderDO::getAppId)); + return success(PayOrderConvert.INSTANCE.convertPage(pageResult, appMap)); + } + + @GetMapping("/export-excel") + // 导出支付订单 Excel + @PreAuthorize("@ss.hasPermission('pay:order:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = orderService.getOrderList(exportReqVO); + if (CollectionUtil.isEmpty(list)) { + ExcelUtils.write(response, "支付订单.xls", "数据", + PayOrderExcelVO.class, new ArrayList<>()); + return; + } + + // 拼接返回 + Map appMap = appService.getAppMap(convertList(list, PayOrderDO::getAppId)); + List excelList = PayOrderConvert.INSTANCE.convertList(list, appMap); + // 导出 Excel + ExcelUtils.write(response, "支付订单.xls", "数据", PayOrderExcelVO.class, excelList); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderBaseVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderBaseVO.java new file mode 100644 index 0000000..d6fa832 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderBaseVO.java @@ -0,0 +1,89 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 支付订单 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + * + * @author aquan + */ +@Data +public class PayOrderBaseVO { + + // 应用编号,必填,示例:1024 + @NotNull(message = "应用编号不能为空") + private Long appId; + + // 渠道编号,示例:2048 + private Long channelId; + + // 渠道编码,示例:wx_app + private String channelCode; + + // 商户订单编号,必填,示例:888 + @NotNull(message = "商户订单编号不能为空") + private String merchantOrderId; + + // 商品标题,必填,示例:土豆 + @NotNull(message = "商品标题不能为空") + private String subject; + + // 商品描述,必填,示例:我是土豆 + @NotNull(message = "商品描述不能为空") + private String body; + + // 异步通知地址,必填,示例:http://127.0.0.1:48080/pay/notify + @NotNull(message = "异步通知地址不能为空") + private String notifyUrl; + + // 支付金额,单位:分,必填,示例:10 + @NotNull(message = "支付金额,单位:分不能为空") + private Long price; + + // 渠道手续费,单位:百分比,示例:10 + private Double channelFeeRate; + + // 渠道手续金额,单位:分,示例:100 + private Integer channelFeePrice; + + // 支付状态,必填,示例:1 + @NotNull(message = "支付状态不能为空") + private Integer status; + + // 用户 IP,必填,示例:127.0.0.1 + @NotNull(message = "用户 IP不能为空") + private String userIp; + + // 订单失效时间,必填 + @NotNull(message = "订单失效时间不能为空") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime expireTime; + + // 订单支付成功时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime successTime; + + // 支付成功的订单拓展单编号,示例:50 + private Long extensionId; + + // 支付订单号,示例:2048888 + private String no; + + // 退款总金额,单位:分,必填,示例:10 + @NotNull(message = "退款总金额,单位:分不能为空") + private Long refundPrice; + + // 渠道用户编号,示例:2048 + private String channelUserId; + + // 渠道订单号,示例:4096 + private String channelOrderNo; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java new file mode 100644 index 0000000..0e4a36e --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderDetailsRespVO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 支付订单详细信息 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderDetailsRespVO extends PayOrderBaseVO { + + // 支付订单编号,必填,示例:1024 + private Long id; + + // 应用名称,必填,示例:芋道源码 + private String appName; + + // 创建时间,必填 + private LocalDateTime createTime; + + // 更新时间,必填 + private LocalDateTime updateTime; + + /** + * 支付订单扩展 + */ + private PayOrderExtension extension; + + @Data + // 支付订单扩展 + public static class PayOrderExtension { + + // 支付订单号,必填,示例:1024 + private String no; + + // 支付异步通知的内容 + private String channelNotifyData; + + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderExcelVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderExcelVO.java new file mode 100644 index 0000000..69c3df7 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderExcelVO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.excel.excel.core.convert.MoneyConvert; +import com.tashow.cloud.payapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 支付订单 Excel VO + * + * @author aquan + */ +@Data +public class PayOrderExcelVO { + + @ExcelProperty("编号") + private Long id; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @ExcelProperty(value = "支付金额", converter = MoneyConvert.class) + private Integer price; + + @ExcelProperty(value = "退款金额", converter = MoneyConvert.class) + private Integer refundPrice; + + @ExcelProperty(value = "手续金额", converter = MoneyConvert.class) + private Integer channelFeePrice; + + @ExcelProperty("商户单号") + private String merchantOrderId; + + @ExcelProperty(value = "支付单号") + private String no; + + @ExcelProperty("渠道单号") + private String channelOrderNo; + + @ExcelProperty(value = "支付状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.ORDER_STATUS) + private Integer status; + + @ExcelProperty(value = "渠道编号名称", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CHANNEL_CODE) + private String channelCode; + + @ExcelProperty("订单支付成功时间") + private LocalDateTime successTime; + + @ExcelProperty("订单失效时间") + private LocalDateTime expireTime; + + @ExcelProperty(value = "应用名称") + private String appName; + + @ExcelProperty("商品标题") + private String subject; + + @ExcelProperty("商品描述") + private String body; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderExportReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderExportReqVO.java new file mode 100644 index 0000000..37d4e76 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderExportReqVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 移除Swagger相关导入 + +// 管理后台 - 支付订单 Excel 导出 Request VO,参数和 PayOrderPageReqVO 是一致的 +@Data +public class PayOrderExportReqVO { + + // 应用编号,示例:1024 + private Long appId; + + // 渠道编码,示例:wx_app + private String channelCode; + + // 商户订单编号,示例:4096 + private String merchantOrderId; + + // 渠道编号,示例:1888 + private String channelOrderNo; + + // 支付单号,示例:2014888 + private String no; + + // 支付状态,示例:0 + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + // 创建时间 + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java new file mode 100644 index 0000000..318d8f6 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderPageItemRespVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 移除Swagger相关导入 + +// 管理后台 - 支付订单分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderPageItemRespVO extends PayOrderBaseVO { + + // 支付订单编号,必填,示例:1024 + private Long id; + + // 创建时间,必填 + private LocalDateTime createTime; + + // 应用名称,示例:wx_pay + private String appName; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderPageReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderPageReqVO.java new file mode 100644 index 0000000..6db0d2a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderPageReqVO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 移除Swagger相关导入 + +// 管理后台 - 支付订单分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderPageReqVO extends PageParam { + + // 应用编号,示例:1024 + private Long appId; + + // 渠道编码,示例:wx_app + private String channelCode; + + // 商户订单编号,示例:4096 + private String merchantOrderId; + + // 渠道编号,示例:1888 + private String channelOrderNo; + + // 支付单号,示例:2014888 + private String no; + + // 支付状态,示例:0 + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + // 创建时间 + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderRespVO.java new file mode 100644 index 0000000..70fd47c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderRespVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 移除Swagger相关导入 + +// 管理后台 - 支付订单 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayOrderRespVO extends PayOrderBaseVO { + + // 支付订单编号,必填 + private Long id; + + // 创建时间,必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java new file mode 100644 index 0000000..bf38b8a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.hibernate.validator.constraints.URL; + +import java.util.Map; + +// 管理后台 - 支付订单提交 Request VO +@Data +public class PayOrderSubmitReqVO { + + // 支付单编号,必填,示例:1024 + @NotNull(message = "支付单编号不能为空") + private Long id; + + // 支付渠道,必填,示例:wx_pub + @NotEmpty(message = "支付渠道不能为空") + private String channelCode; + + // 支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数 + private Map channelExtras; + + // 展示模式,示例:url // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式 + private String displayMode; + + // 回跳地址 + @URL(message = "回跳地址的格式必须是 URL") + private String returnUrl; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java new file mode 100644 index 0000000..efd013b --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.pay.controller.admin.order.vo; + +import lombok.Data; + +// 管理后台 - 支付订单提交 Response VO +@Data +public class PayOrderSubmitRespVO { + + // 支付状态,必填,示例:10 // 参见 PayOrderStatusEnum 枚举 + private Integer status; + + // 展示模式,必填,示例:url // 参见 PayDisplayModeEnum 枚举 + private String displayMode; + // 展示内容,必填 + private String displayContent; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/PayRefundController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/PayRefundController.java new file mode 100644 index 0000000..e0da2ea --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/PayRefundController.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.pay.controller.admin.refund; + +import cn.hutool.core.collection.CollectionUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.pay.controller.admin.refund.vo.*; +import com.tashow.cloud.pay.convert.refund.PayRefundConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.refund.PayRefundService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +// 管理后台 - 退款订单 +@RestController +@RequestMapping("/pay/refund") +@Validated +public class PayRefundController { + + @Resource + private PayRefundService refundService; + @Resource + private PayAppService appService; + + @GetMapping("/get") + // 获得退款订单 + // id: 编号,必填,示例:1024 + @PreAuthorize("@ss.hasPermission('pay:refund:query')") + public CommonResult getRefund(@RequestParam("id") Long id) { + PayRefundDO refund = refundService.getRefund(id); + if (refund == null) { + return success(new PayRefundDetailsRespVO()); + } + + // 拼接数据 + PayAppDO app = appService.getApp(refund.getAppId()); + return success(PayRefundConvert.INSTANCE.convert(refund, app)); + } + + @GetMapping("/page") + // 获得退款订单分页 + @PreAuthorize("@ss.hasPermission('pay:refund:query')") + public CommonResult> getRefundPage(@Valid PayRefundPageReqVO pageVO) { + PageResult pageResult = refundService.getRefundPage(pageVO); + if (CollectionUtil.isEmpty(pageResult.getList())) { + return success(new PageResult<>(pageResult.getTotal())); + } + + // 处理应用ID数据 + Map appMap = appService.getAppMap(convertList(pageResult.getList(), PayRefundDO::getAppId)); + return success(PayRefundConvert.INSTANCE.convertPage(pageResult, appMap)); + } + + @GetMapping("/export-excel") + // 导出退款订单 Excel + @PreAuthorize("@ss.hasPermission('pay:refund:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportRefundExcel(@Valid PayRefundExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = refundService.getRefundList(exportReqVO); + if (CollectionUtil.isEmpty(list)) { + ExcelUtils.write(response, "退款订单.xls", "数据", + PayRefundExcelVO.class, new ArrayList<>()); + return; + } + + // 拼接返回 + Map appMap = appService.getAppMap(convertList(list, PayRefundDO::getAppId)); + List excelList = PayRefundConvert.INSTANCE.convertList(list, appMap); + // 导出 Excel + ExcelUtils.write(response, "退款订单.xls", "数据", PayRefundExcelVO.class, excelList); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundBaseVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundBaseVO.java new file mode 100644 index 0000000..757fa88 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundBaseVO.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.pay.controller.admin.refund.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; + +/** +* 退款订单 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class PayRefundBaseVO { + + // 外部退款号 - 必填 - 示例: 110 + private String no; + + // 应用编号 - 必填 - 示例: 1024 + private Long appId; + + // 渠道编号 - 必填 - 示例: 2048 + private Long channelId; + + // 渠道编码 - 必填 - 示例: wx_app + private String channelCode; + + // 订单编号 - 必填 - 示例: 1024 + private Long orderId; + + // ========== 商户相关字段 ========== + + // 商户订单编号 - 必填 - 示例: 225 + private String merchantOrderId; + + // 商户退款订单号 - 必填 - 示例: 512 + private String merchantRefundId; + + // 异步通知地址 - 必填 + private String notifyUrl; + + // ========== 退款相关字段 ========== + + // 退款状态 - 必填 - 示例: 0 + private Integer status; + + // 支付金额 - 必填 - 示例: 100 + private Long payPrice; + + // 退款金额,单位分 - 必填 - 示例: 200 + private Long refundPrice; + + // 退款原因 - 必填 - 示例: 我要退了 + private String reason; + + // 用户 IP - 必填 - 示例: 127.0.0.1 + private String userIp; + + // ========== 渠道相关字段 ========== + + // 渠道订单号 - 必填 - 示例: 233 + private String channelOrderNo; + + // 渠道退款单号 - 示例: 2022 + private String channelRefundNo; + + // 退款成功时间 + private LocalDateTime successTime; + + // 调用渠道的错误码 + private String channelErrorCode; + + // 调用渠道的错误提示 + private String channelErrorMsg; + + // 支付渠道的额外参数 + private String channelNotifyData; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java new file mode 100644 index 0000000..ce16ad0 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundDetailsRespVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.pay.controller.admin.refund.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 退款订单详情 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundDetailsRespVO extends PayRefundBaseVO { + + // 支付退款编号 - 必填 + private Long id; + + // 应用名称 - 必填 - 示例: 我是芋艿 + private String appName; + + // 支付订单 - 必填 + private Order order; + + // 创建时间 + private LocalDateTime createTime; + + // 更新时间 + private LocalDateTime updateTime; + + // 管理后台 - 支付订单 + @Data + public static class Order { + + // 商品标题 - 必填 - 示例: 土豆 + private String subject; + + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundExcelVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundExcelVO.java new file mode 100644 index 0000000..53c4431 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundExcelVO.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.pay.controller.admin.refund.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.excel.excel.core.convert.MoneyConvert; +import com.tashow.cloud.payapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 退款订单 Excel VO + * + * @author aquan + */ +@Data +public class PayRefundExcelVO { + + @ExcelProperty("支付退款编号") + private Long id; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @ExcelProperty(value = "支付金额", converter = MoneyConvert.class) + private Integer payPrice; + + @ExcelProperty(value = "退款金额", converter = MoneyConvert.class) + private Integer refundPrice; + + @ExcelProperty("商户退款单号") + private String merchantRefundId; + @ExcelProperty("退款单号") + private String no; + @ExcelProperty("渠道退款单号") + private String channelRefundNo; + + @ExcelProperty("商户支付单号") + private String merchantOrderId; + @ExcelProperty("渠道支付单号") + private String channelOrderNo; + + @ExcelProperty(value = "退款状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.REFUND_STATUS) + private Integer status; + + @ExcelProperty(value = "退款渠道", converter = DictConvert.class) + @DictFormat(DictTypeConstants.CHANNEL_CODE) + private String channelCode; + + @ExcelProperty("成功时间") + private LocalDateTime successTime; + + @ExcelProperty(value = "支付应用") + private String appName; + + @ExcelProperty("退款原因") + private String reason; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundExportReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundExportReqVO.java new file mode 100644 index 0000000..17a9a4b --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundExportReqVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.pay.controller.admin.refund.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 退款订单 Excel 导出 Request VO,参数和 PayRefundPageReqVO 是一致的 +@Data +public class PayRefundExportReqVO { + + // 应用编号 - 示例: 1024 + private Long appId; + + // 渠道编码 - 示例: wx_app + private String channelCode; + + // 商户支付单号 - 示例: 10 + private String merchantOrderId; + + // 商户退款单号 - 示例: 20 + private String merchantRefundId; + + // 渠道支付单号 - 示例: 30 + private String channelOrderNo; + + // 渠道退款单号 - 示例: 40 + private String channelRefundNo; + + // 退款状态 - 示例: 0 + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + // 创建时间 + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java new file mode 100644 index 0000000..1f96e39 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundPageItemRespVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.pay.controller.admin.refund.vo; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 退款订单分页查询 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundPageItemRespVO extends PayRefundBaseVO { + + // 支付订单编号 - 必填 - 示例: 1024 + private Long id; + + // 应用名称 - 必填 - 示例: 我是芋艿 + private String appName; + + // 创建时间 - 必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundPageReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundPageReqVO.java new file mode 100644 index 0000000..4006581 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/refund/vo/PayRefundPageReqVO.java @@ -0,0 +1,47 @@ +package com.tashow.cloud.pay.controller.admin.refund.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 退款订单分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayRefundPageReqVO extends PageParam { + + // 应用编号 - 示例: 1024 + private Long appId; + + // 渠道编码 - 示例: wx_app + private String channelCode; + + // 商户支付单号 - 示例: 10 + private String merchantOrderId; + + // 商户退款单号 - 示例: 20 + private String merchantRefundId; + + // 渠道支付单号 - 示例: 30 + private String channelOrderNo; + + // 渠道退款单号 - 示例: 40 + private String channelRefundNo; + + // 退款状态 - 示例: 0 + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + // 创建时间 + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/PayTransferController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/PayTransferController.java new file mode 100644 index 0000000..149cc9f --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/PayTransferController.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.pay.controller.admin.transfer; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.transfer.vo.*; +import com.tashow.cloud.pay.convert.transfer.PayTransferConvert; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import com.tashow.cloud.pay.service.transfer.PayTransferService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; + + +// 管理后台 - 转账单 +@RestController +@RequestMapping("/pay/transfer") +@Validated +public class PayTransferController { + + @Resource + private PayTransferService payTransferService; + + @PostMapping("/create") + // 创建转账单,发起转账 + @PreAuthorize("@ss.hasPermission('pay:transfer:create')") + public CommonResult createPayTransfer(@Valid @RequestBody PayTransferCreateReqVO reqVO) { + PayTransferDO payTransfer = payTransferService.createTransfer(reqVO, getClientIP()); + return success(new PayTransferCreateRespVO().setId(payTransfer.getId()).setStatus(payTransfer.getStatus())); + } + + @GetMapping("/get") + // 获得转账订单 + @PreAuthorize("@ss.hasPermission('pay:transfer:query')") + public CommonResult getTransfer(@RequestParam("id") Long id) { + return success(PayTransferConvert.INSTANCE.convert(payTransferService.getTransfer(id))); + } + + @GetMapping("/page") + // 获得转账订单分页 + @PreAuthorize("@ss.hasPermission('pay:transfer:query')") + public CommonResult> getTransferPage(@Valid PayTransferPageReqVO pageVO) { + PageResult pageResult = payTransferService.getTransferPage(pageVO); + return success(PayTransferConvert.INSTANCE.convertPage(pageResult)); + } +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java new file mode 100644 index 0000000..28dd862 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferCreateReqVO.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.pay.controller.admin.transfer.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.util.validation.ValidationUtils; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import jakarta.validation.Validator; +import jakarta.validation.constraints.*; +import lombok.Data; + +import java.util.Map; + +import static com.tashow.cloud.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED; +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum.typeOf; + + +// 管理后台 - 发起转账 Request VO +@Data +public class PayTransferCreateReqVO { + + // 应用编号 - 必填 - 示例: 1 + @NotNull(message = "应用编号不能为空") + private Long appId; + + // 商户转账单编号 - 必填 - 示例: 1 + @NotNull(message = "商户转账单编号不能为空") + private String merchantTransferId; + + // 转账类型 - 必填 - 示例: 1 + @NotNull(message = "转账类型不能为空") + @InEnum(PayTransferTypeEnum.class) + private Integer type; + + // 转账渠道 - 必填 - 示例: alipay_pc + @NotEmpty(message = "转账渠道不能为空") + private String channelCode; + + @Min(value = 1, message = "转账金额必须大于零") + @NotNull(message = "转账金额不能为空") + private Integer price; + + // 转账标题 - 必填 - 示例: 示例转账 + @NotEmpty(message = "转账标题不能为空") + private String subject; + + // 收款人姓名 - 示例: test1 + @NotBlank(message = "收款人姓名不能为空", groups = {PayTransferTypeEnum.Alipay.class}) + private String userName; + + // 支付宝登录号 - 示例: test1@sandbox.com + @NotBlank(message = "支付宝登录号不能为空", groups = {PayTransferTypeEnum.Alipay.class}) + private String alipayLogonId; + + // 微信 openId - 示例: oLefc4g5Gxx + @NotBlank(message = "微信 openId 不能为空", groups = {PayTransferTypeEnum.WxPay.class}) + private String openid; + + // 转账渠道的额外参数 + private Map channelExtras; + + public void validate(Validator validator) { + PayTransferTypeEnum transferType = typeOf(type); + switch (transferType) { + case ALIPAY_BALANCE: { + ValidationUtils.validate(validator, this, PayTransferTypeEnum.Alipay.class); + break; + } + case WX_BALANCE: { + ValidationUtils.validate(validator, this, PayTransferTypeEnum.WxPay.class); + break; + } + default: { + throw new UnsupportedOperationException("待实现"); + } + } + } + + @AssertTrue(message = "转账类型和转账渠道不匹配") + public boolean isValidChannelCode() { + PayTransferTypeEnum transferType = typeOf(type); + switch (transferType) { + case ALIPAY_BALANCE: { + return PayTypeEnum.isAlipay(channelCode); + } + case WX_BALANCE: + case BANK_CARD: + case WALLET_BALANCE: { + throw exception(NOT_IMPLEMENTED); + } + } + return Boolean.FALSE; + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java new file mode 100644 index 0000000..e26aa66 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferCreateRespVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.pay.controller.admin.transfer.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +// 管理后台 - 发起转账 Response VO +@Data +public class PayTransferCreateRespVO { + + // 转账单编号 - 必填 - 示例: 1 + private Long id; + + // 转账状态 - 必填 - 示例: 1 (参见 PayTransferStatusEnum 枚举) + private Integer status; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferPageItemRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferPageItemRespVO.java new file mode 100644 index 0000000..e56922c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferPageItemRespVO.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.pay.controller.admin.transfer.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author jason + */ +// 管理后台 - 转账单分页项 Response VO +@Data +public class PayTransferPageItemRespVO { + + // 编号 - 必填 - 示例: 2931 + private Long id; + + // 转账单号 - 必填 + private String no; + + // 应用编号 - 必填 - 示例: 12831 + private Long appId; + + // 转账渠道编号 - 必填 - 示例: 24833 + private Long channelId; + + // 转账渠道编码 - 必填 + private String channelCode; + + // 商户转账单编号 - 必填 - 示例: 17481 + private String merchantTransferId; + + // 类型 - 必填 - 示例: 2 + private Integer type; + + // 转账状态 - 必填 - 示例: 2 + private Integer status; + + // 转账成功时间 + private LocalDateTime successTime; + + // 转账金额 - 必填 - 示例: 964 + private Integer price; + + // 转账标题 - 必填 + private String subject; + + // 收款人姓名 - 示例: 王五 + private String userName; + + // 支付宝登录号 - 示例: 29245 + private String alipayLogonId; + + // 微信 openId - 示例: 26589 + private String openid; + + // 渠道转账单号 + private String channelTransferNo; + + // 创建时间 - 必填 + private LocalDateTime createTime; +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java new file mode 100644 index 0000000..a9f41e5 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferPageReqVO.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.pay.controller.admin.transfer.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 转账单分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PayTransferPageReqVO extends PageParam { + + // 转账单号 + private String no; + + // 应用编号 - 示例: 12831 + private Long appId; + + // 渠道编码 - 示例: wx_app + private String channelCode; + + // 商户转账单编号 - 示例: 17481 + private String merchantTransferId; + + // 类型 - 示例: 2 + private Integer type; + + // 转账状态 - 示例: 2 + private Integer status; + + // 收款人姓名 - 示例: 王五 + private String userName; + + // 渠道转账单号 + private String channelTransferNo; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferRespVO.java new file mode 100644 index 0000000..b3e2427 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/admin/transfer/vo/PayTransferRespVO.java @@ -0,0 +1,79 @@ +package com.tashow.cloud.pay.controller.admin.transfer.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +// 管理后台 - 转账单 Response VO +@Data +public class PayTransferRespVO { + + // 编号 - 必填 - 示例: 2931 + private Long id; + + // 转账单号 - 必填 + private String no; + + // 应用编号 - 必填 - 示例: 12831 + private Long appId; + + // 转账渠道编号 - 必填 - 示例: 24833 + private Long channelId; + + // 转账渠道编码 - 必填 + private String channelCode; + + // 商户转账单编号 - 必填 - 示例: 17481 + private String merchantTransferId; + + // 类型 - 必填 - 示例: 2 + private Integer type; + + // 转账状态 - 必填 - 示例: 2 + private Integer status; + + // 转账成功时间 + private LocalDateTime successTime; + + // 转账金额 - 必填 - 示例: 964 + private Integer price; + + // 转账标题 - 必填 + private String subject; + + // 收款人姓名 - 示例: 王五 + private String userName; + + // 支付宝登录号 - 示例: 29245 + private String alipayLogonId; + + // 微信 openId - 示例: 26589 + private String openid; + + // 异步通知商户地址 - 必填 - 示例: https://www.iocoder.cn + private String notifyUrl; + + // 用户 IP - 必填 + private String userIp; + + // 渠道的额外参数 + private Map channelExtras; + + // 渠道转账单号 + private String channelTransferNo; + + // 调用渠道的错误码 + private String channelErrorCode; + + // 调用渠道的错误提示 + private String channelErrorMsg; + + // 渠道的同步/异步通知的内容 + private String channelNotifyData; + + // 创建时间 - 必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/channel/AppPayChannelController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/channel/AppPayChannelController.java new file mode 100644 index 0000000..afea122 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/channel/AppPayChannelController.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.pay.controller.app.channel; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import jakarta.annotation.Resource; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Set; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + + +/** + * 用户 App - 支付渠道 + */ +@RestController +@RequestMapping("/pay/channel") +@Validated +public class AppPayChannelController { + + @Resource + private PayChannelService channelService; + + /** + * 获得指定应用的开启的支付渠道编码列表 + * @param appId 应用编号 + * @return + */ + @GetMapping("/get-enable-code-list") + public CommonResult> getEnableChannelCodeList(@RequestParam("appId") Long appId) { + List channels = channelService.getEnableChannelList(appId); + return success(convertSet(channels, PayChannelDO::getCode)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/AppPayOrderController.http b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/AppPayOrderController.http new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/AppPayOrderController.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/AppPayOrderController.java new file mode 100644 index 0000000..553ac6e --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/AppPayOrderController.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.pay.controller.app.order; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderRespVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +import com.tashow.cloud.pay.controller.app.order.vo.AppPayOrderSubmitReqVO; +import com.tashow.cloud.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; +import com.tashow.cloud.pay.convert.order.PayOrderConvert; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; + + +/** + * 用户 APP - 支付订单 + */ +@RestController +@RequestMapping("/pay/order") +@Validated +@Slf4j +public class AppPayOrderController { + + @Resource + private PayOrderService payOrderService; + + /** + * 获得支付订单 + * @param id 编号 + * @param sync 是否同步 + * @return + */ + @GetMapping("/get") + public CommonResult getOrder(@RequestParam("id") Long id, + @RequestParam(value = "sync", required = false) Boolean sync) { + PayOrderDO order = payOrderService.getOrder(id); + // sync 仅在等待支付 + if (Boolean.TRUE.equals(sync) && PayOrderStatusEnum.isWaiting(order.getStatus())) { + payOrderService.syncOrderQuietly(order.getId()); + // 重新查询,因为同步后,可能会有变化 + order = payOrderService.getOrder(id); + } + return success(BeanUtils.toBean(order, PayOrderRespVO.class)); + } + + /** + * 提交支付订单 + * @param reqVO + * @return + */ + @PostMapping("/submit") + public CommonResult submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { + // 1. 钱包支付事,需要额外传 user_id 和 user_type +// if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { +// Map channelExtras = reqVO.getChannelExtras() == null ? +// Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); +// channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); +// channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); +// reqVO.setChannelExtras(channelExtras); +// } + + // 2. 提交支付 + PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP()); + return success(PayOrderConvert.INSTANCE.convert3(respVO)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java new file mode 100644 index 0000000..bb46908 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java @@ -0,0 +1,9 @@ +package com.tashow.cloud.pay.controller.app.order.vo; + +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import lombok.Data; + +//用户 APP - 支付订单提交 Request VO +@Data +public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO { +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java new file mode 100644 index 0000000..ac320ca --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java @@ -0,0 +1,10 @@ +package com.tashow.cloud.pay.controller.app.order.vo; + +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +import lombok.Data; + +//用户 APP - 支付订单提交 Response VO +@Data +public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO { + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/package-info.java new file mode 100644 index 0000000..3b9527b --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package com.tashow.cloud.pay.controller; diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/app/PayAppConvert.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/app/PayAppConvert.java new file mode 100644 index 0000000..b8a0ebf --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/app/PayAppConvert.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.pay.convert.app; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppCreateReqVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppPageItemRespVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppRespVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 支付应用信息 Convert + * + * @author 芋艿 + */ +@Mapper +public interface PayAppConvert { + + PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class); + + PayAppPageItemRespVO pageConvert (PayAppDO bean); + + PayAppDO convert(PayAppCreateReqVO bean); + + PayAppDO convert(PayAppUpdateReqVO bean); + + PayAppRespVO convert(PayAppDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + default PageResult convertPage(PageResult pageResult, List channels) { + PageResult voPageResult = convertPage(pageResult); + // 处理 channel 关系 + Map> appIdChannelMap = CollectionUtils.convertMultiMap2(channels, PayChannelDO::getAppId, PayChannelDO::getCode); + voPageResult.getList().forEach(app -> app.setChannelCodes(appIdChannelMap.get(app.getId()))); + return voPageResult; + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/channel/PayChannelConvert.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/channel/PayChannelConvert.java new file mode 100644 index 0000000..85e82a4 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/channel/PayChannelConvert.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.pay.convert.channel; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelCreateReqVO; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelRespVO; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayChannelConvert { + + PayChannelConvert INSTANCE = Mappers.getMapper(PayChannelConvert.class); + + @Mapping(target = "config",ignore = true) + PayChannelDO convert(PayChannelCreateReqVO bean); + + @Mapping(target = "config",ignore = true) + PayChannelDO convert(PayChannelUpdateReqVO bean); + + @Mapping(target = "config",expression = "java(com.tashow.cloud.common.util.json.JsonUtils.toJsonString(bean.getConfig()))") + PayChannelRespVO convert(PayChannelDO bean); + + PageResult convertPage(PageResult page); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/notify/PayNotifyTaskConvert.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/notify/PayNotifyTaskConvert.java new file mode 100644 index 0000000..cb53605 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/notify/PayNotifyTaskConvert.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.pay.convert.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.MapUtils; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskRespVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyLogDO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyTaskDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +/** + * 支付通知 Convert + * + * @author 芋道源码 + */ +@Mapper +public interface PayNotifyTaskConvert { + + PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class); + + PayNotifyTaskRespVO convert(PayNotifyTaskDO bean); + + default PageResult convertPage(PageResult page, Map appMap){ + PageResult result = convertPage(page); + result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); + return result; + } + PageResult convertPage(PageResult page); + + default PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, PayAppDO app, List logs) { + PayNotifyTaskDetailRespVO respVO = convert(task, logs); + if (app != null) { + respVO.setAppName(app.getName()); + } + return respVO; + } + PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, List logs); +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/order/PayOrderConvert.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/order/PayOrderConvert.java new file mode 100644 index 0000000..dc7ff9d --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/order/PayOrderConvert.java @@ -0,0 +1,74 @@ +package com.tashow.cloud.pay.convert.order; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.collection.MapUtils; +import com.tashow.cloud.pay.controller.admin.order.vo.*; +import com.tashow.cloud.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderExtensionDO; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +/** + * 支付订单 Convert + * + * @author aquan + */ +@Mapper +public interface PayOrderConvert { + + PayOrderConvert INSTANCE = Mappers.getMapper(PayOrderConvert.class); + + PayOrderRespVO convert(PayOrderDO bean); + + PayOrderRespDTO convert2(PayOrderDO order); + + default PayOrderDetailsRespVO convert(PayOrderDO order, PayOrderExtensionDO orderExtension, PayAppDO app) { + PayOrderDetailsRespVO respVO = convertDetail(order); + respVO.setExtension(convert(orderExtension)); + if (app != null) { + respVO.setAppName(app.getName()); + } + return respVO; + } + PayOrderDetailsRespVO convertDetail(PayOrderDO bean); + PayOrderDetailsRespVO.PayOrderExtension convert(PayOrderExtensionDO bean); + + default PageResult convertPage(PageResult page, Map appMap) { + PageResult result = convertPage(page); + result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); + return result; + } + PageResult convertPage(PageResult page); + + default List convertList(List list, Map appMap) { + return CollectionUtils.convertList(list, order -> { + PayOrderExcelVO excelVO = convertExcel(order); + MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName())); + return excelVO; + }); + } + PayOrderExcelVO convertExcel(PayOrderDO bean); + + PayOrderDO convert(PayOrderCreateReqDTO bean); + + @Mapping(target = "id", ignore = true) + PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp); + + PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO, String userIp); + + @Mapping(source = "order.status", target = "status") + PayOrderSubmitRespVO convert(PayOrderDO order, PayOrderRespDTO respDTO); + + AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/package-info.java new file mode 100644 index 0000000..303126f --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 POJO 类的实体转换 + * + * 目前使用 MapStruct 框架 + */ +package com.tashow.cloud.pay.convert; diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/refund/PayRefundConvert.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/refund/PayRefundConvert.java new file mode 100644 index 0000000..62e432a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/refund/PayRefundConvert.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.pay.convert.refund; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.collection.MapUtils; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundDetailsRespVO; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundExcelVO; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundPageItemRespVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface PayRefundConvert { + + PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class); + + + default PayRefundDetailsRespVO convert(PayRefundDO refund, PayAppDO app) { + PayRefundDetailsRespVO respVO = convert(refund); + if (app != null) { + respVO.setAppName(app.getName()); + } + return respVO; + } + PayRefundDetailsRespVO convert(PayRefundDO bean); + PayRefundDetailsRespVO.Order convert(PayOrderDO bean); + + default PageResult convertPage(PageResult page, Map appMap) { + PageResult result = convertPage(page); + result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName()))); + return result; + } + PageResult convertPage(PageResult page); + + PayRefundDO convert(PayRefundCreateReqDTO bean); + + PayRefundRespDTO convert02(PayRefundDO bean); + + default List convertList(List list, Map appMap) { + return CollectionUtils.convertList(list, order -> { + PayRefundExcelVO excelVO = convertExcel(order); + MapUtils.findAndThen(appMap, order.getAppId(), app -> excelVO.setAppName(app.getName())); + return excelVO; + }); + } + PayRefundExcelVO convertExcel(PayRefundDO bean); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/transfer/PayTransferConvert.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/transfer/PayTransferConvert.java new file mode 100644 index 0000000..2d59963 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/convert/transfer/PayTransferConvert.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.pay.convert.transfer; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferPageItemRespVO; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferRespVO; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import com.tashow.cloud.payapi.api.transfer.dto.PayTransferCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferUnifiedReqDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface PayTransferConvert { + + PayTransferConvert INSTANCE = Mappers.getMapper(PayTransferConvert.class); + + PayTransferDO convert(PayTransferCreateReqDTO dto); + + PayTransferUnifiedReqDTO convert2(PayTransferDO dto); + + PayTransferCreateReqDTO convert(PayTransferCreateReqVO vo); + + PayTransferRespVO convert(PayTransferDO bean); + + PageResult convertPage(PageResult pageResult); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/app/PayAppDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/app/PayAppDO.java new file mode 100644 index 0000000..38df323 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/app/PayAppDO.java @@ -0,0 +1,63 @@ +package com.tashow.cloud.pay.dal.dataobject.app; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.*; + +/** + * 支付应用 DO + * 一个商户下,可能会有多个支付应用。例如说,京东有京东商城、京东到家等等 + * 不过一般来说,一个商户,只有一个应用哈~ + * + * 即 PayMerchantDO : PayAppDO = 1 : n + * + */ +@TableName("pay_app") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayAppDO extends BaseDO { + + /** + * 应用编号,数据库自增 + */ + @TableId + private Long id; + /** + * 应用标识 + */ + private String appKey; + /** + * 应用名 + */ + private String name; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 支付结果的回调地址 + */ + private String orderNotifyUrl; + /** + * 退款结果的回调地址 + */ + private String refundNotifyUrl; + + /** + * 转账结果的回调地址 + */ + private String transferNotifyUrl; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/channel/PayChannelDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/channel/PayChannelDO.java new file mode 100644 index 0000000..e0d9413 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/channel/PayChannelDO.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.pay.dal.dataobject.channel; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import lombok.*; + +/** + * 支付渠道 DO + * 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等 + * + * 即 PayAppDO : PayChannelDO = 1 : n + * + */ +@TableName(value = "pay_channel", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayChannelDO extends TenantBaseDO { + + /** + * 渠道编号,数据库自增 + */ + private Long id; + /** + * 渠道编码 + * + * 枚举 {@link PayTypeEnum} + */ + private String code; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 渠道费率,单位:百分比 + */ + private Double feeRate; + /** + * 备注 + */ + private String remark; + + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 支付渠道配置 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private PayClientConfig config; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/notify/PayNotifyLogDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/notify/PayNotifyLogDO.java new file mode 100644 index 0000000..8a2d80c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/notify/PayNotifyLogDO.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.pay.dal.dataobject.notify; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyStatusEnum; +import lombok.*; + +/** + * 商户支付、退款等的通知 Log + * 每次通知时,都会在该表中,记录一次 Log,方便排查问题 + * + */ +@TableName("pay_notify_log") +@KeySequence("pay_notify_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayNotifyLogDO extends BaseDO { + + /** + * 日志编号,自增 + */ + private Long id; + /** + * 通知任务编号 + * + * 关联 {@link PayNotifyTaskDO#getId()} + */ + private Long taskId; + /** + * 第几次被通知 + * + * 对应到 {@link PayNotifyTaskDO#getNotifyTimes()} + */ + private Integer notifyTimes; + /** + * HTTP 响应结果 + */ + private String response; + /** + * 支付通知状态 + * + * 枚举 {@link PayNotifyStatusEnum} + */ + private Integer status; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/notify/PayNotifyTaskDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/notify/PayNotifyTaskDO.java new file mode 100644 index 0000000..313fa46 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/notify/PayNotifyTaskDO.java @@ -0,0 +1,97 @@ +package com.tashow.cloud.pay.dal.dataobject.notify; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyStatusEnum; +import com.tashow.cloud.payapi.enums.notify.PayNotifyTypeEnum; +import com.tashow.cloud.tenant.core.db.TenantBaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 支付通知 + * 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。 + * + */ +@TableName("pay_notify_task") +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class PayNotifyTaskDO extends TenantBaseDO { + + /** + * 通知频率,单位为秒。 + * + * 算上首次的通知,实际是一共 1 + 8 = 9 次。 + */ + public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{ + 15, 15, 30, 180, + 1800, 1800, 1800, 3600 + }; + + /** + * 编号,自增 + */ + @TableId + private Long id; + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 通知类型 + * + * 枚举 {@link PayNotifyTypeEnum} + */ + private Integer type; + /** + * 数据编号,根据不同 type 进行关联: + * + * 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()} + * 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()} + */ + private Long dataId; + /** + * 商户订单编号 + */ + private String merchantOrderId; + /** + * 商户转账单编号 + */ + private String merchantTransferId; + /** + * 通知状态 + * + * 枚举 {@link PayNotifyStatusEnum} + */ + private Integer status; + /** + * 下一次通知时间 + */ + private LocalDateTime nextNotifyTime; + /** + * 最后一次执行时间 + */ + private LocalDateTime lastExecuteTime; + /** + * 当前通知次数 + */ + private Integer notifyTimes; + /** + * 最大可通知次数 + */ + private Integer maxNotifyTimes; + /** + * 通知地址 + */ + private String notifyUrl; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/order/PayOrderDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/order/PayOrderDO.java new file mode 100644 index 0000000..62d52c1 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/order/PayOrderDO.java @@ -0,0 +1,135 @@ +package com.tashow.cloud.pay.dal.dataobject.order; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 支付订单 DO + * + */ +@TableName("pay_order") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayOrderDO extends BaseDO { + + /** + * 订单编号,数据库自增 + */ + private Long id; + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 渠道编码 + * + * 枚举 {@link PayTypeEnum} + */ + private String channelCode; + + // ========== 商户相关字段 ========== + + /** + * 商户订单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantOrderId; + /** + * 商品标题 + */ + private String subject; + /** + * 商品描述信息 + */ + private String body; + /** + * 异步通知地址 + */ + private String notifyUrl; + + // ========== 订单相关字段 ========== + + /** + * 支付金额,单位:分 + */ + private Integer price; + /** + * 渠道手续费,单位:百分比 + * + * 冗余 {@link PayChannelDO#getFeeRate()} + */ + private Double channelFeeRate; + /** + * 渠道手续金额,单位:分 + */ + private Integer channelFeePrice; + /** + * 支付状态 + * + * 枚举 {@link PayOrderStatusEnum} + */ + private Integer status; + /** + * 用户 IP + */ + private String userIp; + /** + * 订单失效时间 + */ + private LocalDateTime expireTime; + /** + * 订单支付成功时间 + */ + private LocalDateTime successTime; + /** + * 支付成功的订单拓展单编号 + * + * 关联 {@link cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO#getId()} + */ + private Long extensionId; + /** + * 支付成功的外部订单号 + * + * 关联 {@link cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO#getNo()} + */ + private String no; + + // ========== 退款相关字段 ========== + /** + * 退款总金额,单位:分 + */ + private Integer refundPrice; + + // ========== 渠道相关字段 ========== + /** + * 渠道用户编号 + * + * 例如说,微信 openid、支付宝账号 + */ + private String channelUserId; + /** + * 渠道订单号 + */ + private String channelOrderNo; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/order/PayOrderExtensionDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/order/PayOrderExtensionDO.java new file mode 100644 index 0000000..667f57c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/order/PayOrderExtensionDO.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.pay.dal.dataobject.order; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import lombok.*; + +import java.util.Map; + +/** + * 支付订单拓展 DO + * + * 每次调用支付渠道,都会生成一条对应记录 + * + * @author 芋道源码 + */ +@TableName(value = "pay_order_extension",autoResultMap = true) +@KeySequence("pay_order_extension_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayOrderExtensionDO extends BaseDO { + + /** + * 订单拓展编号,数据库自增 + */ + private Long id; + /** + * 外部订单号,根据规则生成 + * + * 调用支付渠道时,使用该字段作为对接的订单号: + * 1. 微信支付:对应 JSAPI 支付 的 out_trade_no 字段 + * 2. 支付宝支付:对应 电脑网站支付 的 out_trade_no 字段 + * + * 例如说,P202110132239124200055 + */ + private String no; + /** + * 订单号 + * + * 关联 {@link PayOrderDO#getId()} + */ + private Long orderId; + /** + * 渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 渠道编码 + */ + private String channelCode; + /** + * 用户 IP + */ + private String userIp; + /** + * 支付状态 + * + * 枚举 {@link PayOrderStatusEnum} + */ + private Integer status; + /** + * 支付渠道的额外参数 + * + * 参见 参数说明 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map channelExtras; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道报错时,错误信息 + */ + private String channelErrorMsg; + + /** + * 支付渠道的同步/异步通知的内容 + * + * 对应 {@link PayOrderRespDTO#getRawData()} + */ + private String channelNotifyData; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/refund/PayRefundDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/refund/PayRefundDO.java new file mode 100644 index 0000000..9805a4f --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/refund/PayRefundDO.java @@ -0,0 +1,159 @@ +package com.tashow.cloud.pay.dal.dataobject.refund; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.payapi.enums.refund.PayRefundStatusEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 支付退款单 DO + * 一个支付订单,可以拥有多个支付退款单 + * + * 即 PayOrderDO : PayRefundDO = 1 : n + * + * @author 芋道源码 + */ +@TableName("pay_refund") +@KeySequence("pay_refund_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundDO extends BaseDO { + + /** + * 退款单编号,数据库自增 + */ + @TableId + private Long id; + /** + * 外部退款号,根据规则生成 + * + * 调用支付渠道时,使用该字段作为对接的退款号: + * 1. 微信退款:对应 申请退款 的 out_refund_no 字段 + * 2. 支付宝退款:对应 的 out_request_no 字段 + */ + private String no; + + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + /** + * 渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + /** + * 渠道编码 + * + * 枚举 {@link PayTypeEnum} + */ + private String channelCode; + /** + * 订单编号 + * + * 关联 {@link PayOrderDO#getId()} + */ + private Long orderId; + /** + * 支付订单编号 + * + * 冗余 {@link PayOrderDO#getNo()} + */ + private String orderNo; + + // ========== 商户相关字段 ========== + /** + * 商户订单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantOrderId; + /** + * 商户退款订单号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantRefundId; + /** + * 异步通知地址 + */ + private String notifyUrl; + + // ========== 退款相关字段 ========== + /** + * 退款状态 + * + * 枚举 {@link PayRefundStatusEnum} + */ + private Integer status; + + /** + * 支付金额,单位:分 + */ + private Integer payPrice; + /** + * 退款金额,单位:分 + */ + private Integer refundPrice; + + /** + * 退款原因 + */ + private String reason; + + /** + * 用户 IP + */ + private String userIp; + + // ========== 渠道相关字段 ========== + /** + * 渠道订单号 + * + * 冗余 {@link PayOrderDO#getChannelOrderNo()} + */ + private String channelOrderNo; + /** + * 渠道退款单号 + * + * 1. 微信退款:对应 申请退款 的 refund_id 字段 + * 2. 支付宝退款:没有字段 + */ + private String channelRefundNo; + /** + * 退款成功时间 + */ + private LocalDateTime successTime; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + + /** + * 支付渠道的同步/异步通知的内容 + * + * 对应 {@link PayRefundRespDTO#getRawData()} + */ + private String channelNotifyData; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/transfer/PayTransferDO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/transfer/PayTransferDO.java new file mode 100644 index 0000000..395a5b0 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/dataobject/transfer/PayTransferDO.java @@ -0,0 +1,157 @@ +package com.tashow.cloud.pay.dal.dataobject.transfer; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferStatusRespEnum; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Map; + +// TODO 芋艿:需要详细 review +/** + * 转账单 DO + * + * @author jason + */ +@TableName(value ="pay_transfer", autoResultMap = true) +@KeySequence("pay_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class PayTransferDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 转账单号 + * + */ + private String no; + + /** + * 应用编号 + * + * 关联 {@link PayAppDO#getId()} + */ + private Long appId; + + /** + * 转账渠道编号 + * + * 关联 {@link PayChannelDO#getId()} + */ + private Long channelId; + + /** + * 转账渠道编码 + * + * 枚举 {@link PayTypeEnum} + */ + private String channelCode; + + // ========== 商户相关字段 ========== + /** + * 商户转账单编号 + * + * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 + */ + private String merchantTransferId; + + // ========== 转账相关字段 ========== + + /** + * 类型 + * + * 枚举 {@link PayTransferTypeEnum} + */ + private Integer type; + + /** + * 转账标题 + */ + private String subject; + + /** + * 转账金额,单位:分 + */ + private Integer price; + + /** + * 收款人姓名 + */ + private String userName; + + /** + * 转账状态 + * + * 枚举 {@link PayTransferStatusRespEnum} + */ + private Integer status; + + /** + * 订单转账成功时间 + */ + private LocalDateTime successTime; + + // ========== 支付宝转账相关字段 ========== + /** + * 支付宝登录号 + */ + private String alipayLogonId; + + + // ========== 微信转账相关字段 ========== + /** + * 微信 openId + */ + private String openid; + + // ========== 其它字段 ========== + + /** + * 异步通知地址 + */ + private String notifyUrl; + + /** + * 用户 IP + */ + private String userIp; + + /** + * 渠道的额外参数 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map channelExtras; + + /** + * 渠道转账单号 + */ + private String channelTransferNo; + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + + /** + * 渠道的同步/异步通知的内容 + * + */ + private String channelNotifyData; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/app/PayAppMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/app/PayAppMapper.java new file mode 100644 index 0000000..9ddf530 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/app/PayAppMapper.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.pay.dal.mysql.app; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PayAppMapper extends BaseMapperX { + + default PageResult selectPage(PayAppPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(PayAppDO::getName, reqVO.getName()) + .likeIfPresent(PayAppDO::getAppKey, reqVO.getAppKey()) + .eqIfPresent(PayAppDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayAppDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayAppDO::getId)); + } + + default PayAppDO selectByAppKey(String appKey) { + return selectOne(PayAppDO::getAppKey, appKey); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/channel/PayChannelMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/channel/PayChannelMapper.java new file mode 100644 index 0000000..79f116b --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/channel/PayChannelMapper.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.pay.dal.mysql.channel; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface PayChannelMapper extends BaseMapperX { + + default PayChannelDO selectByAppIdAndCode(Long appId, String code) { + return selectOne(PayChannelDO::getAppId, appId, PayChannelDO::getCode, code); + } + + default List selectListByAppIds(Collection appIds){ + return selectList(PayChannelDO::getAppId, appIds); + } + + default List selectListByAppId(Long appId, Integer status) { + return selectList(new LambdaQueryWrapperX() + .eq(PayChannelDO::getAppId, appId) + .eq(PayChannelDO::getStatus, status)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/notify/PayNotifyLogMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/notify/PayNotifyLogMapper.java new file mode 100644 index 0000000..765a7cc --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/notify/PayNotifyLogMapper.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.pay.dal.mysql.notify; + +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyLogDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayNotifyLogMapper extends BaseMapperX { + + default List selectListByTaskId(Long taskId) { + return selectList(PayNotifyLogDO::getTaskId, taskId); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/notify/PayNotifyTaskMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/notify/PayNotifyTaskMapper.java new file mode 100644 index 0000000..6b746cc --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/notify/PayNotifyTaskMapper.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.pay.dal.mysql.notify; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyTaskDO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyStatusEnum; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface PayNotifyTaskMapper extends BaseMapperX { + + /** + * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件: + * + * 1. status 非成功 + * 2. nextNotifyTime 小于当前时间 + * + * @return PayTransactionNotifyTaskDO 数组 + */ + default List selectListByNotify() { + return selectList(new LambdaQueryWrapper() + .in(PayNotifyTaskDO::getStatus, PayNotifyStatusEnum.WAITING.getStatus(), + PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(), PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()) + .le(PayNotifyTaskDO::getNextNotifyTime, LocalDateTime.now())); + } + + default PageResult selectPage(PayNotifyTaskPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayNotifyTaskDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayNotifyTaskDO::getType, reqVO.getType()) + .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId()) + .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus()) + .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayNotifyTaskDO::getId)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/order/PayOrderExtensionMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/order/PayOrderExtensionMapper.java new file mode 100644 index 0000000..57342b0 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/order/PayOrderExtensionMapper.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.pay.dal.mysql.order; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderExtensionDO; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface PayOrderExtensionMapper extends BaseMapperX { + + default PayOrderExtensionDO selectByNo(String no) { + return selectOne(PayOrderExtensionDO::getNo, no); + } + + default int updateByIdAndStatus(Long id, Integer status, PayOrderExtensionDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayOrderExtensionDO::getId, id).eq(PayOrderExtensionDO::getStatus, status)); + } + + default List selectListByOrderId(Long orderId) { + return selectList(PayOrderExtensionDO::getOrderId, orderId); + } + + default List selectListByOrderIdAndStatus(Long orderId, Integer status) { + return selectList(PayOrderExtensionDO::getOrderId, orderId, + PayOrderExtensionDO::getStatus, status); + } + + default List selectListByStatusAndCreateTimeGe(Integer status, LocalDateTime minCreateTime) { + return selectList(new LambdaQueryWrapper() + .eq(PayOrderExtensionDO::getStatus, status) + .ge(PayOrderExtensionDO::getCreateTime, minCreateTime)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/order/PayOrderMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/order/PayOrderMapper.java new file mode 100644 index 0000000..113aa71 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/order/PayOrderMapper.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.pay.dal.mysql.order; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderExportReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface PayOrderMapper extends BaseMapperX { + + default PageResult selectPage(PayOrderPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayOrderDO::getId)); + } + + default List selectList(PayOrderExportReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(PayOrderDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayOrderDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayOrderDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayOrderDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(PayOrderDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayOrderDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayOrderDO::getId)); + } + + default Long selectCountByAppId(Long appId) { + return selectCount(PayOrderDO::getAppId, appId); + } + + default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) { + return selectOne(PayOrderDO::getAppId, appId, + PayOrderDO::getMerchantOrderId, merchantOrderId); + } + + default int updateByIdAndStatus(Long id, Integer status, PayOrderDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayOrderDO::getId, id).eq(PayOrderDO::getStatus, status)); + } + + default List selectListByStatusAndExpireTimeLt(Integer status, LocalDateTime expireTime) { + return selectList(new LambdaQueryWrapper() + .eq(PayOrderDO::getStatus, status) + .lt(PayOrderDO::getExpireTime, expireTime)); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/refund/PayRefundMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/refund/PayRefundMapper.java new file mode 100644 index 0000000..1cce36d --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/refund/PayRefundMapper.java @@ -0,0 +1,78 @@ +package com.tashow.cloud.pay.dal.mysql.refund; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundExportReqVO; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayRefundMapper extends BaseMapperX { + + default Long selectCountByAppId(Long appId) { + return selectCount(PayRefundDO::getAppId, appId); + } + + default PayRefundDO selectByAppIdAndMerchantRefundId(Long appId, String merchantRefundId) { + return selectOne(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getMerchantRefundId, merchantRefundId)); + } + + default Long selectCountByAppIdAndOrderId(Long appId, Long orderId, Integer status) { + return selectCount(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getOrderId, orderId) + .eq(PayRefundDO::getStatus, status)); + } + + default PayRefundDO selectByAppIdAndNo(Long appId, String no) { + return selectOne(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getNo, no)); + } + + default PayRefundDO selectByNo(String no) { + return selectOne(PayRefundDO::getNo, no); + } + + default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status)); + } + + default PageResult selectPage(PayRefundPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId()) + .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo()) + .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayRefundDO::getId)); + } + + default List selectList(PayRefundExportReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .eqIfPresent(PayRefundDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayRefundDO::getChannelCode, reqVO.getChannelCode()) + .likeIfPresent(PayRefundDO::getMerchantOrderId, reqVO.getMerchantOrderId()) + .likeIfPresent(PayRefundDO::getMerchantRefundId, reqVO.getMerchantRefundId()) + .likeIfPresent(PayRefundDO::getChannelOrderNo, reqVO.getChannelOrderNo()) + .likeIfPresent(PayRefundDO::getChannelRefundNo, reqVO.getChannelRefundNo()) + .eqIfPresent(PayRefundDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(PayRefundDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayRefundDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(PayRefundDO::getStatus, status); + } +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/transfer/PayTransferMapper.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/transfer/PayTransferMapper.java new file mode 100644 index 0000000..582afcc --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/mysql/transfer/PayTransferMapper.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.pay.dal.mysql.transfer; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface PayTransferMapper extends BaseMapperX { + + default int updateByIdAndStatus(Long id, List status, PayTransferDO updateObj) { + return update(updateObj, new LambdaQueryWrapper() + .eq(PayTransferDO::getId, id).in(PayTransferDO::getStatus, status)); + } + + default PayTransferDO selectByAppIdAndMerchantTransferId(Long appId, String merchantTransferId){ + return selectOne(PayTransferDO::getAppId, appId, + PayTransferDO::getMerchantTransferId, merchantTransferId); + } + + default PageResult selectPage(PayTransferPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(PayTransferDO::getNo, reqVO.getNo()) + .eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId()) + .eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode()) + .eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantTransferId()) + .eqIfPresent(PayTransferDO::getType, reqVO.getType()) + .eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus()) + .likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName()) + .eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo()) + .betweenIfPresent(PayTransferDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(PayTransferDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(PayTransferDO::getStatus, status); + } + + default PayTransferDO selectByAppIdAndNo(Long appId, String no) { + return selectOne(PayTransferDO::getAppId, appId, + PayTransferDO::getNo, no); + } + +} + + + + diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/RedisKeyConstants.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000..168eb8f --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/RedisKeyConstants.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.pay.dal.redis; + +/** + * 支付 Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 通知任务的分布式锁 + * + * KEY 格式:pay_notify:lock:%d // 参数来自 DefaultLockKeyBuilder 类 + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 + * 过期时间:不固定 + */ + String PAY_NOTIFY_LOCK = "pay_notify:lock:%d"; + + /** + * 支付钱包的分布式锁 + * + * KEY 格式:pay_wallet:lock:%d + * VALUE 数据格式:HASH // RLock.class:Redisson 的 Lock 锁,使用 Hash 数据结构 + * 过期时间:不固定 + */ + String PAY_WALLET_LOCK = "pay_wallet:lock:%d"; + + /** + * 支付序号的缓存 + * + * KEY 格式:pay_no:{prefix} + * VALUE 数据格式:编号自增 + */ + String PAY_NO = "pay_no:"; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/no/PayNoRedisDAO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/no/PayNoRedisDAO.java new file mode 100644 index 0000000..77083b3 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/no/PayNoRedisDAO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.pay.dal.redis.no; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import com.tashow.cloud.pay.dal.redis.RedisKeyConstants; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 支付序号的 Redis DAO + * + * @author 芋道源码 + */ +@Repository +public class PayNoRedisDAO { + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 生成序号 + * + * @param prefix 前缀 + * @return 序号 + */ + public String generate(String prefix) { + // 递增序号 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN); + String key = RedisKeyConstants.PAY_NO + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + // 设置过期时间 + stringRedisTemplate.expire(key, Duration.ofMinutes(1L)); + return noPrefix + no; + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/notify/PayNotifyLockRedisDAO.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/notify/PayNotifyLockRedisDAO.java new file mode 100644 index 0000000..affec36 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/dal/redis/notify/PayNotifyLockRedisDAO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.pay.dal.redis.notify; + +import jakarta.annotation.Resource; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Repository; + +import java.util.concurrent.TimeUnit; + +import static com.tashow.cloud.pay.dal.redis.RedisKeyConstants.PAY_NOTIFY_LOCK; + +/** + * 支付通知的锁 Redis DAO + * + * @author 芋道源码 + */ +@Repository +public class PayNotifyLockRedisDAO { + + @Resource + private RedissonClient redissonClient; + + public void lock(Long id, Long timeoutMillis, Runnable runnable) { + String lockKey = formatKey(id); + RLock lock = redissonClient.getLock(lockKey); + try { + lock.lock(timeoutMillis, TimeUnit.MILLISECONDS); + // 执行逻辑 + runnable.run(); + } finally { + lock.unlock(); + } + } + + private static String formatKey(Long id) { + return String.format(PAY_NOTIFY_LOCK, id); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/job/config/PayJobConfiguration.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/job/config/PayJobConfiguration.java new file mode 100644 index 0000000..e849b22 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/job/config/PayJobConfiguration.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.pay.framework.job.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration(proxyBeanMethods = false) +public class PayJobConfiguration { + + public static final String NOTIFY_THREAD_POOL_TASK_EXECUTOR = "NOTIFY_THREAD_POOL_TASK_EXECUTOR"; + + @Bean(NOTIFY_THREAD_POOL_TASK_EXECUTOR) + public ThreadPoolTaskExecutor notifyThreadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); // 设置核心线程数 + executor.setMaxPoolSize(16); // 设置最大线程数 + executor.setKeepAliveSeconds(60); // 设置空闲时间 + executor.setQueueCapacity(100); // 设置队列大小 + executor.setThreadNamePrefix("notify-task-"); // 配置线程池的前缀 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 进行加载 + executor.initialize(); + return executor; + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/job/core/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/job/core/package-info.java new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/package-info.java new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/config/PayConfiguration.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/config/PayConfiguration.java new file mode 100644 index 0000000..49ad8ab --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/config/PayConfiguration.java @@ -0,0 +1,9 @@ +package com.tashow.cloud.pay.framework.pay.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(PayProperties.class) +public class PayConfiguration { +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/config/PayProperties.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/config/PayProperties.java new file mode 100644 index 0000000..4dc1b68 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/config/PayProperties.java @@ -0,0 +1,69 @@ +package com.tashow.cloud.pay.framework.pay.config; + +import lombok.Data; +import org.hibernate.validator.constraints.URL; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import jakarta.validation.constraints.NotEmpty; + +@ConfigurationProperties(prefix = "yudao.pay") +@Validated +@Data +public class PayProperties { + + private static final String ORDER_NO_PREFIX = "P"; + private static final String REFUND_NO_PREFIX = "R"; + + private static final String WALLET_PAY_APP_KEY_DEFAULT = "wallet"; + + /** + * 支付回调地址 + * + * 实际上,对应的 PayNotifyController 的 notifyOrder 方法的 URL + * + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 orderNotifyUrl 地址 => 业务的 PayAppDO.orderNotifyUrl 地址 + */ + @NotEmpty(message = "支付回调地址不能为空") + @URL(message = "支付回调地址的格式必须是 URL") + private String orderNotifyUrl; + + /** + * 退款回调地址 + * + * 实际上,对应的 PayNotifyController 的 notifyRefund 方法的 URL + * + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 refundNotifyUrl 地址 => 业务的 PayAppDO.notifyRefundUrl 地址 + */ + @NotEmpty(message = "支付回调地址不能为空") + @URL(message = "支付回调地址的格式必须是 URL") + private String refundNotifyUrl; + + /** + * 转账回调地址 + * + * 实际上,对应的 PayNotifyController 的 notifyTransfer 方法的 URL + * + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 transferNotifyUrl 地址 => 业务的 PayAppDO.transferNotifyUrl 地址 + */ + private String transferNotifyUrl; + + /** + * 支付订单 no 的前缀 + */ + @NotEmpty(message = "支付订单 no 的前缀不能为空") + private String orderNoPrefix = ORDER_NO_PREFIX; + + /** + * 退款订单 no 的前缀 + */ + @NotEmpty(message = "退款订单 no 的前缀不能为空") + private String refundNoPrefix = REFUND_NO_PREFIX; + + /** + * 钱包支付应用 AppKey + */ + @NotEmpty(message = "钱包支付应用 AppKey 不能为空") + private String walletPayAppKey = WALLET_PAY_APP_KEY_DEFAULT; + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/package-info.java new file mode 100644 index 0000000..13f9809 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/pay/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.pay.framework.pay; \ No newline at end of file diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/rpc/config/RpcConfiguration.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..0812bb9 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,10 @@ +package com.tashow.cloud.pay.framework.rpc.config; + +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {SocialClientApi.class}) +public class RpcConfiguration { +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/rpc/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/rpc/package-info.java new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..473f63b --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.pay.framework.security.config; + +import com.tashow.cloud.payapi.enums.ApiConstants; +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Pay 模块的 Security 配置 + */ +@Configuration("paySecurityConfiguration") +public class SecurityConfiguration { + + @Bean("payAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Swagger 接口文档 + registry.requestMatchers("/v3/api-docs/**").permitAll() + .requestMatchers("/webjars/**").permitAll() + .requestMatchers("/swagger-ui").permitAll() + .requestMatchers("/swagger-ui/**").permitAll(); + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").permitAll() + .requestMatchers("/actuator/**").permitAll(); + // Druid 监控 + registry.requestMatchers("/druid/**").permitAll(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/security/core/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/framework/security/core/package-info.java new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/notify/PayNotifyJob.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/notify/PayNotifyJob.java new file mode 100644 index 0000000..c2e46a7 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/notify/PayNotifyJob.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.pay.job.notify; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.pay.service.notify.PayNotifyService; +import com.tashow.cloud.tenant.core.job.TenantJob; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 支付通知 Job + * 通过不断扫描待通知的 PayNotifyTaskDO 记录,回调业务线的回调接口 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class PayNotifyJob { + + @Resource + private PayNotifyService payNotifyService; + +// @XxlJob("payNotifyJob") + @TenantJob // 多租户 + public String execute() throws Exception { + int notifyCount = payNotifyService.executeNotify(); + log.info("[execute][执行支付通知 ({}) 个]", notifyCount); + return StrUtil.format("执行支付通知 ({}) 个",notifyCount); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/order/PayOrderExpireJob.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/order/PayOrderExpireJob.java new file mode 100644 index 0000000..3947d9e --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/order/PayOrderExpireJob.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.pay.job.order; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.tenant.core.job.TenantJob; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 支付订单的过期 Job + * + * 支付超过过期时间时,支付渠道是不会通知进行过期,所以需要定时进行过期关闭。 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class PayOrderExpireJob { + + @Resource + private PayOrderService orderService; + +// @XxlJob("payOrderExpireJob") + @TenantJob // 多租户 + public String execute(String param) { + int count = orderService.expireOrder(); + log.info("[execute][支付过期 ({}) 个]", count); + return StrUtil.format("支付过期 ({}) 个",count); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/order/PayOrderSyncJob.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/order/PayOrderSyncJob.java new file mode 100644 index 0000000..0ba6d51 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/order/PayOrderSyncJob.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.pay.job.order; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.tenant.core.job.TenantJob; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 支付订单的同步 Job + * + * 由于支付订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class PayOrderSyncJob { + + /** + * 同步创建时间在 N 分钟之内的订单 + * + * 为什么同步 10 分钟之内的订单? + * 因为一个订单发起支付,到支付成功,大多数在 10 分钟内,需要保证轮询到。 + * 如果设置为 30、60 或者更大时间范围,会导致轮询的订单太多,影响性能。当然,你也可以根据自己的业务情况来处理。 + */ + private static final Duration CREATE_TIME_DURATION_BEFORE = Duration.ofMinutes(10); + + @Resource + private PayOrderService orderService; + +// @XxlJob("payOrderSyncJob") + @TenantJob // 多租户 + public String execute() { + LocalDateTime minCreateTime = LocalDateTime.now().minus(CREATE_TIME_DURATION_BEFORE); + int count = orderService.syncOrder(minCreateTime); + log.info("[execute][同步支付订单 ({}) 个]", count); + return StrUtil.format("同步支付订单 ({}) 个",count); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/refund/PayRefundSyncJob.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/refund/PayRefundSyncJob.java new file mode 100644 index 0000000..39ae12f --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/refund/PayRefundSyncJob.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.pay.job.refund; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.pay.service.refund.PayRefundService; +import com.tashow.cloud.tenant.core.job.TenantJob; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 退款订单的同步 Job + * + * 由于退款订单的状态,是由支付渠道异步通知进行同步,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class PayRefundSyncJob { + + @Resource + private PayRefundService refundService; + +// @XxlJob("payRefundSyncJob") + @TenantJob // 多租户 + public String execute() { + int count = refundService.syncRefund(); + log.info("[execute][同步退款订单 ({}) 个]", count); + return StrUtil.format("同步退款订单 ({}) 个",count); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/transfer/PayTransferSyncJob.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/transfer/PayTransferSyncJob.java new file mode 100644 index 0000000..2ca994c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/job/transfer/PayTransferSyncJob.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.pay.job.transfer; + +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.pay.service.transfer.PayTransferService; +import com.tashow.cloud.tenant.core.job.TenantJob; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 转账订单的同步 Job + * + * 由于转账订单的转账结果,有些渠道是异步通知进行同步的,考虑到异步通知可能会失败(小概率),所以需要定时进行同步。 + * + * @author jason + */ +@Component +@Slf4j +public class PayTransferSyncJob { + + @Resource + private PayTransferService transferService; + +// @XxlJob("payTransferSyncJob") + @TenantJob // 多租户 + public String execute(String param) { + int count = transferService.syncTransfer(); + log.info("[execute][同步转账订单 ({}) 个]", count); + return StrUtil.format("同步转账订单 ({}) 个",count); + } +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/package-info.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/package-info.java new file mode 100644 index 0000000..e7fbd6d --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/package-info.java @@ -0,0 +1,10 @@ +/** + * pay 模块,我们放支付业务,提供业务的支付能力。 + * 例如说:商户、应用、支付、退款等等 + * + * 1. Controller URL:以 /pay/ 开头,避免和其它 Module 冲突 + * 2. DataObject 表名:以 pay_ 开头,方便在数据库中区分 + * + * 注意,由于 Pay 模块和 Trade 模块,容易重名,所以类名都加载 Pay 的前缀~ + */ +package com.tashow.cloud.pay; diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/app/PayAppService.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/app/PayAppService.java new file mode 100644 index 0000000..64e9af0 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/app/PayAppService.java @@ -0,0 +1,115 @@ +package com.tashow.cloud.pay.service.app; + +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppCreateReqVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppPageReqVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 支付应用 Service 接口 + * + * @author 芋艿 + */ +public interface PayAppService { + + /** + * 创建支付应用 + * + * @param createReqVO 创建 + * @return 编号 + */ + Long createApp(@Valid PayAppCreateReqVO createReqVO); + + /** + * 更新支付应用 + * + * @param updateReqVO 更新 + */ + void updateApp(@Valid PayAppUpdateReqVO updateReqVO); + + /** + * 修改应用状态 + * + * @param id 应用编号 + * @param status 状态 + */ + void updateAppStatus(Long id, Integer status); + + /** + * 删除支付应用 + * + * @param id 编号 + */ + void deleteApp(Long id); + + /** + * 获得支付应用 + * + * @param id 编号 + * @return 支付应用 + */ + PayAppDO getApp(Long id); + + /** + * 获得支付应用列表 + * + * @param ids 编号 + * @return 支付应用列表 + */ + List getAppList(Collection ids); + + /** + * 获得支付应用列表 + * + * @return 支付应用列表 + */ + List getAppList(); + + /** + * 获得支付应用分页 + * + * @param pageReqVO 分页查询 + * @return 支付应用分页 + */ + PageResult getAppPage(PayAppPageReqVO pageReqVO); + + /** + * 获得指定编号的商户 Map + * + * @param ids 应用编号集合 + * @return 商户 Map + */ + default Map getAppMap(Collection ids) { + List list = getAppList(ids); + return CollectionUtils.convertMap(list, PayAppDO::getId); + } + + /** + * 支付应用的合法性 + *

+ * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param id 应用编号 + * @return 应用 + */ + PayAppDO validPayApp(Long id); + + /** + * 支付应用的合法性 + *

+ * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param appKey 应用标识 + * @return 应用 + */ + PayAppDO validPayApp(String appKey); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/app/PayAppServiceImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/app/PayAppServiceImpl.java new file mode 100644 index 0000000..0aa2590 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/app/PayAppServiceImpl.java @@ -0,0 +1,163 @@ +package com.tashow.cloud.pay.service.app; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppCreateReqVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppPageReqVO; +import com.tashow.cloud.pay.controller.admin.app.vo.PayAppUpdateReqVO; +import com.tashow.cloud.pay.convert.app.PayAppConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.mysql.app.PayAppMapper; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.pay.service.refund.PayRefundService; +import com.tashow.cloud.payapi.enums.ErrorCodeConstants; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.payapi.enums.ErrorCodeConstants.*; + + +/** + * 支付应用 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +public class PayAppServiceImpl implements PayAppService { + + @Resource + private PayAppMapper appMapper; + + @Resource + @Lazy // 延迟加载,避免循环依赖报错 + private PayOrderService orderService; + @Resource + @Lazy // 延迟加载,避免循环依赖报错 + private PayRefundService refundService; + + @Override + public Long createApp(PayAppCreateReqVO createReqVO) { + // 验证 appKey 是否重复 + validateAppKeyUnique(null, createReqVO.getAppKey()); + + // 插入 + PayAppDO app = PayAppConvert.INSTANCE.convert(createReqVO); + appMapper.insert(app); + // 返回 + return app.getId(); + } + + @Override + public void updateApp(PayAppUpdateReqVO updateReqVO) { + // 校验存在 + validateAppExists(updateReqVO.getId()); + // 验证 appKey 是否重复 + validateAppKeyUnique(updateReqVO.getId(), updateReqVO.getAppKey()); + + // 更新 + PayAppDO updateObj = PayAppConvert.INSTANCE.convert(updateReqVO); + appMapper.updateById(updateObj); + } + + void validateAppKeyUnique(Long id, String appKey) { + PayAppDO app = appMapper.selectByAppKey(appKey); + if (app == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 appKey 的应用 + if (id == null) { + throw exception(APP_KEY_EXISTS); + } + if (!app.getId().equals(id)) { + throw exception(APP_KEY_EXISTS); + } + } + + @Override + public void updateAppStatus(Long id, Integer status) { + // 校验商户存在 + validateAppExists(id); + // 更新状态 + appMapper.updateById(new PayAppDO().setId(id).setStatus(status)); + } + + @Override + public void deleteApp(Long id) { + // 校验存在 + validateAppExists(id); + // 校验关联数据是否存在 + if (orderService.getOrderCountByAppId(id) > 0) { + throw exception(APP_EXIST_ORDER_CANT_DELETE); + } + if (refundService.getRefundCountByAppId(id) > 0) { + throw exception(APP_EXIST_REFUND_CANT_DELETE); + } + + // 删除 + appMapper.deleteById(id); + } + + private void validateAppExists(Long id) { + if (appMapper.selectById(id) == null) { + throw exception(APP_NOT_FOUND); + } + } + + @Override + public PayAppDO getApp(Long id) { + return appMapper.selectById(id); + } + + @Override + public List getAppList(Collection ids) { + return appMapper.selectBatchIds(ids); + } + + @Override + public List getAppList() { + return appMapper.selectList(); + } + + @Override + public PageResult getAppPage(PayAppPageReqVO pageReqVO) { + return appMapper.selectPage(pageReqVO); + } + + @Override + public PayAppDO validPayApp(Long appId) { + PayAppDO app = appMapper.selectById(appId); + return validatePayApp(app); + } + + @Override + public PayAppDO validPayApp(String appKey) { + PayAppDO app = appMapper.selectByAppKey(appKey); + return validatePayApp(app); + } + + /** + * 校验支付应用实体的有效性:存在 + 开启 + * + * @param app 待校验的支付应用实体 + * @return 校验通过的支付应用实体 + */ + private PayAppDO validatePayApp(PayAppDO app) { + // 校验是否存在 + if (app == null) { + throw exception(ErrorCodeConstants.APP_NOT_FOUND); + } + // 校验是否禁用 + if (CommonStatusEnum.isDisable(app.getStatus())) { + throw exception(ErrorCodeConstants.APP_IS_DISABLE); + } + return app; + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/channel/PayChannelService.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/channel/PayChannelService.java new file mode 100644 index 0000000..564de7b --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/channel/PayChannelService.java @@ -0,0 +1,104 @@ +package com.tashow.cloud.pay.service.channel; + +import com.tashow.cloud.common.exception.ServiceException; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelCreateReqVO; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.sdk.payment.client.PayClient; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 支付渠道 Service 接口 + * + * @author aquan + */ +public interface PayChannelService { + + /** + * 创建支付渠道 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createChannel(@Valid PayChannelCreateReqVO createReqVO); + + /** + * 更新支付渠道 + * + * @param updateReqVO 更新信息 + */ + void updateChannel(@Valid PayChannelUpdateReqVO updateReqVO); + + /** + * 删除支付渠道 + * + * @param id 编号 + */ + void deleteChannel(Long id); + + /** + * 获得支付渠道 + * + * @param id 编号 + * @return 支付渠道 + */ + PayChannelDO getChannel(Long id); + + /** + * 根据支付应用 ID 集合,获得支付渠道列表 + * + * @param appIds 应用编号集合 + * @return 支付渠道列表 + */ + List getChannelListByAppIds(Collection appIds); + + /** + * 根据条件获取渠道 + * + * @param appId 应用编号 + * @param code 渠道编码 + * @return 数量 + */ + PayChannelDO getChannelByAppIdAndCode(Long appId, String code); + + /** + * 支付渠道的合法性 + * + * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param id 渠道编号 + * @return 渠道信息 + */ + PayChannelDO validPayChannel(Long id); + + /** + * 支付渠道的合法性 + * + * 如果不合法,抛出 {@link ServiceException} 业务异常 + * + * @param appId 应用编号 + * @param code 支付渠道 + * @return 渠道信息 + */ + PayChannelDO validPayChannel(Long appId, String code); + + /** + * 获得指定应用的开启的渠道列表 + * + * @param appId 应用编号 + * @return 渠道列表 + */ + List getEnableChannelList(Long appId); + + /** + * 获得指定编号的支付客户端 + * + * @param id 编号 + * @return 支付客户端 + */ + PayClient getPayClient(Long id); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/channel/PayChannelServiceImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/channel/PayChannelServiceImpl.java new file mode 100644 index 0000000..2a0a24a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/channel/PayChannelServiceImpl.java @@ -0,0 +1,171 @@ +package com.tashow.cloud.pay.service.channel; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelCreateReqVO; +import com.tashow.cloud.pay.controller.admin.channel.vo.PayChannelUpdateReqVO; +import com.tashow.cloud.pay.convert.channel.PayChannelConvert; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.dal.mysql.channel.PayChannelMapper; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.client.PayClientConfig; +import com.tashow.cloud.sdk.payment.client.PayClientFactory; +import com.tashow.cloud.sdk.payment.client.impl.weixin.WxPubPayClient; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.payapi.enums.ErrorCodeConstants.*; + + +/** + * 支付渠道 Service 实现类 + * + * @author aquan + */ +@Service +@Slf4j +@Validated +public class PayChannelServiceImpl implements PayChannelService { + + @Resource + private PayClientFactory payClientFactory; + + @Resource + private PayChannelMapper payChannelMapper; + + @Resource + private Validator validator; + + /** + * 初始化,为了注册钱包 + */ + @PostConstruct + public void init() { + payClientFactory.registerPayClientClass(PayTypeEnum.WX_PUB, WxPubPayClient.class); + } + + @Override + public Long createChannel(PayChannelCreateReqVO reqVO) { + // 断言是否有重复的 + PayChannelDO dbChannel = getChannelByAppIdAndCode(reqVO.getAppId(), reqVO.getCode()); + if (dbChannel != null) { + throw exception(CHANNEL_EXIST_SAME_CHANNEL_ERROR); + } + + // 新增渠道 + PayChannelDO channel = PayChannelConvert.INSTANCE.convert(reqVO) + .setConfig(parseConfig(reqVO.getCode(), reqVO.getConfig())); + payChannelMapper.insert(channel); + return channel.getId(); + } + + @Override + public void updateChannel(PayChannelUpdateReqVO updateReqVO) { + // 校验存在 + PayChannelDO dbChannel = validateChannelExists(updateReqVO.getId()); + + // 更新 + PayChannelDO channel = PayChannelConvert.INSTANCE.convert(updateReqVO) + .setConfig(parseConfig(dbChannel.getCode(), updateReqVO.getConfig())); + payChannelMapper.updateById(channel); + } + + /** + * 解析并校验配置 + * + * @param code 渠道编码 + * @param configStr 配置 + * @return 支付配置 + */ + private PayClientConfig parseConfig(String code, String configStr) { + // 解析配置 + Class payClass = PayTypeEnum.getByCode(code).getConfigClass(); + if (ObjectUtil.isNull(payClass)) { + throw exception(CHANNEL_NOT_FOUND); + } + PayClientConfig config = JsonUtils.parseObject2(configStr, payClass); + Assert.notNull(config); + + // 验证参数 + config.validate(validator); + return config; + } + + @Override + public void deleteChannel(Long id) { + // 校验存在 + validateChannelExists(id); + + // 删除 + payChannelMapper.deleteById(id); + } + + private PayChannelDO validateChannelExists(Long id) { + PayChannelDO channel = payChannelMapper.selectById(id); + if (channel == null) { + throw exception(CHANNEL_NOT_FOUND); + } + return channel; + } + + @Override + public PayChannelDO getChannel(Long id) { + return payChannelMapper.selectById(id); + } + + @Override + public List getChannelListByAppIds(Collection appIds) { + return payChannelMapper.selectListByAppIds(appIds); + } + + @Override + public PayChannelDO getChannelByAppIdAndCode(Long appId, String code) { + return payChannelMapper.selectByAppIdAndCode(appId, code); + } + + @Override + public PayChannelDO validPayChannel(Long id) { + PayChannelDO channel = payChannelMapper.selectById(id); + validPayChannel(channel); + return channel; + } + + @Override + public PayChannelDO validPayChannel(Long appId, String code) { + PayChannelDO channel = payChannelMapper.selectByAppIdAndCode(appId, code); + validPayChannel(channel); + return channel; + } + + private void validPayChannel(PayChannelDO channel) { + if (channel == null) { + throw exception(CHANNEL_NOT_FOUND); + } + if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) { + throw exception(CHANNEL_IS_DISABLE); + } + } + + @Override + public List getEnableChannelList(Long appId) { + return payChannelMapper.selectListByAppId(appId, CommonStatusEnum.ENABLE.getStatus()); + } + + @Override + public PayClient getPayClient(Long id) { + PayChannelDO channel = validPayChannel(id); + return payClientFactory.createOrUpdatePayClient(id, channel.getCode(), channel.getConfig()); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/notify/PayNotifyService.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/notify/PayNotifyService.java new file mode 100644 index 0000000..56e4e12 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/notify/PayNotifyService.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.pay.service.notify; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyLogDO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyTaskDO; + +import java.util.List; + +/** + * 回调通知 Service 接口 + * + */ +public interface PayNotifyService { + + /** + * 创建回调通知任务 + * + * @param type 类型 + * @param dataId 数据编号 + */ + void createPayNotifyTask(Integer type, Long dataId); + + /** + * 执行回调通知 + * + * 注意,该方法提供给定时任务调用。目前是 yudao-server 进行调用 + * @return 通知数量 + */ + int executeNotify() throws InterruptedException; + + /** + * 获得回调通知 + * + * @param id 编号 + * @return 回调通知 + */ + PayNotifyTaskDO getNotifyTask(Long id); + + /** + * 获得回调通知分页 + * + * @param pageReqVO 分页查询 + * @return 回调通知分页 + */ + PageResult getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO); + + /** + * 获得回调日志列表 + * + * @param taskId 任务编号 + * @return 日志列表 + */ + List getNotifyLogList(Long taskId); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/notify/PayNotifyServiceImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/notify/PayNotifyServiceImpl.java new file mode 100644 index 0000000..a234e3e --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/notify/PayNotifyServiceImpl.java @@ -0,0 +1,308 @@ +package com.tashow.cloud.pay.service.notify; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.date.DateUtils; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyLogDO; +import com.tashow.cloud.pay.dal.dataobject.notify.PayNotifyTaskDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import com.tashow.cloud.pay.dal.mysql.notify.PayNotifyLogMapper; +import com.tashow.cloud.pay.dal.mysql.notify.PayNotifyTaskMapper; +import com.tashow.cloud.pay.dal.redis.notify.PayNotifyLockRedisDAO; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.pay.service.refund.PayRefundService; +import com.tashow.cloud.pay.service.transfer.PayTransferService; +import com.tashow.cloud.payapi.api.notify.dto.PayOrderNotifyReqDTO; +import com.tashow.cloud.payapi.api.notify.dto.PayRefundNotifyReqDTO; +import com.tashow.cloud.payapi.api.notify.dto.PayTransferNotifyReqDTO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyStatusEnum; +import com.tashow.cloud.payapi.enums.notify.PayNotifyTypeEnum; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static com.tashow.cloud.common.util.date.LocalDateTimeUtils.addTime; +import static com.tashow.cloud.pay.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR; + +/** + * 支付通知 Core Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Valid +@Slf4j +public class PayNotifyServiceImpl implements PayNotifyService { + + /** + * 通知超时时间,单位:秒 + */ + public static final int NOTIFY_TIMEOUT = 120; + /** + * {@link #NOTIFY_TIMEOUT} 的毫秒 + */ + public static final long NOTIFY_TIMEOUT_MILLIS = 120 * DateUtils.SECOND_MILLIS; + + @Resource + @Lazy // 循环依赖,避免报错 + private PayOrderService orderService; + @Resource + @Lazy // 循环依赖,避免报错 + private PayRefundService refundService; + @Resource + @Lazy // 循环依赖,避免报错 + private PayTransferService transferService; + + @Resource + private PayNotifyTaskMapper notifyTaskMapper; + @Resource + private PayNotifyLogMapper notifyLogMapper; + + @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR) + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Resource + private PayNotifyLockRedisDAO notifyLockCoreRedisDAO; + + @Override + @Transactional(rollbackFor = Exception.class) + public void createPayNotifyTask(Integer type, Long dataId) { + PayNotifyTaskDO task = new PayNotifyTaskDO().setType(type).setDataId(dataId); + task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now()) + .setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1); + // 补充 appId + notifyUrl 字段 + if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { + PayOrderDO order = orderService.getOrder(task.getDataId()); // 不进行非空判断,有问题直接异常 + task.setAppId(order.getAppId()). + setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl()); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { + PayRefundDO refundDO = refundService.getRefund(task.getDataId()); + task.setAppId(refundDO.getAppId()) + .setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl()); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { + PayTransferDO transfer = transferService.getTransfer(task.getDataId()); + task.setAppId(transfer.getAppId()).setMerchantTransferId(transfer.getMerchantTransferId()) + .setNotifyUrl(transfer.getNotifyUrl()); + } + + // 执行插入 + notifyTaskMapper.insert(task); + + // 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + executeNotify(task); + } + }); + } + + @Override + public int executeNotify() throws InterruptedException { + // 获得需要通知的任务 + List tasks = notifyTaskMapper.selectListByNotify(); + if (CollUtil.isEmpty(tasks)) { + return 0; + } + + // 遍历,逐个通知 + CountDownLatch latch = new CountDownLatch(tasks.size()); + tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> { + try { + executeNotify(task); + } finally { + latch.countDown(); + } + })); + // 等待完成 + awaitExecuteNotify(latch); + // 返回执行完成的任务数(成功 + 失败) + return tasks.size(); + } + + /** + * 等待全部支付通知的完成 + * 每 1 秒会打印一次剩余任务数量 + * + * @param latch Latch + * @throws InterruptedException 如果被打断 + */ + private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException { + long size = latch.getCount(); + for (int i = 0; i < NOTIFY_TIMEOUT; i++) { + if (latch.await(1L, TimeUnit.SECONDS)) { + return; + } + log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount()); + } + log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount()); + } + + /** + * 同步执行单个支付通知 + * + * @param task 通知任务 + */ + public void executeNotify(PayNotifyTaskDO task) { + // 分布式锁,避免并发问题 + notifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> { + // 校验,当前任务是否已经被通知过 + // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。 + // 因此,此处我们通过第 notifyTimes 通知次数是否匹配来判断 + PayNotifyTaskDO dbTask = notifyTaskMapper.selectById(task.getId()); + if (ObjectUtil.notEqual(task.getNotifyTimes(), dbTask.getNotifyTimes())) { + log.warn("[executeNotifySync][task({}) 任务被忽略,原因是它的通知不是第 ({}) 次,可能是因为并发执行了]", + JsonUtils.toJsonString(task), dbTask.getNotifyTimes()); + return; + } + + // 执行通知 + getSelf().executeNotify0(dbTask); + }); + } + + @Transactional(rollbackFor = Exception.class) + public void executeNotify0(PayNotifyTaskDO task) { + // 发起回调 + CommonResult invokeResult = null; + Throwable invokeException = null; + try { + invokeResult = executeNotifyInvoke(task); + } catch (Throwable e) { + invokeException = e; + } + + // 处理结果 + Integer newStatus = processNotifyResult(task, invokeResult, invokeException); + + // 记录 PayNotifyLog 日志 + String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) : + JsonUtils.toJsonString(invokeResult); + notifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId()) + .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build()); + } + + /** + * 执行单个支付任务的 HTTP 调用 + * + * @param task 通知任务 + * @return HTTP 响应 + */ + private CommonResult executeNotifyInvoke(PayNotifyTaskDO task) { + // 拼接 body 参数 + Object request; + if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) { + request = PayOrderNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) + .payOrderId(task.getDataId()).build(); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { + request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId()) + .payRefundId(task.getDataId()).build(); + } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) { + request = new PayTransferNotifyReqDTO().setMerchantTransferId(task.getMerchantTransferId()) + .setPayTransferId(task.getDataId()); + } else { + throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task)); + } + // 拼接 header 参数 + Map headers = new HashMap<>(); + TenantUtils.addTenantHeader(headers, task.getTenantId()); + + // 发起请求 + try (HttpResponse response = HttpUtil.createPost(task.getNotifyUrl()) + .body(JsonUtils.toJsonString(request)).addHeaders(headers) + .timeout((int) NOTIFY_TIMEOUT_MILLIS).execute()) { + // 解析结果 + return JsonUtils.parseObject(response.body(), CommonResult.class); + } + } + + /** + * 处理并更新通知结果 + * + * @param task 通知任务 + * @param invokeResult 通知结果 + * @param invokeException 通知异常 + * @return 最终任务的状态 + */ + @VisibleForTesting + Integer processNotifyResult(PayNotifyTaskDO task, CommonResult invokeResult, Throwable invokeException) { + // 设置通用的更新 PayNotifyTaskDO 的字段 + PayNotifyTaskDO updateTask = new PayNotifyTaskDO() + .setId(task.getId()) + .setLastExecuteTime(LocalDateTime.now()) + .setNotifyTimes(task.getNotifyTimes() + 1); + + // 情况一:调用成功 + if (invokeResult != null && invokeResult.isSuccess()) { + updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus()); + notifyTaskMapper.updateById(updateTask); + return updateTask.getStatus(); + } + + // 情况二:调用失败、调用异常 + // 2.1 超过最大回调次数 + if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) { + updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus()); + notifyTaskMapper.updateById(updateTask); + return updateTask.getStatus(); + } + // 2.2 未超过最大回调次数 + updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]))); + updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus() + : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus()); + notifyTaskMapper.updateById(updateTask); + return updateTask.getStatus(); + } + + @Override + public PayNotifyTaskDO getNotifyTask(Long id) { + return notifyTaskMapper.selectById(id); + } + + @Override + public PageResult getNotifyTaskPage(PayNotifyTaskPageReqVO pageReqVO) { + return notifyTaskMapper.selectPage(pageReqVO); + } + + @Override + public List getNotifyLogList(Long taskId) { + return notifyLogMapper.selectListByTaskId(taskId); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayNotifyServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/order/PayOrderService.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/order/PayOrderService.java new file mode 100644 index 0000000..26cae0d --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/order/PayOrderService.java @@ -0,0 +1,159 @@ +package com.tashow.cloud.pay.service.order; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderExportReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderPageReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderExtensionDO; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 支付订单 Service 接口 + * + * @author aquan + */ +public interface PayOrderService { + + /** + * 获得支付订单 + * + * @param id 编号 + * @return 支付订单 + */ + PayOrderDO getOrder(Long id); + + /** + * 获得支付订单 + * + * @param appId 应用编号 + * @param merchantOrderId 商户订单编号 + * @return 支付订单 + */ + PayOrderDO getOrder(Long appId, String merchantOrderId); + + /** + * 获得支付订单列表 + * + * @param ids 编号数组 + * @return 支付订单列表 + */ + List getOrderList(Collection ids); + + /** + * 获得指定应用的订单数量 + * + * @param appId 应用编号 + * @return 订单数量 + */ + Long getOrderCountByAppId(Long appId); + + /** + * 获得支付订单分页 + * + * @param pageReqVO 分页查询 + * @return 支付订单分页 + */ + PageResult getOrderPage(PayOrderPageReqVO pageReqVO); + + /** + * 获得支付订单列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 支付订单列表 + */ + List getOrderList(PayOrderExportReqVO exportReqVO); + + /** + * 创建支付单 + * + * @param reqDTO 创建请求 + * @return 支付单编号 + */ + Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); + + /** + * 提交支付 + * 此时,会发起支付渠道的调用 + * + * @param reqVO 提交请求 + * @param userIp 提交 IP + * @return 提交结果 + */ + PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO, + @NotEmpty(message = "提交 IP 不能为空") String userIp); + + /** + * 通知支付单成功 + * + * @param channelId 渠道编号 + * @param notify 通知 + */ + void notifyOrder(Long channelId, PayOrderRespDTO notify); + + /** + * 更新支付订单的退款金额 + * + * @param id 编号 + * @param incrRefundPrice 增加的退款金额 + */ + void updateOrderRefundPrice(Long id, Integer incrRefundPrice); + + /** + * 更新支付订单价格 + * + * @param id 支付单编号 + * @param payPrice 支付单价格 + */ + void updatePayOrderPrice(Long id, Integer payPrice); + + /** + * 获得支付订单 + * + * @param id 编号 + * @return 支付订单 + */ + PayOrderExtensionDO getOrderExtension(Long id); + + /** + * 获得支付订单 + * + * @param no 支付订单 no + * @return 支付订单 + */ + PayOrderExtensionDO getOrderExtensionByNo(String no); + + /** + * 同步订单的支付状态 + * + * @param minCreateTime 最小创建时间 + * @return 同步到已支付的订单数量 + */ + int syncOrder(LocalDateTime minCreateTime); + + /** + * 同步订单的支付状态 + * + * 1. Quietly 表示,即使同步失败,也不会抛出异常 + * 2. 什么时候回出现异常?因为是主动同步,可能和支付渠道的异步回调存在并发冲突,导致抛出异常 + * + * @param id 订单编号 + */ + void syncOrderQuietly(Long id); + + /** + * 将已过期的订单,状态修改为已关闭 + * + * @return 过期的订单数量 + */ + int expireOrder(); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/order/PayOrderServiceImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/order/PayOrderServiceImpl.java new file mode 100644 index 0000000..7f43f12 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/order/PayOrderServiceImpl.java @@ -0,0 +1,606 @@ +package com.tashow.cloud.pay.service.order; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.google.common.annotations.VisibleForTesting; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.date.LocalDateTimeUtils; +import com.tashow.cloud.common.util.number.MoneyUtils; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderExportReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderPageReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitReqVO; +import com.tashow.cloud.pay.controller.admin.order.vo.PayOrderSubmitRespVO; +import com.tashow.cloud.pay.convert.order.PayOrderConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderExtensionDO; +import com.tashow.cloud.pay.dal.mysql.order.PayOrderExtensionMapper; +import com.tashow.cloud.pay.dal.mysql.order.PayOrderMapper; +import com.tashow.cloud.pay.dal.redis.no.PayNoRedisDAO; +import com.tashow.cloud.pay.framework.pay.config.PayProperties; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import com.tashow.cloud.pay.service.notify.PayNotifyService; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyTypeEnum; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.order.PayOrderStatusRespEnum; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; +import static com.tashow.cloud.payapi.enums.ErrorCodeConstants.*; + + +/** + * 支付订单 Service 实现类 + * + * @author aquan + */ +@Service +@Validated +@Slf4j +public class PayOrderServiceImpl implements PayOrderService { + + @Resource + private PayProperties payProperties; + + @Resource + private PayOrderMapper orderMapper; + @Resource + private PayOrderExtensionMapper orderExtensionMapper; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayNotifyService notifyService; + + @Override + public PayOrderDO getOrder(Long id) { + return orderMapper.selectById(id); + } + + @Override + public PayOrderDO getOrder(Long appId, String merchantOrderId) { + return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId); + } + + @Override + public List getOrderList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return orderMapper.selectBatchIds(ids); + } + + @Override + public Long getOrderCountByAppId(Long appId) { + return orderMapper.selectCountByAppId(appId); + } + + @Override + public PageResult getOrderPage(PayOrderPageReqVO pageReqVO) { + return orderMapper.selectPage(pageReqVO); + } + + @Override + public List getOrderList(PayOrderExportReqVO exportReqVO) { + return orderMapper.selectList(exportReqVO); + } + + @Override + public Long createOrder(PayOrderCreateReqDTO reqDTO) { + // 校验 App + PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); + + // 查询对应的支付交易单是否已经存在。如果是,则直接返回 + PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId( + app.getId(), reqDTO.getMerchantOrderId()); + if (order != null) { + log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(), + order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况 + return order.getId(); + } + + // 创建支付交易单 + order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId()) + // 商户相关字段 + .setNotifyUrl(app.getOrderNotifyUrl()) + // 订单相关字段 + .setStatus(PayOrderStatusEnum.WAITING.getStatus()) + // 退款相关字段 + .setRefundPrice(0); + orderMapper.insert(order); + return order.getId(); + } + + @Override // 注意,这里不能添加事务注解,避免调用支付渠道失败时,将 PayOrderExtensionDO 回滚了 + public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) { + // 1.1 获得 PayOrderDO ,并校验其是否存在 + PayOrderDO order = validateOrderCanSubmit(reqVO.getId()); + // 1.32 校验支付渠道是否有效 + PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode()); + PayClient client = channelService.getPayClient(channel.getId()); + + // 2. 插入 PayOrderExtensionDO + String no = noRedisDAO.generate(payProperties.getOrderNoPrefix()); + PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp) + .setOrderId(order.getId()).setNo(no) + .setChannelId(channel.getId()).setChannelCode(channel.getCode()) + .setStatus(PayOrderStatusEnum.WAITING.getStatus()); + orderExtensionMapper.insert(orderExtension); + + // 3. 调用三方接口 + PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp) + // 商户相关的字段 + .setOutTradeNo(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性! + .setSubject(order.getSubject()).setBody(order.getBody()) + .setNotifyUrl(genChannelOrderNotifyUrl(channel)) + .setReturnUrl(reqVO.getReturnUrl()) + // 订单相关字段 + .setPrice(order.getPrice()).setExpireTime(order.getExpireTime()); + PayOrderRespDTO unifiedOrderResp = client.unifiedOrder(unifiedOrderReqDTO); + + // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功 + if (unifiedOrderResp != null) { + try { + getSelf().notifyOrder(channel, unifiedOrderResp); + } catch (Exception e) { + // 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景 + // 支付宝或微信扫码之后时,由于 PayClient 是直接返回支付成功,而支付也会有回调,导致存在并发更新问题,此时一般是可以 try catch 直接忽略 + log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常,可能是并发问题]", + order, channel, unifiedOrderResp, e); + } + // 如有渠道错误码,则抛出业务异常,提示用户 + if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) { + throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(), + unifiedOrderResp.getChannelErrorMsg()); + } + // 此处需要读取最新的状态 + order = orderMapper.selectById(order.getId()); + } + return PayOrderConvert.INSTANCE.convert(order, unifiedOrderResp); + } + + private PayOrderDO validateOrderCanSubmit(Long id) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { // 是否存在 + throw exception(PAY_ORDER_NOT_FOUND); + } + if (PayOrderStatusEnum.isSuccess(order.getStatus())) { // 校验状态,发现已支付 + throw exception(PAY_ORDER_STATUS_IS_SUCCESS); + } + if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期 + throw exception(PAY_ORDER_IS_EXPIRED); + } + + // 【重要】校验是否支付拓展单已支付,只是没有回调、或者数据不正常 + validateOrderActuallyPaid(id); + return order; + } + + /** + * 校验支付订单实际已支付 + * + * @param id 支付编号 + */ + @VisibleForTesting + void validateOrderActuallyPaid(Long id) { + List orderExtensions = orderExtensionMapper.selectListByOrderId(id); + orderExtensions.forEach(orderExtension -> { + // 情况一:校验数据库中的 orderExtension 是不是已支付 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + log.warn("[validateOrderCanSubmit][order({}) 的 extension({}) 已支付,可能是数据不一致]", + id, orderExtension.getId()); + throw exception(PAY_ORDER_EXTENSION_IS_PAID); + } + // 情况二:调用三方接口,查询支付单状态,是不是已支付 + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + if (payClient == null) { + log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); + return; + } + PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); + if (respDTO != null && PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) { + log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付,可能是回调延迟]", + id, toJsonString(respDTO)); + throw exception(PAY_ORDER_EXTENSION_IS_PAID); + } + }); + } + + private PayChannelDO validateChannelCanSubmit(Long appId, String channelCode) { + // 校验 App + appService.validPayApp(appId); + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(appId, channelCode); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + return channel; + } + + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channel 支付渠道 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id + */ + private String genChannelOrderNotifyUrl(PayChannelDO channel) { + return payProperties.getOrderNotifyUrl() + "/" + channel.getId(); + } + + @Override + public void notifyOrder(Long channelId, PayOrderRespDTO notify) { + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 更新支付订单为已支付 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify)); + } + + /** + * 通知并更新订单的支付结果 + * + * @param channel 支付渠道 + * @param notify 通知 + */ + @Transactional(rollbackFor = Exception.class) + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyPayOrder(channel, notify) 调用,否则事务不生效 + public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) { + // 情况一:支付成功的回调 + if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) { + notifyOrderSuccess(channel, notify); + return; + } + // 情况二:支付失败的回调 + if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) { + notifyOrderClosed(channel, notify); + } + // 情况三:WAITING:无需处理 + // 情况四:REFUND:通过退款回调处理 + } + + private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { + // 1. 更新 PayOrderExtensionDO 支付成功 + PayOrderExtensionDO orderExtension = updateOrderSuccess(notify); + // 2. 更新 PayOrderDO 支付成功 + Boolean paid = updateOrderSuccess(channel, orderExtension, notify); + if (paid) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调 + return; + } + + // 3. 插入支付通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.ORDER.getType(), + orderExtension.getOrderId()); + } + + /** + * 更新 PayOrderExtensionDO 支付成功 + * + * @param notify 通知 + * @return PayOrderExtensionDO 对象 + */ + private PayOrderExtensionDO updateOrderSuccess(PayOrderRespDTO notify) { + // 1. 查询 PayOrderExtensionDO + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); + if (orderExtension == null) { + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); + } + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付,无需更新]", orderExtension.getId()); + return orderExtension; + } + if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderExtensionDO + int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), + PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build()); + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + log.info("[updateOrderExtensionSuccess][orderExtension({}) 更新为已支付]", orderExtension.getId()); + return orderExtension; + } + + /** + * 更新 PayOrderDO 支付成功 + * + * @param channel 支付渠道 + * @param orderExtension 支付拓展单 + * @param notify 通知回调 + * @return 是否之前已经成功回调 + */ + private Boolean updateOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, + PayOrderRespDTO notify) { + // 1. 判断 PayOrderDO 是否处于待支付 + PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新 + && Objects.equals(order.getExtensionId(), orderExtension.getId())) { + log.info("[updateOrderExtensionSuccess][order({}) 已经是已支付,无需更新]", order.getId()); + return true; + } + if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderDO + int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), + PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()) + .channelId(channel.getId()).channelCode(channel.getCode()) + .successTime(notify.getSuccessTime()).extensionId(orderExtension.getId()).no(orderExtension.getNo()) + .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId()) + .channelFeeRate(channel.getFeeRate()) + .channelFeePrice(MoneyUtils.calculateRatePrice(order.getPrice(), channel.getFeeRate())) + .build()); + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + log.info("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId()); + return false; + } + + private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) { + updateOrderExtensionClosed(channel, notify); + } + + private void updateOrderExtensionClosed(PayChannelDO channel, PayOrderRespDTO notify) { + // 1. 查询 PayOrderExtensionDO + PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); + if (orderExtension == null) { + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); + } + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { // 如果已经是关闭,直接返回,不用重复更新 + log.info("[updateOrderExtensionClosed][orderExtension({}) 已经是支付关闭,无需更新]", orderExtension.getId()); + return; + } + // 一般出现先是支付成功,然后支付关闭,都是全部退款导致关闭的场景。这个情况,我们不更新支付拓展单,只通过退款流程,更新支付单 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + log.info("[updateOrderExtensionClosed][orderExtension({}) 是已支付,无需更新为支付关闭]", orderExtension.getId()); + return; + } + if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + + // 2. 更新 PayOrderExtensionDO + int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), + PayOrderExtensionDO.builder().status(PayOrderStatusEnum.CLOSED.getStatus()).channelNotifyData(toJsonString(notify)) + .channelErrorCode(notify.getChannelErrorCode()).channelErrorMsg(notify.getChannelErrorMsg()).build()); + if (updateCounts == 0) { // 校验状态,必须是待支付 + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + } + log.info("[updateOrderExtensionClosed][orderExtension({}) 更新为支付关闭]", orderExtension.getId()); + } + + @Override + public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) { + throw exception(REFUND_PRICE_EXCEED); + } + + // 更新订单 + PayOrderDO updateObj = new PayOrderDO() + .setRefundPrice(order.getRefundPrice() + incrRefundPrice) + .setStatus(PayOrderStatusEnum.REFUND.getStatus()); + int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj); + if (updateCount == 0) { + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + } + + @Override + public void updatePayOrderPrice(Long id, Integer payPrice) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (ObjectUtil.notEqual(PayOrderStatusEnum.WAITING.getStatus(), order.getStatus())) { + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); + } + if (ObjectUtil.equal(order.getPrice(), payPrice)) { + return; + } + + orderMapper.updateById(new PayOrderDO().setId(order.getId()).setPrice(payPrice)); + } + + @Override + public PayOrderExtensionDO getOrderExtension(Long id) { + return orderExtensionMapper.selectById(id); + } + + @Override + public PayOrderExtensionDO getOrderExtensionByNo(String no) { + return orderExtensionMapper.selectByNo(no); + } + + @Override + public int syncOrder(LocalDateTime minCreateTime) { + // 1. 查询指定创建时间前的待支付订单 + List orderExtensions = orderExtensionMapper.selectListByStatusAndCreateTimeGe( + PayOrderStatusEnum.WAITING.getStatus(), minCreateTime); + if (CollUtil.isEmpty(orderExtensions)) { + return 0; + } + // 2. 遍历执行 + int count = 0; + for (PayOrderExtensionDO orderExtension : orderExtensions) { + count += syncOrder(orderExtension) ? 1 : 0; + } + return count; + } + + @Override + public void syncOrderQuietly(Long id) { + // 1. 查询待支付订单 + List orderExtensions = orderExtensionMapper.selectListByOrderIdAndStatus(id, + PayOrderStatusEnum.WAITING.getStatus()); + + // 2. 遍历执行 + for (PayOrderExtensionDO orderExtension : orderExtensions) { + syncOrder(orderExtension); + } + } + + /** + * 同步单个支付拓展单 + * + * @param orderExtension 支付拓展单 + * @return 是否已支付 + */ + private boolean syncOrder(PayOrderExtensionDO orderExtension) { + try { + // 1.1 查询支付订单信息 + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + if (payClient == null) { + log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); + return false; + } + PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); + // 如果查询到订单不存在,PayClient 返回的状态为关闭。但此时不能关闭订单。存在以下一种场景: + // 拉起渠道支付后,短时间内用户未及时完成支付,但是该订单同步定时任务恰巧自动触发了,主动查询结果为订单不存在。 + // 当用户支付成功之后,该订单状态在渠道的回调中无法从已关闭改为已支付,造成重大影响。 + // 考虑此定时任务是异常场景的兜底操作,因此这里不做变更,优先以回调为准。 + // 让订单自动随着支付渠道那边一起等到过期,确保渠道先过期关闭支付入口,而后通过订单过期定时任务关闭自己的订单。 + if (PayOrderStatusRespEnum.isClosed(respDTO.getStatus())) { + return false; + } + // 1.2 回调支付结果 + notifyOrder(orderExtension.getChannelId(), respDTO); + + // 2. 如果是已支付,则返回 true + return PayOrderStatusRespEnum.isSuccess(respDTO.getStatus()); + } catch (Throwable e) { + log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderExtension.getId(), e); + return false; + } + } + + @Override + public int expireOrder() { + // 1. 查询过期的待支付订单 + List orders = orderMapper.selectListByStatusAndExpireTimeLt( + PayOrderStatusEnum.WAITING.getStatus(), LocalDateTime.now()); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行 + int count = 0; + for (PayOrderDO order : orders) { + count += expireOrder(order) ? 1 : 0; + } + return count; + } + + /** + * 同步单个支付单 + * + * @param order 支付单 + * @return 是否已过期 + */ + private boolean expireOrder(PayOrderDO order) { + try { + // 1. 需要先处理关联的支付拓展单,避免错误的过期已支付 or 已退款的订单 + List orderExtensions = orderExtensionMapper.selectListByOrderId(order.getId()); + for (PayOrderExtensionDO orderExtension : orderExtensions) { + if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) { + continue; + } + // 情况一:校验数据库中的 orderExtension 是不是已支付 + if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { + log.error("[expireOrder][order({}) 的 extension({}) 已支付,可能是数据不一致]", + order.getId(), orderExtension.getId()); + return false; + } + // 情况二:调用三方接口,查询支付单状态,是不是已支付/已退款 + PayClient payClient = channelService.getPayClient(orderExtension.getChannelId()); + if (payClient == null) { + log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId()); + return false; + } + PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo()); + if (PayOrderStatusRespEnum.isRefund(respDTO.getStatus())) { + // 补充说明:按道理,应该是 WAITING => SUCCESS => REFUND 状态,如果直接 WAITING => REFUND 状态,说明中间丢了过程 + // 此时,需要人工介入,手工补齐数据,保持 WAITING => SUCCESS => REFUND 的过程 + log.error("[expireOrder][extension({}) 的 PayOrderRespDTO({}) 已退款,可能是回调延迟]", + orderExtension.getId(), toJsonString(respDTO)); + return false; + } + if (PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) { + notifyOrder(orderExtension.getChannelId(), respDTO); + return false; + } + // 兜底逻辑:将支付拓展单更新为已关闭 + PayOrderExtensionDO updateObj = new PayOrderExtensionDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setChannelNotifyData(toJsonString(respDTO)); + if (orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(), + updateObj) == 0) { + log.error("[expireOrder][extension({}) 更新为支付关闭失败]", orderExtension.getId()); + return false; + } + log.info("[expireOrder][extension({}) 更新为支付关闭成功]", orderExtension.getId()); + } + + // 2. 都没有上述情况,可以安心更新为已关闭 + PayOrderDO updateObj = new PayOrderDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus()); + if (orderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateObj) == 0) { + log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); + return false; + } + log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId()); + return true; + } catch (Throwable e) { + log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e); + return false; + } + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayOrderServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/refund/PayRefundService.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/refund/PayRefundService.java new file mode 100644 index 0000000..f68b36a --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/refund/PayRefundService.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.pay.service.refund; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundExportReqVO; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; + +import java.util.List; + +/** + * 退款订单 Service 接口 + * + * @author aquan + */ +public interface PayRefundService { + + /** + * 获得退款订单 + * + * @param id 编号 + * @return 退款订单 + */ + PayRefundDO getRefund(Long id); + + /** + * 获得退款订单 + * + * @param no 外部退款单号 + * @return 退款订单 + */ + PayRefundDO getRefundByNo(String no); + + /** + * 获得指定应用的退款数量 + * + * @param appId 应用编号 + * @return 退款数量 + */ + Long getRefundCountByAppId(Long appId); + + /** + * 获得退款订单分页 + * + * @param pageReqVO 分页查询 + * @return 退款订单分页 + */ + PageResult getRefundPage(PayRefundPageReqVO pageReqVO); + + /** + * 获得退款订单列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 退款订单列表 + */ + List getRefundList(PayRefundExportReqVO exportReqVO); + + /** + * 创建退款申请 + * + * @param reqDTO 退款申请信息 + * @return 退款单号 + */ + Long createPayRefund(PayRefundCreateReqDTO reqDTO); + + /** + * 渠道的退款通知 + * + * @param channelId 渠道编号 + * @param notify 通知 + */ + void notifyRefund(Long channelId, PayRefundRespDTO notify); + + /** + * 同步渠道退款的退款状态 + * + * @return 同步到状态的退款数量,包括退款成功、退款失败 + */ + int syncRefund(); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/refund/PayRefundServiceImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/refund/PayRefundServiceImpl.java new file mode 100644 index 0000000..8f08c4c --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/refund/PayRefundServiceImpl.java @@ -0,0 +1,333 @@ +package com.tashow.cloud.pay.service.refund; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundExportReqVO; +import com.tashow.cloud.pay.controller.admin.refund.vo.PayRefundPageReqVO; +import com.tashow.cloud.pay.convert.refund.PayRefundConvert; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.dal.dataobject.order.PayOrderDO; +import com.tashow.cloud.pay.dal.dataobject.refund.PayRefundDO; +import com.tashow.cloud.pay.dal.mysql.refund.PayRefundMapper; +import com.tashow.cloud.pay.dal.redis.no.PayNoRedisDAO; +import com.tashow.cloud.pay.framework.pay.config.PayProperties; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import com.tashow.cloud.pay.service.notify.PayNotifyService; +import com.tashow.cloud.pay.service.order.PayOrderService; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyTypeEnum; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import com.tashow.cloud.payapi.enums.refund.PayRefundStatusEnum; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundRespDTO; +import com.tashow.cloud.sdk.payment.dto.refund.PayRefundUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.refund.PayRefundStatusRespEnum; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.*; +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; +import static com.tashow.cloud.payapi.enums.ErrorCodeConstants.*; + + +/** + * 退款订单 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +@Validated +public class PayRefundServiceImpl implements PayRefundService { + + @Resource + private PayProperties payProperties; + + @Resource + private PayRefundMapper refundMapper; + @Resource + private PayNoRedisDAO noRedisDAO; + + @Resource + private PayOrderService orderService; + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayNotifyService notifyService; + + @Override + public PayRefundDO getRefund(Long id) { + return refundMapper.selectById(id); + } + + @Override + public PayRefundDO getRefundByNo(String no) { + return refundMapper.selectByNo(no); + } + + @Override + public Long getRefundCountByAppId(Long appId) { + return refundMapper.selectCountByAppId(appId); + } + + @Override + public PageResult getRefundPage(PayRefundPageReqVO pageReqVO) { + return refundMapper.selectPage(pageReqVO); + } + + @Override + public List getRefundList(PayRefundExportReqVO exportReqVO) { + return refundMapper.selectList(exportReqVO); + } + + @Override + public Long createPayRefund(PayRefundCreateReqDTO reqDTO) { + // 1.1 校验 App + PayAppDO app = appService.validPayApp(reqDTO.getAppKey()); + // 1.2 校验支付订单 + PayOrderDO order = validatePayOrderCanRefund(reqDTO, app.getId()); + // 1.3 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(order.getChannelId()); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + // 1.4 校验退款订单是否已经存在 + PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId( + app.getId(), reqDTO.getMerchantRefundId()); + if (refund != null) { + throw exception(REFUND_EXISTS); + } + + // 2.1 插入退款单 + String no = noRedisDAO.generate(payProperties.getRefundNoPrefix()); + refund = PayRefundConvert.INSTANCE.convert(reqDTO) + .setNo(no).setAppId(app.getId()).setOrderId(order.getId()).setOrderNo(order.getNo()) + .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode()) + // 商户相关的字段 + .setNotifyUrl(app.getRefundNotifyUrl()) + // 渠道相关字段 + .setChannelOrderNo(order.getChannelOrderNo()) + // 退款相关字段 + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice()); + refundMapper.insert(refund); + try { + // 2.2 向渠道发起退款申请 + PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO() + .setPayPrice(order.getPrice()) + .setRefundPrice(reqDTO.getPrice()) + .setOutTradeNo(order.getNo()) + .setOutRefundNo(refund.getNo()) + .setNotifyUrl(genChannelRefundNotifyUrl(channel)) + .setReason(reqDTO.getReason()); + PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); + // 2.3 处理退款返回 + getSelf().notifyRefund(channel, refundRespDTO); + } catch (Throwable e) { + // 注意:这里仅打印异常,不进行抛出。 + // 原因是:虽然调用支付渠道进行退款发生异常(网络请求超时),实际退款成功。这个结果,后续通过退款回调、或者退款轮询补偿可以拿到。 + // 最终,在异常的情况下,支付中心会异步回调业务的退款回调接口,提供退款结果 + log.error("[createPayRefund][退款 id({}) requestDTO({}) 发生异常]", + refund.getId(), reqDTO, e); + } + + // 返回退款编号 + return refund.getId(); + } + + /** + * 校验支付订单是否可以退款 + * + * @param reqDTO 退款申请信息 + * @return 支付订单 + */ + private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO, Long appId) { + PayOrderDO order = orderService.getOrder(appId, reqDTO.getMerchantOrderId()); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + // 校验状态,必须是已支付、或者已退款 + if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) { + throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR); + } + + // 校验金额,退款金额不能大于原定的金额 + if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()) { + throw exception(REFUND_PRICE_EXCEED); + } + // 是否有退款中的订单 + if (refundMapper.selectCountByAppIdAndOrderId(appId, order.getId(), + PayRefundStatusEnum.WAITING.getStatus()) > 0) { + throw exception(REFUND_HAS_REFUNDING); + } + return order; + } + + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channel 支付渠道 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id + */ + private String genChannelRefundNotifyUrl(PayChannelDO channel) { + return payProperties.getRefundNotifyUrl() + "/" + channel.getId(); + } + + @Override + public void notifyRefund(Long channelId, PayRefundRespDTO notify) { + // 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 更新退款订单 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyRefund(channel, notify)); + } + + /** + * 通知并更新订单的退款结果 + * + * @param channel 支付渠道 + * @param notify 通知 + */ + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyRefund(channel, notify) 调用,否则事务不生效 + @Transactional(rollbackFor = Exception.class) + public void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) { + // 情况一:退款成功 + if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) { + notifyRefundSuccess(channel, notify); + return; + } + // 情况二:退款失败 + if (PayRefundStatusRespEnum.isFailure(notify.getStatus())) { + notifyRefundFailure(channel, notify); + } + } + + private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) { + // 1.1 查询 PayRefundDO + PayRefundDO refund = refundMapper.selectByAppIdAndNo( + channel.getAppId(), notify.getOutRefundNo()); + if (refund == null) { + throw exception(REFUND_NOT_FOUND); + } + if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + log.info("[notifyRefundSuccess][退款订单({}) 已经是退款成功,无需更新]", refund.getId()); + return; + } + if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + // 1.2 更新 PayRefundDO + PayRefundDO updateRefundObj = new PayRefundDO() + .setSuccessTime(notify.getSuccessTime()) + .setChannelRefundNo(notify.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) + .setChannelNotifyData(toJsonString(notify)); + int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); + if (updateCounts == 0) { // 校验状态,必须是等待状态 + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + log.info("[notifyRefundSuccess][退款订单({}) 更新为退款成功]", refund.getId()); + + // 2. 更新订单 + orderService.updateOrderRefundPrice(refund.getOrderId(), refund.getRefundPrice()); + + // 3. 插入退款通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), + refund.getId()); + } + + private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) { + // 1.1 查询 PayRefundDO + PayRefundDO refund = refundMapper.selectByAppIdAndNo( + channel.getAppId(), notify.getOutRefundNo()); + if (refund == null) { + throw exception(REFUND_NOT_FOUND); + } + if (PayRefundStatusEnum.isFailure(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + log.info("[notifyRefundSuccess][退款订单({}) 已经是退款关闭,无需更新]", refund.getId()); + return; + } + if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + // 1.2 更新 PayRefundDO + PayRefundDO updateRefundObj = new PayRefundDO() + .setChannelRefundNo(notify.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) + .setChannelNotifyData(toJsonString(notify)) + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()); + int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); + if (updateCounts == 0) { // 校验状态,必须是等待状态 + throw exception(REFUND_STATUS_IS_NOT_WAITING); + } + log.info("[notifyRefundFailure][退款订单({}) 更新为退款失败]", refund.getId()); + + // 2. 插入退款通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.REFUND.getType(), + refund.getId()); + } + + @Override + public int syncRefund() { + // 1. 查询指定创建时间内的待退款订单 + List refunds = refundMapper.selectListByStatus(PayRefundStatusEnum.WAITING.getStatus()); + if (CollUtil.isEmpty(refunds)) { + return 0; + } + // 2. 遍历执行 + int count = 0; + for (PayRefundDO refund : refunds) { + count += syncRefund(refund) ? 1 : 0; + } + return count; + } + + /** + * 同步单个退款订单 + * + * @param refund 退款订单 + * @return 是否同步到 + */ + private boolean syncRefund(PayRefundDO refund) { + try { + // 1.1 查询退款订单信息 + PayClient payClient = channelService.getPayClient(refund.getChannelId()); + if (payClient == null) { + log.error("[syncRefund][渠道编号({}) 找不到对应的支付客户端]", refund.getChannelId()); + return false; + } + PayRefundRespDTO respDTO = payClient.getRefund(refund.getOrderNo(), refund.getNo()); + // 1.2 回调退款结果 + notifyRefund(refund.getChannelId(), respDTO); + + // 2. 如果同步到,则返回 true + return PayRefundStatusEnum.isSuccess(respDTO.getStatus()) + || PayRefundStatusEnum.isFailure(respDTO.getStatus()); + } catch (Throwable e) { + log.error("[syncRefund][refund({}) 同步退款状态异常]", refund.getId(), e); + return false; + } + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayRefundServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/transfer/PayTransferService.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/transfer/PayTransferService.java new file mode 100644 index 0000000..6fe60b4 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/transfer/PayTransferService.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.pay.service.transfer; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import com.tashow.cloud.payapi.api.transfer.dto.PayTransferCreateReqDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import jakarta.validation.Valid; + +/** + * 转账 Service 接口 + * + * @author jason + */ +public interface PayTransferService { + + /** + * 创建转账单,并发起转账 + * + * 此时,会发起转账渠道的调用 + * + * @param reqVO 请求 + * @param userIp 用户 ip + * @return 渠道的返回结果 + */ + PayTransferDO createTransfer(@Valid PayTransferCreateReqVO reqVO, String userIp); + + /** + * 创建转账单,并发起转账 + * + * @param reqDTO 创建请求 + * @return 转账单编号 + */ + Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO); + + /** + * 获取转账单 + * @param id 转账单编号 + */ + PayTransferDO getTransfer(Long id); + + /** + * 获得转账单分页 + * + * @param pageReqVO 分页查询 + * @return 转账单分页 + */ + PageResult getTransferPage(PayTransferPageReqVO pageReqVO); + + /** + * 同步渠道转账单状态 + * + * @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的 + */ + int syncTransfer(); + + /** + * 渠道的转账通知 + * + * @param channelId 渠道编号 + * @param notify 通知 + */ + void notifyTransfer(Long channelId, PayTransferRespDTO notify); + +} diff --git a/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/transfer/PayTransferServiceImpl.java b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/transfer/PayTransferServiceImpl.java new file mode 100644 index 0000000..62e179d --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/java/com/tashow/cloud/pay/service/transfer/PayTransferServiceImpl.java @@ -0,0 +1,317 @@ +package com.tashow.cloud.pay.service.transfer; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; +import com.tashow.cloud.pay.controller.admin.transfer.vo.PayTransferPageReqVO; +import com.tashow.cloud.pay.dal.dataobject.app.PayAppDO; +import com.tashow.cloud.pay.dal.dataobject.channel.PayChannelDO; +import com.tashow.cloud.pay.dal.dataobject.transfer.PayTransferDO; +import com.tashow.cloud.pay.dal.mysql.transfer.PayTransferMapper; +import com.tashow.cloud.pay.dal.redis.no.PayNoRedisDAO; +import com.tashow.cloud.pay.framework.pay.config.PayProperties; +import com.tashow.cloud.pay.service.app.PayAppService; +import com.tashow.cloud.pay.service.channel.PayChannelService; +import com.tashow.cloud.pay.service.notify.PayNotifyService; +import com.tashow.cloud.payapi.api.transfer.dto.PayTransferCreateReqDTO; +import com.tashow.cloud.payapi.enums.notify.PayNotifyTypeEnum; +import com.tashow.cloud.payapi.enums.transfer.PayTransferStatusEnum; +import com.tashow.cloud.sdk.payment.client.PayClient; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO; +import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferUnifiedReqDTO; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferStatusRespEnum; +import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum; +import com.tashow.cloud.tenant.core.util.TenantUtils; +import jakarta.annotation.Resource; +import jakarta.validation.Validator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.pay.convert.transfer.PayTransferConvert.INSTANCE; +import static com.tashow.cloud.payapi.enums.ErrorCodeConstants.*; +import static com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum.isWaiting; +import static com.tashow.cloud.payapi.enums.transfer.PayTransferStatusEnum.isPendingStatus; +import static com.tashow.cloud.sdk.payment.enums.order.PayOrderStatusRespEnum.*; +import static com.tashow.cloud.sdk.payment.enums.transfer.PayTransferStatusRespEnum.IN_PROGRESS; +import static com.tashow.cloud.sdk.payment.enums.transfer.PayTransferStatusRespEnum.isInProgress; + +// TODO @jason:等彻底实现完,单测写写; + +/** + * 转账 Service 实现类 + * + * @author jason + */ +@Service +@Slf4j +public class PayTransferServiceImpl implements PayTransferService { + + private static final String TRANSFER_NO_PREFIX = "T"; + + @Resource + private PayProperties payProperties; + + @Resource + private PayTransferMapper transferMapper; + @Resource + private PayAppService appService; + @Resource + private PayChannelService channelService; + @Resource + private PayNotifyService notifyService; + @Resource + private PayNoRedisDAO noRedisDAO; + @Resource + private Validator validator; + + @Override + public PayTransferDO createTransfer(PayTransferCreateReqVO reqVO, String userIp) { + // 1. 校验参数 + reqVO.validate(validator); + + // 2. 创建转账单,发起转账 + PayTransferCreateReqDTO req = INSTANCE.convert(reqVO).setUserIp(userIp); + Long transferId = createTransfer(req); + + // 3. 返回转账单 + return getTransfer(transferId); + } + + @Override + public Long createTransfer(PayTransferCreateReqDTO reqDTO) { + // 1.1 校验 App + PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey()); + // 1.2 校验支付渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(payApp.getId(), reqDTO.getChannelCode()); + PayClient client = channelService.getPayClient(channel.getId()); + if (client == null) { + log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); + throw exception(CHANNEL_NOT_FOUND); + } + // 1.3 校验转账单已经发起过转账。 + PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId()); + + if (transfer == null) { + // 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账 + String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); + transfer = INSTANCE.convert(reqDTO) + .setChannelId(channel.getId()) + .setNo(no).setStatus(WAITING.getStatus()) + .setNotifyUrl(payApp.getTransferNotifyUrl()) + .setAppId(channel.getAppId()); + transferMapper.insert(transfer); + } + try { + // 3. 调用三方渠道发起转账 + PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer) + .setOutTransferNo(transfer.getNo()); + transferUnifiedReq.setNotifyUrl(genChannelTransferNotifyUrl(channel)); + PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); + // 4. 通知转账结果 + getSelf().notifyTransfer(channel, unifiedTransferResp); + } catch (Throwable e) { + // 注意这里仅打印异常,不进行抛出。 + // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。 + // 或者使用相同 no 再次发起转账请求 + log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); + } + + return transfer.getId(); + } + + /** + * 根据支付渠道的编码,生成支付渠道的回调地址 + * + * @param channel 支付渠道 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id + */ + private String genChannelTransferNotifyUrl(PayChannelDO channel) { + return payProperties.getTransferNotifyUrl() + "/" + channel.getId(); + } + + private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) { + PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId()); + if (transfer != null) { + // 已经存在,并且状态不为等待状态。说明已经调用渠道转账并返回结果. + if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) { + throw exception(PAY_MERCHANT_TRANSFER_EXISTS); + } + if (ObjectUtil.notEqual(dto.getPrice(), transfer.getPrice())) { + throw exception(PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH); + } + if (ObjectUtil.notEqual(dto.getType(), transfer.getType())) { + throw exception(PAY_SAME_MERCHANT_TRANSFER_TYPE_NOT_MATCH); + } + } + // 如果状态为等待状态。不知道渠道转账是否发起成功。 允许使用相同的 no 再次发起转账,渠道会保证幂等 + return transfer; + } + + @Transactional(rollbackFor = Exception.class) + // 注意,如果是方法内调用该方法,需要通过 getSelf().notifyTransfer(channel, notify) 调用,否则事务不生效 + public void notifyTransfer(PayChannelDO channel, PayTransferRespDTO notify) { + // 转账成功的回调 + if (PayTransferStatusRespEnum.isSuccess(notify.getStatus())) { + notifyTransferSuccess(channel, notify); + } + // 转账关闭的回调 + if (PayTransferStatusRespEnum.isClosed(notify.getStatus())) { + notifyTransferClosed(channel, notify); + } + // 转账处理中的回调 + if (isInProgress(notify.getStatus())) { + notifyTransferInProgress(channel, notify); + } + // WAITING 状态无需处理 + } + + private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) { + // 1.校验 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (isInProgress(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + return; + } + if (!isWaiting(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + } + // 2.更新 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(WAITING.getStatus()), + new PayTransferDO().setStatus(IN_PROGRESS.getStatus())); + if (updateCounts == 0) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + } + log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId()); + } + + + private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { + // 1.校验 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新 + return; + } + if (!isPendingStatus(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + } + // 2.更新 + int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), + new PayTransferDO().setStatus(SUCCESS.getStatus()).setSuccessTime(notify.getSuccessTime()) + .setChannelTransferNo(notify.getChannelTransferNo()) + .setChannelId(channel.getId()).setChannelCode(channel.getCode()) + .setChannelNotifyData(JsonUtils.toJsonString(notify))); + if (updateCounts == 0) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + } + log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); + + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), + transfer.getId()); + } + + private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { + // 1.校验 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + if (isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新 + log.info("[updateTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId()); + return; + } + if (!isPendingStatus(transfer.getStatus())) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + } + + // 2.更新 + int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(), + CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), + new PayTransferDO().setStatus(CLOSED.getStatus()).setChannelId(channel.getId()) + .setChannelCode(channel.getCode()).setChannelTransferNo(notify.getChannelTransferNo()) + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()) + .setChannelNotifyData(JsonUtils.toJsonString(notify))); + if (updateCount == 0) { + throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + } + log.info("[updateTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId()); + + // 3. 插入转账通知记录 + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), + transfer.getId()); + + } + + @Override + public PayTransferDO getTransfer(Long id) { + return transferMapper.selectById(id); + } + + @Override + public PageResult getTransferPage(PayTransferPageReqVO pageReqVO) { + return transferMapper.selectPage(pageReqVO); + } + + @Override + public int syncTransfer() { + List list = transferMapper.selectListByStatus(WAITING.getStatus()); + if (CollUtil.isEmpty(list)) { + return 0; + } + int count = 0; + for (PayTransferDO transfer : list) { + count += syncTransfer(transfer) ? 1 : 0; + } + return count; + } + + private boolean syncTransfer(PayTransferDO transfer) { + try { + // 1. 查询转账订单信息 + PayClient payClient = channelService.getPayClient(transfer.getChannelId()); + if (payClient == null) { + log.error("[syncTransfer][渠道编号({}) 找不到对应的支付客户端]", transfer.getChannelId()); + return false; + } + PayTransferRespDTO resp = payClient.getTransfer(transfer.getNo(), + PayTransferTypeEnum.typeOf(transfer.getType())); + + // 2. 回调转账结果 + notifyTransfer(transfer.getChannelId(), resp); + return true; + } catch (Throwable ex) { + log.error("[syncTransfer][transfer({}) 同步转账单状态异常]", transfer.getId(), ex); + return false; + } + } + + public void notifyTransfer(Long channelId, PayTransferRespDTO notify) { + // 校验渠道是否有效 + PayChannelDO channel = channelService.validPayChannel(channelId); + // 通知转账结果给对应的业务 + TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyTransfer(channel, notify)); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayTransferServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } +} diff --git a/tashow-module/tashow-module-pay/src/main/resources/application-dev.yaml b/tashow-module/tashow-module-pay/src/main/resources/application-dev.yaml new file mode 100644 index 0000000..17c1e15 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/resources/application-dev.yaml @@ -0,0 +1,113 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + username: # Nacos 账号 + password: # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 5 # 初始连接数 + min-idle: 10 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: root + password: 123456 + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 + lazy: true # 开启懒加载,保证启动速度 + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: root + password: 123456 + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 400-infra.server.iocoder.cn # 地址 + port: 6379 # 端口 + database: 1 # 数据库索引 +# password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + pay: + order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址 + refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址 + transfer-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址 + demo: true # 关闭演示模式 diff --git a/tashow-module/tashow-module-pay/src/main/resources/application-local.yaml b/tashow-module/tashow-module-pay/src/main/resources/application-local.yaml new file mode 100644 index 0000000..97082b3 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/resources/application-local.yaml @@ -0,0 +1,141 @@ +--- #################### 注册中心 + 配置中心相关配置 #################### + +spring: + cloud: + nacos: + server-addr: 127.0.0.1:8848 # Nacos 服务器地址 + username: # Nacos 账号 + password: # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + +--- #################### 数据库相关配置 #################### +spring: + # 数据源配置项 + autoconfigure: + exclude: + - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 + # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 + # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 + # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro # SQLServer 连接的示例 + # url: jdbc:dm://10.211.55.4:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 + username: root + password: 123456 + # username: sa # SQL Server 连接的示例 + # password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # SQL Server 连接的示例 + # username: SYSDBA # DM 连接的示例 + # password: SYSDBA # DM 连接的示例 + slave: # 模拟从库,可根据自己需要修改 + lazy: true # 开启懒加载,保证启动速度 + url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true + username: root + password: 123456 + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 0 # 数据库索引 + # password: 123456 # 密码,建议生产环境开启 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### +xxl: + job: + enabled: false # 是否开启调度中心,默认为 true 开启 + admin: + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + +# 日志文件配置 +logging: + level: + # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.pay.dal.mysql: debug + cn.iocoder.yudao.module.pay.dal.mysql.notify.com.tashow.cloud.payapi.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 PayNotifyTaskMapper 的日志级别为 info + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + env: # 多环境的配置项 + tag: ${HOSTNAME} + security: + mock-enable: true + access-log: # 访问日志的配置项 + enable: false + pay: + order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址 + refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址 + transfer-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址 \ No newline at end of file diff --git a/tashow-module/tashow-module-pay/src/main/resources/application.yaml b/tashow-module/tashow-module-pay/src/main/resources/application.yaml new file mode 100644 index 0000000..c3f0203 --- /dev/null +++ b/tashow-module/tashow-module-pay/src/main/resources/application.yaml @@ -0,0 +1,125 @@ +spring: + application: + name: pay-server + + profiles: + active: local + + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务 + + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +server: + port: 48085 + +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true # 1. 是否开启 Swagger 接文档的元数据 + path: /v3/api-docs + swagger-ui: + enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面 + path: /swagger-ui + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: false # TODO 芋艿:需要关闭增强,具体原因见:https://github.com/xiaoymin/knife4j/issues/874 + setting: + language: zh_cn + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${yudao.info.base-package}.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 关闭控制台的 Banner 打印 + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +# VO 转换(数据翻译)相关 +easy-trans: + is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口 + +--- #################### MQ 消息队列相关配置 #################### + +--- #################### 定时任务相关配置 #################### + +xxl: + job: + executor: + appname: ${spring.application.name} # 执行器 AppName + logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径 + accessToken: default_token # 执行器通讯TOKEN + +--- #################### 芋道相关配置 #################### + +yudao: + info: + version: 1.0.0 + base-package: cn.iocoder.yudao.module.pay + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + xss: + enable: false + exclude-urls: # 如下 url,仅仅是为了演示,去掉配置也没关系 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + swagger: + title: 管理后台 + description: 提供管理员管理的所有功能 + version: ${yudao.info.version} + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + - /admin-api/pay/notify/** # 支付回调通知,不携带租户编号 + +debug: false diff --git a/tashow-module/tashow-module-pay/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-pay/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..e69de29 diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java index 6c2e2b8..e24bfed 100644 --- a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/SystemServerApplication.java @@ -2,16 +2,11 @@ package com.tashow.cloud.system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; -import org.springframework.scheduling.annotation.EnableAsync; /** * 项目的启动类 - * @author 芋道源码 */ @SpringBootApplication -@EnableAsync // 开启异步 -//@EnableFeignClients(basePackages = "com.tashow.cloud.productapi.api") public class SystemServerApplication { public static void main(String[] args) { diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java index cc71677..8c10333 100644 --- a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java @@ -1,14 +1,15 @@ package com.tashow.cloud.system.controller.admin.sms.vo.log; -import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - import com.tashow.cloud.common.pojo.PageParam; -import java.time.LocalDateTime; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + /** 管理后台 - 短信日志分页 Request VO */ @Data @EqualsAndHashCode(callSuper = true) @@ -27,8 +28,8 @@ public class SmsLogPageReqVO extends PageParam { /** 发送状态,参见 SmsSendStatusEnum 枚举类 */ private Integer sendStatus; - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) /** 发送时间 */ + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] sendTime; /** 接收状态,参见 SmsReceiveStatusEnum 枚举类 */ diff --git a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java index 6a48c1d..0ac1494 100644 --- a/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java +++ b/tashow-module/tashow-module-system/src/main/java/com/tashow/cloud/system/service/permission/PermissionServiceImpl.java @@ -240,7 +240,7 @@ public class PermissionServiceImpl implements PermissionService { } @Override - @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") +// @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") public Set getUserRoleIdListByUserIdFromCache(Long userId) { return getUserRoleIdListByUserId(userId); } diff --git a/tashow-module/tashow-module-system/src/main/resources/application-local.yaml b/tashow-module/tashow-module-system/src/main/resources/application-local.yaml index a4d2d57..03bada2 100644 --- a/tashow-module/tashow-module-system/src/main/resources/application-local.yaml +++ b/tashow-module/tashow-module-system/src/main/resources/application-local.yaml @@ -5,11 +5,11 @@ spring: username: nacos # Nacos 账号 password: nacos # Nacos 密码 discovery: # 【配置中心】配置项 - namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + namespace: dev # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP metadata: version: 1.0.0 # 服务实例的版本号,可用于灰度发布 config: # 【注册中心】配置项 - namespace: 16bd40df-7cc7-4c2c-82c2-6186ade7bb08 # 命名空间。这里使用 dev 开发环境 + namespace: dev # 命名空间。这里使用 dev 开发环境 group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP diff --git a/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml index 16f0c0f..8c6b0f7 100644 --- a/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml +++ b/tashow-module/tashow-module-system/src/main/resources/logback-spring.xml @@ -1,7 +1,7 @@ - + diff --git a/tashow-module/tashow-module-trade/Dockerfile b/tashow-module/tashow-module-trade/Dockerfile new file mode 100644 index 0000000..b6aef79 --- /dev/null +++ b/tashow-module/tashow-module-trade/Dockerfile @@ -0,0 +1,19 @@ +## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性 +## 感谢复旦核博士的建议!灰子哥,牛皮! +FROM eclipse-temurin:21-jre + +## 创建目录,并使用它作为工作目录 +RUN mkdir -p /yudao-module-trade-biz +WORKDIR /yudao-module-trade-biz +## 将后端项目的 Jar 文件,复制到镜像中 +COPY ./target/yudao-module-trade-biz.jar app.jar + +## 设置 TZ 时区 +## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 +ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m" + +## 暴露后端项目的 48080 端口 +EXPOSE 48102 + +## 启动后端项目 +CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar diff --git a/tashow-module/tashow-module-trade/pom.xml b/tashow-module/tashow-module-trade/pom.xml new file mode 100644 index 0000000..ac90544 --- /dev/null +++ b/tashow-module/tashow-module-trade/pom.xml @@ -0,0 +1,112 @@ + + + + com.tashow.cloud + tashow-module + ${revision} + + 4.0.0 + tashow-module-trade + jar + + ${project.artifactId} + + trade 模块,主要实现交易相关功能 + 例如:订单、退款、购物车等功能。 + + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + + com.tashow.cloud + tashow-framework-monitor + + + + com.tashow.cloud + tashow-framework-env + + + + + com.tashow.cloud + tashow-trade-api + + + + + com.tashow.cloud + tashow-framework-web + + + + com.tashow.cloud + tashow-framework-security + + + + + com.tashow.cloud + tashow-data-mybatis + + + + com.tashow.cloud + tashow-data-redis + + + + + com.tashow.cloud + tashow-framework-rpc + + + + com.tashow.cloud + tashow-data-excel + + + com.tashow.cloud + tashow-pay-api + + + com.tashow.cloud + tashow-member-api + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + + diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/TradeServerApplication.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/TradeServerApplication.java new file mode 100644 index 0000000..cf819f0 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/TradeServerApplication.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.trade; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 项目的启动类 + * + */ +@SpringBootApplication +public class TradeServerApplication { + + public static void main(String[] args) { + SpringApplication.run(TradeServerApplication.class, args); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/api/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/api/package-info.java new file mode 100644 index 0000000..88efea9 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/api/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.trade.api; \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/AfterSaleController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/AfterSaleController.java new file mode 100644 index 0000000..ebb6dbc --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/AfterSaleController.java @@ -0,0 +1,160 @@ +package com.tashow.cloud.trade.controller.admin.aftersale; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.memberapi.api.user.MemberUserApi; +import com.tashow.cloud.payapi.api.notify.dto.PayRefundNotifyReqDTO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.*; +import com.tashow.cloud.trade.convert.aftersale.AfterSaleConvert; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleDO; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.service.aftersale.AfterSaleLogService; +import com.tashow.cloud.trade.service.aftersale.AfterSaleService; +import com.tashow.cloud.trade.service.order.TradeOrderQueryService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.servlet.ServletUtils.getClientIP; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +// 管理后台 - 售后订单 +@RestController +@RequestMapping("/trade/after-sale") +@Validated +@Slf4j +public class AfterSaleController { + + @Resource + private AfterSaleService afterSaleService; + @Resource + private TradeOrderQueryService tradeOrderQueryService; + @Resource + private AfterSaleLogService afterSaleLogService; + @Resource + private MemberUserApi memberUserApi; + + // 获得售后订单分页 + @GetMapping("/page") + @PreAuthorize("@ss.hasPermission('trade:after-sale:query')") + public CommonResult> getAfterSalePage(@Valid AfterSalePageReqVO pageVO) { + // 查询售后 + PageResult pageResult = afterSaleService.getAfterSalePage(pageVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 查询会员 +// Map memberUsers = memberUserApi.getUserMap( +// convertSet(pageResult.getList(), AfterSaleDO::getUserId)); + return success(AfterSaleConvert.INSTANCE.convertPage(pageResult, null)); + } + + @GetMapping("/get-detail") + // 获得售后订单详情 + // 售后编号 - 必填 - 示例: 1 + @PreAuthorize("@ss.hasPermission('trade:after-sale:query')") + public CommonResult getOrderDetail(@RequestParam("id") Long id) { + // 查询订单 + AfterSaleDO afterSale = afterSaleService.getAfterSale(id); + if (afterSale == null) { + return success(null); + } + + // 查询订单 + TradeOrderDO order = tradeOrderQueryService.getOrder(afterSale.getOrderId()); + // 查询订单项 + TradeOrderItemDO orderItem = tradeOrderQueryService.getOrderItem(afterSale.getOrderItemId()); + // 拼接数据 +// MemberUserRespDTO user = memberUserApi.getUser(afterSale.getUserId()).getCheckedData(); + List logs = afterSaleLogService.getAfterSaleLogList(afterSale.getId()); + return success(AfterSaleConvert.INSTANCE.convert(afterSale, order, orderItem, null, logs)); + } + + + /** + * 同意售后 + * @param id 售后编号 + * @return + */ + @PutMapping("/agree") + @PreAuthorize("@ss.hasPermission('trade:after-sale:agree')") + public CommonResult agreeAfterSale(@RequestParam("id") Long id) { + afterSaleService.agreeAfterSale(getLoginUserId(), id); + return success(true); + } + + /** + * 拒绝售后 + * @param confirmReqVO + * @return + */ + @PutMapping("/disagree") + @PreAuthorize("@ss.hasPermission('trade:after-sale:disagree')") + public CommonResult disagreeAfterSale(@RequestBody AfterSaleDisagreeReqVO confirmReqVO) { + afterSaleService.disagreeAfterSale(getLoginUserId(), confirmReqVO); + return success(true); + } + + /** + * 确认收货 + * @param id 售后编号 + * @return + */ + @PutMapping("/receive") + @PreAuthorize("@ss.hasPermission('trade:after-sale:receive')") + public CommonResult receiveAfterSale(@RequestParam("id") Long id) { + afterSaleService.receiveAfterSale(getLoginUserId(), id); + return success(true); + } + + /** + * 拒绝收货 + * @param refuseReqVO 售后编号 + * @return + */ + @PutMapping("/refuse") + @PreAuthorize("@ss.hasPermission('trade:after-sale:receive')") + public CommonResult refuseAfterSale(AfterSaleRefuseReqVO refuseReqVO) { + afterSaleService.refuseAfterSale(getLoginUserId(), refuseReqVO); + return success(true); + } + + /** + * 确认退款 + * @param id 售后编号 + * @return + */ + @PutMapping("/refund") + @PreAuthorize("@ss.hasPermission('trade:after-sale:refund')") + public CommonResult refundAfterSale(@RequestParam("id") Long id) { + afterSaleService.refundAfterSale(getLoginUserId(), getClientIP(), id); + return success(true); + } + + /** + * 更新售后订单为已退款 + * @param notifyReqDTO + * @return + */ + @PostMapping("/update-refunded") + @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现 + public CommonResult updateAfterRefund(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { + // 目前业务逻辑,不需要做任何事情 + // 当然,退款会有小概率会失败的情况,可以监控失败状态,进行告警 + log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/TradeAfterSaleController.http b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/TradeAfterSaleController.http new file mode 100644 index 0000000..f342e94 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/TradeAfterSaleController.http @@ -0,0 +1,33 @@ +### 获得交易售后分页 => 成功 +GET {{baseUrl}}/trade/after-sale/page?pageNo=1&pageSize=10 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 同意售后 => 成功 +PUT {{baseUrl}}/trade/after-sale/agree?id=7 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} +Content-Type: application/json + +### 拒绝售后 => 成功 +PUT {{baseUrl}}/trade/after-sale/disagree +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} +Content-Type: application/json + +{ + "id": 6, + "auditReason": "阿巴巴" +} + +### 确认退款 => 成功 +PUT {{baseUrl}}/trade/after-sale/refund?id=6 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} +Content-Type: application/json + +### 确认收货 => 成功 +PUT {{baseUrl}}/trade/after-sale/receive?id=7 +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} +Content-Type: application/json diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleBaseVO.java new file mode 100644 index 0000000..c53affa --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleBaseVO.java @@ -0,0 +1,120 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +/** +* 交易售后 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class AfterSaleBaseVO { + + // 售后流水号 - 必填 - 示例: 202211190847450020500077 + @NotNull(message = "售后流水号不能为空") + private String no; + + // 售后状态 - 必填 - 示例: 10 + @NotNull(message = "售后状态不能为空") + private Integer status; + + // 售后类型 - 必填 - 示例: 20 + @NotNull(message = "售后类型不能为空") + private Integer type; + + // 售后方式 - 必填 - 示例: 10 + @NotNull(message = "售后方式不能为空") + private Integer way; + + // 用户编号 - 必填 - 示例: 30337 + @NotNull(message = "用户编号不能为空") + private Long userId; + + // 申请原因 - 必填 - 示例: 不喜欢 + @NotNull(message = "申请原因不能为空") + private String applyReason; + + // 补充描述 - 示例: 你说的对 + private String applyDescription; + + // 补充凭证图片 - 示例: https://www.iocoder.cn/1.png + private List applyPicUrls; + + // 订单编号 - 必填 - 示例: 18078 + @NotNull(message = "订单编号不能为空") + private Long orderId; + + // 订单流水号 - 必填 - 示例: 2022111917190001 + @NotNull(message = "订单流水号不能为空") + private String orderNo; + + // 订单项编号 - 必填 - 示例: 572 + @NotNull(message = "订单项编号不能为空") + private Long orderItemId; + + // 商品 SPU 编号 - 必填 - 示例: 2888 + @NotNull(message = "商品 SPU 编号不能为空") + private Long spuId; + + // 商品 SPU 名称 - 必填 - 示例: 李四 + @NotNull(message = "商品 SPU 名称不能为空") + private String spuName; + + // 商品 SKU 编号 - 必填 - 示例: 15657 + @NotNull(message = "商品 SKU 编号不能为空") + private Long skuId; + + // 商品图片 - 示例: https://www.iocoder.cn/2.png + private String picUrl; + + // 购买数量 - 必填 - 示例: 20012 + @NotNull(message = "购买数量不能为空") + private Integer count; + + // 审批时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime auditTime; + + // 审批人 - 示例: 30835 + private Long auditUserId; + + // 审批备注 - 示例: 不香 + private String auditReason; + + // 退款金额,单位:分 - 必填 - 示例: 18077 + @NotNull(message = "退款金额,单位:分不能为空") + private Integer refundPrice; + + // 支付退款编号 - 示例: 10271 + private Long payRefundId; + + // 退款时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime refundTime; + + //退货物流公司编号 + private Long logisticsId; + + //退货物流单号 + private String logisticsNo; + + //退货时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime deliveryTime; + + //收货时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime receiveTime; + + //收货备注 + private String receiveReason; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleDetailRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleDetailRespVO.java new file mode 100644 index 0000000..72edc16 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleDetailRespVO.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.admin.aftersale.vo.log.AfterSaleLogRespVO; +import com.tashow.cloud.trade.controller.admin.base.member.user.MemberUserRespVO; +import com.tashow.cloud.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderBaseVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderItemBaseVO; +import lombok.Data; + +import java.util.List; + +// 管理后台 - 售后订单的详情 Response VO +@Data +public class AfterSaleDetailRespVO extends AfterSaleBaseVO { + + // 售后编号 - 必填 - 示例: 1024 + private Long id; + + + + /** + * 订单基本信息 + */ + private TradeOrderBaseVO order; + /** + * 订单项列表 + */ + private OrderItem orderItem; + + /** + * 用户信息 + */ + private MemberUserRespVO user; + + /** + * 售后日志 + */ + private List logs; + + // 管理后台 - 交易订单的详情的订单项目 + @Data + public static class OrderItem extends TradeOrderItemBaseVO { + + /** + * 属性数组 + */ + private List properties; + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleDisagreeReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleDisagreeReqVO.java new file mode 100644 index 0000000..3f0471a --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleDisagreeReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 交易售后拒绝 Request VO +@Data +public class AfterSaleDisagreeReqVO { + + // 售后编号 - 必填 - 示例: 1024 + @NotNull(message = "售后编号不能为空") + private Long id; + + // 审批备注 - 必填 - 示例: 你猜 + @NotEmpty(message = "审批备注不能为空") + private String auditReason; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java new file mode 100644 index 0000000..5d73635 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSalePageReqVO.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleStatusEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleTypeEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleWayEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 交易售后分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AfterSalePageReqVO extends PageParam { + + // 用户编号 - 示例: 1024 + private Long userId; + + // 售后流水号 - 示例: 202211190847450020500077 + private String no; + + // 售后状态 - 示例: 10 + @InEnum(value = AfterSaleStatusEnum.class, message = "售后状态必须是 {value}") + private Integer status; + + // 售后类型 - 示例: 20 + @InEnum(value = AfterSaleTypeEnum.class, message = "售后类型必须是 {value}") + private Integer type; + + // 售后方式 - 示例: 10 + @InEnum(value = AfterSaleWayEnum.class, message = "售后方式必须是 {value}") + private Integer way; + + // 订单编号 - 示例: 18078 + private String orderNo; + + // 商品 SPU 名称 - 示例: 李四 + private String spuName; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleRefuseReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleRefuseReqVO.java new file mode 100644 index 0000000..7b942d5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleRefuseReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 交易售后拒绝收货 Request VO +@Data +public class AfterSaleRefuseReqVO { + + // 售后编号 - 必填 - 示例: 1024 + @NotNull(message = "售后编号不能为空") + private Long id; + + // 收货备注 - 必填 - 示例: 你猜 + @NotNull(message = "收货备注不能为空") + private String refuseMemo; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java new file mode 100644 index 0000000..5fa387f --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/AfterSaleRespPageItemVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.admin.base.member.user.MemberUserRespVO; +import com.tashow.cloud.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +// 管理后台 - 交易售后分页项 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AfterSaleRespPageItemVO extends AfterSaleBaseVO { + + // 售后编号 - 必填 - 示例: 27630 + private Long id; + + // 创建时间 - 必填 + private LocalDateTime createTime; + + /** + * 商品属性数组 + */ + private List properties; + + /** + * 用户信息 + */ + private MemberUserRespVO user; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/log/AfterSaleLogRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/log/AfterSaleLogRespVO.java new file mode 100644 index 0000000..a046b46 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/aftersale/vo/log/AfterSaleLogRespVO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.trade.controller.admin.aftersale.vo.log; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; + +// 管理后台 - 交易售后日志 Response VO +@Data +public class AfterSaleLogRespVO { + + // 编号 - 必填 - 示例: 20669 + private Long id; + + // 用户编号 - 必填 - 示例: 22634 + private Long userId; + + // 用户类型 - 必填 - 示例: 2 + private Integer userType; + + // 售后编号 - 必填 - 示例: 3023 + private Long afterSaleId; + + // 售后状态(之前) - 示例: 2 + private Integer beforeStatus; + + // 售后状态(之后) - 必填 - 示例: 1 + private Integer afterStatus; + + // 操作明细 - 必填 - 示例: 维权完成,退款金额:¥37776.00 + private String content; + + // 创建时间 - 必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/member/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/member/package-info.java new file mode 100644 index 0000000..bd77464 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/member/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位符,可忽略 + */ +package com.tashow.cloud.trade.controller.admin.base.member; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/member/user/MemberUserRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/member/user/MemberUserRespVO.java new file mode 100644 index 0000000..7092a8d --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/member/user/MemberUserRespVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.controller.admin.base.member.user; + +// 移除Swagger相关导入 +import lombok.Data; + +// 管理后台 - 会员用户 Response VO +@Data +public class MemberUserRespVO { + + // 用户 ID - 必填 - 示例: 1 + private Long id; + + // 用户昵称 - 必填 - 示例: 芋道源码 + private String nickname; + + // 用户头像 - 示例: https://www.iocoder.cn/xxx.png + private String avatar; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/package-info.java new file mode 100644 index 0000000..9f93ed2 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/package-info.java @@ -0,0 +1,4 @@ +/** + * 放置该模块通用的 VO 类 + */ +package com.tashow.cloud.trade.controller.admin.base; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/product/property/ProductPropertyValueDetailRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/product/property/ProductPropertyValueDetailRespVO.java new file mode 100644 index 0000000..f825ad4 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/product/property/ProductPropertyValueDetailRespVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.controller.admin.base.product.property; + +// 移除Swagger相关导入 +import lombok.Data; + +// 管理后台 - 商品属性值的明细 Response VO +@Data +public class ProductPropertyValueDetailRespVO { + + // 属性的编号 - 必填 - 示例: 1 + private Long propertyId; + + // 属性的名称 - 必填 - 示例: 颜色 + private String propertyName; + + // 属性值的编号 - 必填 - 示例: 1024 + private Long valueId; + + // 属性值的名称 - 必填 - 示例: 红色 + private String valueName; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/system/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/system/package-info.java new file mode 100644 index 0000000..694594d --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/system/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位符,可忽略 + */ +package com.tashow.cloud.trade.controller.admin.base.system; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/system/user/UserSimpleBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/system/user/UserSimpleBaseVO.java new file mode 100644 index 0000000..1ca6338 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/base/system/user/UserSimpleBaseVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.controller.admin.base.system.user; + +// 移除Swagger相关导入 +import lombok.Data; + +// 用户精简信息 VO +@Data +public class UserSimpleBaseVO { + + // 用户编号 - 必填 - 示例: 1 + private Long id; + + // 用户昵称 - 必填 - 示例: 芋艿 + private String nickname; + + // 用户头像 - 示例: https://www.iocoder.cn/1.png + private String avatar; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryExpressController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryExpressController.java new file mode 100644 index 0000000..0c94867 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryExpressController.java @@ -0,0 +1,96 @@ +package com.tashow.cloud.trade.controller.admin.delivery; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.excel.excel.core.util.ExcelUtils; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.*; +import com.tashow.cloud.trade.convert.delivery.DeliveryExpressConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressService; +import com.tashow.cloud.web.apilog.core.annotation.ApiAccessLog; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.web.apilog.core.enums.OperateTypeEnum.EXPORT; + + +// 管理后台 - 快递公司 +@RestController +@RequestMapping("/trade/delivery/express") +@Validated +public class DeliveryExpressController { + + @Resource + private DeliveryExpressService deliveryExpressService; + + @PostMapping("/create") + // 创建快递公司 + @PreAuthorize("@ss.hasPermission('trade:delivery:express:create')") + public CommonResult createDeliveryExpress(@Valid @RequestBody DeliveryExpressCreateReqVO createReqVO) { + return success(deliveryExpressService.createDeliveryExpress(createReqVO)); + } + + @PutMapping("/update") + // 更新快递公司 + @PreAuthorize("@ss.hasPermission('trade:delivery:express:update')") + public CommonResult updateDeliveryExpress(@Valid @RequestBody DeliveryExpressUpdateReqVO updateReqVO) { + deliveryExpressService.updateDeliveryExpress(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + // 删除快递公司 + // 编号 - 必填 + @PreAuthorize("@ss.hasPermission('trade:delivery:express:delete')") + public CommonResult deleteDeliveryExpress(@RequestParam("id") Long id) { + deliveryExpressService.deleteDeliveryExpress(id); + return success(true); + } + + @GetMapping("/get") + // 获得快递公司 + // 编号 - 必填,示例:1024 + @PreAuthorize("@ss.hasPermission('trade:delivery:express:query')") + public CommonResult getDeliveryExpress(@RequestParam("id") Long id) { + DeliveryExpressDO deliveryExpress = deliveryExpressService.getDeliveryExpress(id); + return success(DeliveryExpressConvert.INSTANCE.convert(deliveryExpress)); + } + + @GetMapping("/list-all-simple") + // 获取快递公司精简信息列表 - 主要用于前端的下拉选项 + public CommonResult> getSimpleDeliveryExpressList() { + List list = deliveryExpressService.getDeliveryExpressListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(DeliveryExpressConvert.INSTANCE.convertList1(list)); + } + + @GetMapping("/page") + // 获得快递公司分页 + @PreAuthorize("@ss.hasPermission('trade:delivery:express:query')") + public CommonResult> getDeliveryExpressPage(@Valid DeliveryExpressPageReqVO pageVO) { + PageResult pageResult = deliveryExpressService.getDeliveryExpressPage(pageVO); + return success(DeliveryExpressConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/export-excel") + // 导出快递公司 Excel + @PreAuthorize("@ss.hasPermission('trade:delivery:express:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportDeliveryExpressExcel(@Valid DeliveryExpressExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = deliveryExpressService.getDeliveryExpressList(exportReqVO); + // 导出 Excel + List dataList = DeliveryExpressConvert.INSTANCE.convertList02(list); + ExcelUtils.write(response, "快递公司.xls", "数据", DeliveryExpressExcelVO.class, dataList); + } +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryExpressTemplateController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryExpressTemplateController.java new file mode 100644 index 0000000..19c4b47 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryExpressTemplateController.java @@ -0,0 +1,88 @@ +package com.tashow.cloud.trade.controller.admin.delivery; + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.*; +import com.tashow.cloud.trade.convert.delivery.DeliveryExpressTemplateConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressTemplateService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 管理后台 - 快递运费模板 +@RestController +@RequestMapping("/trade/delivery/express-template") +@Validated +public class DeliveryExpressTemplateController { + + @Resource + private DeliveryExpressTemplateService deliveryExpressTemplateService; + + @PostMapping("/create") + // 创建快递运费模板 + @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:create')") + public CommonResult createDeliveryExpressTemplate(@Valid @RequestBody DeliveryExpressTemplateCreateReqVO createReqVO) { + return success(deliveryExpressTemplateService.createDeliveryExpressTemplate(createReqVO)); + } + + @PutMapping("/update") + // 更新快递运费模板 + @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:update')") + public CommonResult updateDeliveryExpressTemplate(@Valid @RequestBody DeliveryExpressTemplateUpdateReqVO updateReqVO) { + deliveryExpressTemplateService.updateDeliveryExpressTemplate(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + // 删除快递运费模板 + // 编号 - 必填 + @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:delete')") + public CommonResult deleteDeliveryExpressTemplate(@RequestParam("id") Long id) { + deliveryExpressTemplateService.deleteDeliveryExpressTemplate(id); + return success(true); + } + + @GetMapping("/get") + // 获得快递运费模板 + // 编号 - 必填,示例:1024 + @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')") + public CommonResult getDeliveryExpressTemplate(@RequestParam("id") Long id) { + return success(deliveryExpressTemplateService.getDeliveryExpressTemplate(id)); + } + + @GetMapping("/list") + // 获得快递运费模板列表 + // 编号列表 - 必填,示例:1024,2048 + @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')") + public CommonResult> getDeliveryExpressTemplateList(@RequestParam("ids") Collection ids) { + List list = deliveryExpressTemplateService.getDeliveryExpressTemplateList(ids); + return success(DeliveryExpressTemplateConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/list-all-simple") + // 获取快递模版精简信息列表 - 主要用于前端的下拉选项 + public CommonResult> getSimpleTemplateList() { + // 获取运费模版列表,只要开启状态的 + List list = deliveryExpressTemplateService.getDeliveryExpressTemplateList(); + // 排序后,返回给前端 + return success(DeliveryExpressTemplateConvert.INSTANCE.convertList1(list)); + } + + @GetMapping("/page") + // 获得快递运费模板分页 + @PreAuthorize("@ss.hasPermission('trade:delivery:express-template:query')") + public CommonResult> getDeliveryExpressTemplatePage(@Valid DeliveryExpressTemplatePageReqVO pageVO) { + PageResult pageResult = deliveryExpressTemplateService.getDeliveryExpressTemplatePage(pageVO); + return success(DeliveryExpressTemplateConvert.INSTANCE.convertPage(pageResult)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryPickUpStoreController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryPickUpStoreController.java new file mode 100644 index 0000000..8607c81 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/DeliveryPickUpStoreController.java @@ -0,0 +1,111 @@ +package com.tashow.cloud.trade.controller.admin.delivery; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import com.tashow.cloud.systemapi.api.user.dto.AdminUserRespDTO; +import com.tashow.cloud.trade.controller.admin.base.system.user.UserSimpleBaseVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.*; +import com.tashow.cloud.trade.convert.delivery.DeliveryPickUpStoreConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import com.tashow.cloud.trade.service.delivery.DeliveryPickUpStoreService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 管理后台 - 自提门店 +@RestController +@RequestMapping("/trade/delivery/pick-up-store") +@Validated +public class DeliveryPickUpStoreController { + + @Resource + private DeliveryPickUpStoreService deliveryPickUpStoreService; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + // 创建自提门店 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:create')") + public CommonResult createDeliveryPickUpStore(@Valid @RequestBody DeliveryPickUpStoreCreateReqVO createReqVO) { + return success(deliveryPickUpStoreService.createDeliveryPickUpStore(createReqVO)); + } + + @PutMapping("/update") + // 更新自提门店 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:update')") + public CommonResult updateDeliveryPickUpStore(@Valid @RequestBody DeliveryPickUpStoreUpdateReqVO updateReqVO) { + deliveryPickUpStoreService.updateDeliveryPickUpStore(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + // 删除自提门店 + // 编号 - 必填 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:delete')") + public CommonResult deleteDeliveryPickUpStore(@RequestParam("id") Long id) { + deliveryPickUpStoreService.deleteDeliveryPickUpStore(id); + return success(true); + } + + @GetMapping("/get") + // 获得自提门店 + // 编号 - 必填,示例:1024 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:query')") + public CommonResult getDeliveryPickUpStore(@RequestParam("id") Long id) { + DeliveryPickUpStoreDO deliveryPickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(id); + if (deliveryPickUpStore == null) { + return success(null); + } + List verifyUsers = CollUtil.isNotEmpty(deliveryPickUpStore.getVerifyUserIds()) ? + adminUserApi.getUserList(deliveryPickUpStore.getVerifyUserIds()).getCheckedData() : null; + return success(BeanUtils.toBean(deliveryPickUpStore, DeliveryPickUpStoreRespVO.class) + .setVerifyUsers(BeanUtils.toBean(verifyUsers, UserSimpleBaseVO.class))); + } + + @GetMapping("/simple-list") + // 获得自提门店精简信息列表 + public CommonResult> getSimpleDeliveryPickUpStoreList() { + List list = deliveryPickUpStoreService.getDeliveryPickUpStoreListByStatus( + CommonStatusEnum.ENABLE.getStatus()); + return success(DeliveryPickUpStoreConvert.INSTANCE.convertList1(list)); + } + + @GetMapping("/list") + // 获得自提门店列表 + // 编号列表 - 必填,示例:1024,2048 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:query')") + public CommonResult> getDeliveryPickUpStoreList(@RequestParam("ids") Collection ids) { + List list = deliveryPickUpStoreService.getDeliveryPickUpStoreList(ids); + return success(DeliveryPickUpStoreConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/page") + // 获得自提门店分页 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:query')") + public CommonResult> getDeliveryPickUpStorePage(@Valid DeliveryPickUpStorePageReqVO pageVO) { + PageResult pageResult = deliveryPickUpStoreService.getDeliveryPickUpStorePage(pageVO); + return success(DeliveryPickUpStoreConvert.INSTANCE.convertPage(pageResult)); + } + + @PostMapping("/bind") + // 绑定自提店员 + @PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:create')") + public CommonResult bindDeliveryPickUpStore(@Valid @RequestBody DeliveryPickUpBindReqVO bindReqVO) { + deliveryPickUpStoreService.bindDeliveryPickUpStore(bindReqVO); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressBaseVO.java new file mode 100644 index 0000000..e078879 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressBaseVO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** +* 快递公司 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class DeliveryExpressBaseVO { + + // 快递公司编码 - 必填 + @NotNull(message = "快递公司编码不能为空") + private String code; + + // 快递公司名称 - 必填,示例:李四 + @NotNull(message = "快递公司名称不能为空") + private String name; + + // 快递公司logo + private String logo; + + // 排序 - 必填 + @NotNull(message = "排序不能为空") + private Integer sort; + + // 状态 - 必填,示例:1 + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressCreateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressCreateReqVO.java new file mode 100644 index 0000000..7dcceaa --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressCreateReqVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 快递公司创建 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressCreateReqVO extends DeliveryExpressBaseVO { + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressExcelVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressExcelVO.java new file mode 100644 index 0000000..94e110c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressExcelVO.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.tashow.cloud.excel.excel.core.annotations.DictFormat; +import com.tashow.cloud.excel.excel.core.convert.DictConvert; +import com.tashow.cloud.systemapi.enums.DictTypeConstants; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 快递公司 Excel VO + */ +@Data +public class DeliveryExpressExcelVO { + + @ExcelProperty("编号") + private Long id; + + @ExcelProperty("快递公司编码") + private String code; + + @ExcelProperty("快递公司名称") + private String name; + + @ExcelProperty("快递公司 logo") + private String logo; + + @ExcelProperty("排序") + private Integer sort; + + @ExcelProperty(value = "状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.COMMON_STATUS) + private Integer status; + + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressExportReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressExportReqVO.java new file mode 100644 index 0000000..ef15733 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressExportReqVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 快递公司 Excel 导出 Request VO +@Data +public class DeliveryExpressExportReqVO { + + // 快递公司编码 + private String code; + + // 快递公司名称,示例:李四 + private String name; + + // 状态(0正常 1停用),示例:1 + private Integer status; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressPageReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressPageReqVO.java new file mode 100644 index 0000000..433e5ca --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressPageReqVO.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 快递公司分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressPageReqVO extends PageParam { + + // 快递公司编码 + private String code; + + // 快递公司名称,示例:李四 + private String name; + + // 状态(0正常 1停用),示例:1 + private Integer status; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressRespVO.java new file mode 100644 index 0000000..1fadca0 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressRespVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 快递公司 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressRespVO extends DeliveryExpressBaseVO { + + // 编号 - 必填,示例:6592 + private Long id; + + // 创建时间 - 必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressSimpleRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressSimpleRespVO.java new file mode 100644 index 0000000..340cebf --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressSimpleRespVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +// 管理后台 - 快递公司精简信息 Response VO +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeliveryExpressSimpleRespVO { + + // 编号 - 必填,示例:6592 + @NotNull(message = "编号不能为空") + private Long id; + + // 快递公司名称 - 必填,示例:顺丰速运 + @NotNull(message = "快递公司名称不能为空") + private String name; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressUpdateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressUpdateReqVO.java new file mode 100644 index 0000000..dc34086 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/express/DeliveryExpressUpdateReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.express; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 快递公司更新 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressUpdateReqVO extends DeliveryExpressBaseVO { + + // 编号 - 必填,示例:6592 + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateBaseVO.java new file mode 100644 index 0000000..b90e096 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateBaseVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** +* 快递运费模板 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class DeliveryExpressTemplateBaseVO { + + // 模板名称 - 必填,示例:王五 + @NotNull(message = "模板名称不能为空") + private String name; + + // 配送计费方式 1:按件 2:按重量 3:按体积 - 必填 + @NotNull(message = "配送计费方式 1:按件 2:按重量 3:按体积不能为空") + private Integer chargeMode; + + // 排序 - 必填 + @NotNull(message = "排序不能为空") + private Integer sort; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateChargeBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateChargeBaseVO.java new file mode 100644 index 0000000..2644650 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateChargeBaseVO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 快递运费模板运费设置 Base VO,提供给添加运费模板使用 + */ +@Data +public class DeliveryExpressTemplateChargeBaseVO { + + // 编号 - 示例:6592 (hidden) + private Long id; + + // 区域编号列表 - 必填,示例:[1,120000] + @NotEmpty(message = "区域编号列表不能为空") + private List areaIds; + + // 首件数量 - 必填,示例:5 + @NotNull(message = "首件数量不能为空") + private Double startCount; + + // 起步价 - 必填,示例:1000 + @NotNull(message = "起步价不能为空") + private Integer startPrice; + + // 续件数量 - 必填,示例:10 + @NotNull(message = "续件数量不能为空") + private Double extraCount; + + // 额外价 - 必填,示例:2000 + @NotNull(message = "额外价不能为空") + private Integer extraPrice; +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateCreateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateCreateReqVO.java new file mode 100644 index 0000000..3223e09 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateCreateReqVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import jakarta.validation.Valid; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +// 管理后台 - 快递运费模板创建 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressTemplateCreateReqVO extends DeliveryExpressTemplateBaseVO { + + // 区域运费列表 + @Valid + private List charges; + + // 包邮区域列表 + @Valid + private List frees; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateDetailRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateDetailRespVO.java new file mode 100644 index 0000000..be09bf6 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateDetailRespVO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +// 管理后台 - 快递运费模板的详细 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressTemplateDetailRespVO extends DeliveryExpressTemplateBaseVO { + + // 编号 - 必填,示例:371 + private Long id; + + // 运费模板运费设置 - 必填 + private List charges; + + // 运费模板包邮区域 - 必填 + private List frees; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateFreeBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateFreeBaseVO.java new file mode 100644 index 0000000..8d2d909 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateFreeBaseVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 快递运费模板包邮 Base VO,提供给添加运费模板使用 + */ +@Data +public class DeliveryExpressTemplateFreeBaseVO { + + // 区域编号列表 - 必填,示例:[1,120000] + @NotEmpty(message = "区域编号列表不能为空") + private List areaIds; + + // 包邮金额 - 必填,示例:5000 + @NotNull(message = "包邮金额不能为空") + private Integer freePrice; + + // 包邮件数 - 必填,示例:5 + @NotNull(message = "包邮件数不能为空") + private Integer freeCount; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplatePageReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplatePageReqVO.java new file mode 100644 index 0000000..2db7902 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplatePageReqVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 快递运费模板分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressTemplatePageReqVO extends PageParam { + + // 模板名称 - 示例:王五 + private String name; + + // 配送计费方式 1:按件 2:按重量 3:按体积 + private Integer chargeMode; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateRespVO.java new file mode 100644 index 0000000..111fa6d --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateRespVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +// 管理后台 - 快递运费模板 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressTemplateRespVO extends DeliveryExpressTemplateBaseVO { + + // 编号,自增 - 必填,示例:371 + private Long id; + + // 创建时间 - 必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateSimpleRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateSimpleRespVO.java new file mode 100644 index 0000000..e34a1d5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateSimpleRespVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + + +// 管理后台 - 模版精简信息 Response VO +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeliveryExpressTemplateSimpleRespVO { + + // 模版编号 - 必填,示例:1024 + private Long id; + + // 模板名称 - 必填,示例:测试模版 + private String name; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateUpdateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateUpdateReqVO.java new file mode 100644 index 0000000..080b173 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/expresstemplate/DeliveryExpressTemplateUpdateReqVO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate; + +// 移除Swagger相关导入 +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +// 管理后台 - 快递运费模板更新 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryExpressTemplateUpdateReqVO extends DeliveryExpressTemplateBaseVO { + + // 编号 - 必填,示例:371 + @NotNull(message = "编号不能为空") + private Long id; + + // 区域运费列表 + @Valid + private List charges; + + // 包邮区域列表 + @Valid + private List frees; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpBindReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpBindReqVO.java new file mode 100644 index 0000000..2d34447 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpBindReqVO.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.ToString; + +import java.util.List; + +// 管理后台 - 自提门店绑定核销人 Request VO +@Data +@ToString(callSuper = true) +public class DeliveryPickUpBindReqVO { + + // 编号 - 必填,示例:23128 + @NotNull(message = "编号不能为空") + private Long id; + + // 绑定用户编号组数 - 必填,示例:23128 + @NotEmpty(message = "绑定用户编号组数不能未空") + private List verifyUserIds; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreBaseVO.java new file mode 100644 index 0000000..4b4a40b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreBaseVO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalTime; + +/** +* 自提门店 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class DeliveryPickUpStoreBaseVO { + + // 门店名称 - 必填,示例:李四 + @NotBlank(message = "门店名称不能为空") + private String name; + + // 门店简介 - 示例:我是门店简介 + private String introduction; + + // 门店手机 - 必填,示例:15601892312 + @NotBlank(message = "门店手机不能为空") + @Mobile + private String phone; + + // 区域编号 - 必填,示例:18733 + @NotNull(message = "区域编号不能为空") + private Integer areaId; + + // 门店详细地址 - 必填,示例:复旦大学路 188 号 + @NotBlank(message = "门店详细地址不能为空") + private String detailAddress; + + // 门店 logo - 必填,示例:https://www.iocoder.cn/1.png + @NotBlank(message = "门店 logo 不能为空") + private String logo; + + // 营业开始时间 - 必填 + @NotNull(message = "营业开始时间不能为空") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm") + private LocalTime openingTime; + + // 营业结束时间 - 必填 + @NotNull(message = "营业结束时间不能为空") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm") + private LocalTime closingTime; + + // 纬度 - 必填,示例:5.88 + @NotNull(message = "纬度不能为空") + private Double latitude; + + // 经度 - 必填,示例:6.99 + @NotNull(message = "经度不能为空") + private Double longitude; + + // 门店状态 - 必填,示例:1 + @NotNull(message = "门店状态不能为空") + @InEnum(CommonStatusEnum.class) + private Integer status; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreCreateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreCreateReqVO.java new file mode 100644 index 0000000..f199d80 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreCreateReqVO.java @@ -0,0 +1,14 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +// 移除Swagger相关导入 +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 自提门店创建 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryPickUpStoreCreateReqVO extends DeliveryPickUpStoreBaseVO { + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStorePageReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStorePageReqVO.java new file mode 100644 index 0000000..e818352 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStorePageReqVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.validation.InEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 自提门店分页 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryPickUpStorePageReqVO extends PageParam { + + // 门店名称 - 示例:李四 + private String name; + + // 门店手机 + private String phone; + + // 区域编号 - 示例:18733 + private Integer areaId; + + // 门店状态 - 示例:1 + @InEnum(CommonStatusEnum.class) + private Integer status; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java new file mode 100644 index 0000000..294b1ce --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreRespVO.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.admin.base.system.user.UserSimpleBaseVO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.List; + +// 管理后台 - 自提门店 Response VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryPickUpStoreRespVO extends DeliveryPickUpStoreBaseVO { + + // 编号 - 必填,示例:23128 + private Long id; + + // 创建时间 - 必填 + private LocalDateTime createTime; + + // 核销用户数组 - 必填 + private List verifyUsers; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreSimpleRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreSimpleRespVO.java new file mode 100644 index 0000000..7b43605 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreSimpleRespVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +// 移除Swagger相关导入 +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +// 管理后台 - 自提门店精简信息 Response VO +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeliveryPickUpStoreSimpleRespVO { + + // 编号 - 必填,示例:23128 + private Long id; + + // 门店名称 - 必填,示例:李四 + private String name; + + // 门店手机 - 必填,示例:15601892312 + private String phone; + + // 区域编号 - 必填,示例:18733 + private Integer areaId; + + // 区域名称 - 必填,示例:xx市 + private String areaName; + + // 门店详细地址 - 必填,示例:复旦大学路 188 号 + private String detailAddress; + + // 绑定用户编号组数 - 必填,示例:23128 + private List verifyUserIds; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreUpdateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreUpdateReqVO.java new file mode 100644 index 0000000..2336876 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreUpdateReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.controller.admin.delivery.vo.pickup; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +// 管理后台 - 自提门店更新 Request VO +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DeliveryPickUpStoreUpdateReqVO extends DeliveryPickUpStoreBaseVO { + + // 编号 - 必填,示例:23128 + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/TradeOrderController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/TradeOrderController.java new file mode 100644 index 0000000..7785b3c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/TradeOrderController.java @@ -0,0 +1,178 @@ +package com.tashow.cloud.trade.controller.admin.order; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.memberapi.api.user.MemberUserApi; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderDetailRespVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageRespVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageReqVO; +import com.tashow.cloud.trade.convert.order.TradeOrderConvert; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.service.order.TradeOrderLogService; +import com.tashow.cloud.trade.service.order.TradeOrderQueryService; +import com.tashow.cloud.trade.service.order.TradeOrderUpdateService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; + + +/** + * 管理后台 - 交易订单 + */ +@RestController +@RequestMapping("/trade/order") +@Validated +@Slf4j +public class TradeOrderController { + + @Resource + private TradeOrderUpdateService tradeOrderUpdateService; + @Resource + private TradeOrderQueryService tradeOrderQueryService; + @Resource + private TradeOrderLogService tradeOrderLogService; + + @Resource + private MemberUserApi memberUserApi; + + /** + * 获得交易订单分页 + * @param reqVO + * @return + */ + @GetMapping("/page") + @PermitAll +// @PreAuthorize("@ss.hasPermission('trade:order:query')") + public CommonResult> getOrderPage(@Valid TradeOrderPageReqVO reqVO) { + // 查询订单 + PageResult pageResult = tradeOrderQueryService.getOrderPage(reqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + +// // 查询用户信息 +// Set userIds = CollUtil.unionDistinct(convertList(pageResult.getList(), TradeOrderDO::getUserId), +// convertList(pageResult.getList(), TradeOrderDO::getBrokerageUserId, Objects::nonNull)); +// Map userMap = memberUserApi.getUserMap(userIds); + // 查询订单项 + List orderItems = tradeOrderQueryService.getOrderItemListByOrderId( + convertSet(pageResult.getList(), TradeOrderDO::getId)); + // 最终组合 + return success(TradeOrderConvert.INSTANCE.convertPage(pageResult, orderItems, null)); + } + + /** + * 获得交易订单详情 + * @param id 订单编号 + * @return + */ + @GetMapping("/get-detail") + @PermitAll +// @PreAuthorize("@ss.hasPermission('trade:order:query')") + public CommonResult getOrderDetail(@RequestParam("id") Long id) { + // 查询订单 + TradeOrderDO order = tradeOrderQueryService.getOrder(id); + if (order == null) { + return success(null); + } + // 查询订单项 + List orderItems = tradeOrderQueryService.getOrderItemListByOrderId(id); + + // 拼接数据 +// MemberUserRespDTO user = memberUserApi.getUser(order.getUserId()).getCheckedData(); +// MemberUserRespDTO brokerageUser = order.getBrokerageUserId() != null ? +// memberUserApi.getUser(order.getBrokerageUserId()).getCheckedData() : null; + List orderLogs = tradeOrderLogService.getOrderLogListByOrderId(id); + return success(TradeOrderConvert.INSTANCE.convert(order, orderItems, orderLogs, null, null)); + } + +// // 获得交易订单统计 +// @GetMapping("/summary") +// @PreAuthorize("@ss.hasPermission('trade:order:query')") +// public CommonResult getOrderSummary(TradeOrderPageReqVO reqVO) { +// return success(tradeOrderQueryService.getOrderSummary(reqVO)); +// } +// +// // 获得交易订单的物流轨迹 +// // id: 交易订单编号 +// @GetMapping("/get-express-track-list") +// @PreAuthorize("@ss.hasPermission('trade:order:query')") +// public CommonResult> getOrderExpressTrackList(@RequestParam("id") Long id) { +// return success(TradeOrderConvert.INSTANCE.convertList02( +// tradeOrderQueryService.getExpressTrackList(id))); +// } +// +// // 订单发货 +// @PutMapping("/delivery") +// @PreAuthorize("@ss.hasPermission('trade:order:update')") +// public CommonResult deliveryOrder(@RequestBody TradeOrderDeliveryReqVO deliveryReqVO) { +// tradeOrderUpdateService.deliveryOrder(deliveryReqVO); +// return success(true); +// } +// +// // 订单备注 +// @PutMapping("/update-remark") +// @PreAuthorize("@ss.hasPermission('trade:order:update')") +// public CommonResult updateOrderRemark(@RequestBody TradeOrderRemarkReqVO reqVO) { +// tradeOrderUpdateService.updateOrderRemark(reqVO); +// return success(true); +// } +// +// // 订单调价 +// @PutMapping("/update-price") +// @PreAuthorize("@ss.hasPermission('trade:order:update')") +// public CommonResult updateOrderPrice(@RequestBody TradeOrderUpdatePriceReqVO reqVO) { +// tradeOrderUpdateService.updateOrderPrice(reqVO); +// return success(true); +// } +// +// // 修改订单收货地址 +// @PutMapping("/update-address") +// @PreAuthorize("@ss.hasPermission('trade:order:update')") +// public CommonResult updateOrderAddress(@RequestBody TradeOrderUpdateAddressReqVO reqVO) { +// tradeOrderUpdateService.updateOrderAddress(reqVO); +// return success(true); +// } +// +// // 订单核销 +// // id: 交易订单编号 +// @PutMapping("/pick-up-by-id") +// @PreAuthorize("@ss.hasPermission('trade:order:pick-up')") +// public CommonResult pickUpOrderById(@RequestParam("id") Long id) { +// tradeOrderUpdateService.pickUpOrderByAdmin(getLoginUserId(), id); +// return success(true); +// } +// +// // 订单核销 +// // pickUpVerifyCode: 自提核销码 +// @PutMapping("/pick-up-by-verify-code") +// @PreAuthorize("@ss.hasPermission('trade:order:pick-up')") +// public CommonResult pickUpOrderByVerifyCode(@RequestParam("pickUpVerifyCode") String pickUpVerifyCode) { +// tradeOrderUpdateService.pickUpOrderByAdmin(getLoginUserId(), pickUpVerifyCode); +// return success(true); +// } +// +// // 查询核销码对应的订单 +// // pickUpVerifyCode: 自提核销码 +// @GetMapping("/get-by-pick-up-verify-code") +// @PreAuthorize("@ss.hasPermission('trade:order:query')") +// public CommonResult getByPickUpVerifyCode(@RequestParam("pickUpVerifyCode") String pickUpVerifyCode) { +// TradeOrderDO tradeOrder = tradeOrderUpdateService.getByPickUpVerifyCode(pickUpVerifyCode); +// return success(TradeOrderConvert.INSTANCE.convert2(tradeOrder, null)); +// } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderBaseVO.java new file mode 100644 index 0000000..f4397a1 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderBaseVO.java @@ -0,0 +1,71 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 交易订单 Base VO,提供给添加、修改、详细的子 VO 使用 + */ +@Data +public class TradeOrderBaseVO { + + // 订单编号 + private Long id; + + // 订单流水号 + private String orderNum; + + // 下单时间 + private LocalDateTime createTime; + + // 订单类目 + private String orderCategoryName; + + // 订单来源 + private Integer orderTerminal; + + // 用户编号 + private Long userId; + // 用户姓名 + private String userName; + //用户昵称 + private String userNickName; + // 用户头像 + private String userAvatar; + //用户手机号 + private String userMobile; + // 订单状态 + private Integer orderStatus; + + //商品图片 + private String picUrl; + //商品名称 + private String spuName; + //商品规格 + private String skuName; + // 购买的商品数量 + private Integer count; + // 单价 + private Integer price; + //到手价 + private Integer handedPrice; + //实付金额 + private Integer payPrice; + // 单位 + private String unit; + + //预约时间 + private String orderTime; + //服务地址 + private String serveAddress; + // 用户备注 - 必填,示例:你猜 + private String userRemark; + //支付方式 + private String payType; + //财务状态 + private String financeStatus; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderDeliveryReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderDeliveryReqVO.java new file mode 100644 index 0000000..36e735f --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderDeliveryReqVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 订单发货 Request VO +@Data +public class TradeOrderDeliveryReqVO { + + // 订单编号 - 必填,示例:1024 + @NotNull(message = "订单编号不能为空") + private Long id; + + // 发货物流公司编号,示例:1 + @NotNull(message = "发货物流公司不能为空") + private Long logisticsId; + + // 发货物流单号,示例:SF123456789 + private String logisticsNo; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java new file mode 100644 index 0000000..a87ae08 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderDetailRespVO.java @@ -0,0 +1,115 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +// 管理后台 - 交易订单的详情 Response VO +@Data +@Accessors(chain = true) +public class TradeOrderDetailRespVO extends TradeOrderBaseVO { + + //基本信息 + private TradeOrderBaseInfo tradeOrderInfoBase; + //配送信息 + private TradeDeliveryInfo tradeDeliveryInfo; + //商品信息 + private String tradeProductInfo; + //扩展服务信息 + private String tradeExtendServeInfo; + //附加费信息 + private String tradeExtendCostInfo; + + + @Data + @Accessors(chain = true) + class TradeOrderBaseInfo{ + //订单id + private String id; + //订单编号 + private String orderNo; + //订单状态 + private String orderStatus; + //订单类型 + private String orderType; + //订单来源 + private String orderTerminal; + //创建时间 + private LocalDateTime createTime; + //完成时间 + private LocalDateTime finishTime; + //取消时间 + private LocalDateTime cancelTime; + //订单金额 + private String payPrice; + //优惠金额 + private String discountPrice; + //退款金额 + private String refundPrice; + //实收金额 + private String actualPrice; + //支付方式 + private String payChannel; + //交易流水号 + private String payOrderId; + //支付时间 + private LocalDateTime payTime; + // 退款状态 - 必填,示例:待审核/待退款/已退款/已拒绝 + private String refundStatus; + // 售后编号 - 必填,示例:450878 + private String afterSaleCode; + // 售后类型 - 必填,示例:仅退款/退货退款/可扩展增加使用 + private String afterSaleType; + // 退款类型 - 必填,示例:订单退款/差价退款/运费退款/可扩展增加 + private String refundType; + // 退款原因 - 必填,示例:不想要了 + private String refundReason; + // 退款说明 - 必填,示例:这里是用户填写的退款描述 + private String refundRemark; + // 退款金额 - 必填 + private Integer refundAmount; + // 申请人 - 必填,示例:用户/商家/平台+ID + private String applicant; + // 审核人 - 必填,示例:姓名+ID + private String auditor; + // 审核时间 - 必填,示例:2025-07-01 12:00:00 + private LocalDateTime auditTime; + // 退款方式 - 必填,示例:系统自动退款 + private String refundMethod; + // 退款至 - 必填,示例:原支付方式返还 + private String refundTo; + // 关闭时间 - 必填,示例:2025-07-01 12:00:00 + private LocalDateTime closeTime; + //用户昵称 示例:钱多多 + private String userNickname; + //用户ID 示例:666 + private Long userId; + + } + + @Data + @Accessors(chain = true) + class TradeDeliveryInfo{ + //承运方 + private String logisticsName; + //送货方式 + private String logisticsType; + //送货上门 + private String logisticsNum; + //收货人 + private String receiverName; + //收货手机 + private String receiverMobile; + //收货地址 + private String receiverDetailAddress; + //快递详情 + private String deliveryDetail; + } + + + + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderItemBaseVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderItemBaseVO.java new file mode 100644 index 0000000..bc60785 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderItemBaseVO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +/** + * 交易订单项 Base VO,提供给添加、修改、详细的子 VO 使用 + * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 + */ +@Data +public class TradeOrderItemBaseVO { + + // ========== 订单项基本信息 ========== + + // 编号 - 必填,示例:1 + private Long id; + + // 用户编号 - 必填,示例:1 + private Long userId; + + // 订单编号 - 必填,示例:1 + private Long orderId; + + // ========== 商品基本信息 ========== + + // 商品 SPU 编号 - 必填,示例:1 + private Long spuId; + + // 商品 SPU 名称 - 必填,示例:芋道源码 + private String spuName; + + // 商品 SKU 编号 - 必填,示例:1 + private Long skuId; + + // 商品图片 - 必填,示例:https://www.iocoder.cn/1.png + private String picUrl; + + // 购买数量 - 必填,示例:1 + private Integer count; + + // ========== 价格 + 支付基本信息 ========== + + // 商品原价(单) - 必填,示例:100 + private Integer price; + + // 商品优惠(总) - 必填,示例:100 + private Integer discountPrice; + + // 商品实付金额(总) - 必填,示例:100 + private Integer payPrice; + + // 子订单分摊金额(总) - 必填,示例:100 + private Integer orderPartPrice; + + // 分摊后子订单实付金额(总) - 必填,示例:100 + private Integer orderDividePrice; + + // ========== 营销基本信息 ========== + + // TODO 芋艿:在捉摸一下 + + // ========== 售后基本信息 ========== + + // 售后状态 - 必填,示例:1 + private Integer afterSaleStatus; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderPageReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderPageReqVO.java new file mode 100644 index 0000000..cc27d0b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderPageReqVO.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.PageParam; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + + +// 管理后台 - 交易订单的分页 Request VO +@Data +public class TradeOrderPageReqVO extends PageParam { + + // 订单状态,示例:1 + private Integer orderStatus; + //聚合检索字段(商品名称,商品id,订单号) + private String prodSearch; + + //聚合检索字段 买家昵称/手机号 + private String userSearch; + + //卖家名称 + private String merchantName; + + // 订单类目id,示例:1 + private Integer orderCategoryId; + // 订单来源,示例:1 + private Integer orderTerminal; + // 财务状态,示例:1 + private Integer financeStatus; + // 售后状态,示例:1 + private Integer afterSaleStatus; + // 预约类型,示例:1 + private Integer subType; + + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + // 创建时间 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] subTime; + + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderPageRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderPageRespVO.java new file mode 100644 index 0000000..fc2db92 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderPageRespVO.java @@ -0,0 +1,11 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + + +import lombok.Data; + +// 管理后台 - 交易订单的分页项 Response VO +@Data +public class TradeOrderPageRespVO extends TradeOrderBaseVO { + + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderRemarkReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderRemarkReqVO.java new file mode 100644 index 0000000..99c64d7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderRemarkReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 订单备注 Request VO +@Data +public class TradeOrderRemarkReqVO { + + // 订单编号 - 必填,示例:1024 + @NotNull(message = "订单编号不能为空") + private Long id; + + // 商家备注,示例:你猜一下 + @NotEmpty(message = "订单备注不能为空") + private String remark; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderSummaryRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderSummaryRespVO.java new file mode 100644 index 0000000..6dfcc0b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderSummaryRespVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +// 管理后台 - 交易订单统计 Response VO +@Data +public class TradeOrderSummaryRespVO { + + // 订单数量 - 必填,示例:1024 + private Long orderCount; + + // 订单金额 - 必填,示例:1024 + private Long orderPayPrice; + + // 退款单数 - 必填,示例:1024 + private Long afterSaleCount; + + // 退款金额 - 必填,示例:1024 + private Long afterSalePrice; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderUpdateAddressReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderUpdateAddressReqVO.java new file mode 100644 index 0000000..7429f8d --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderUpdateAddressReqVO.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 订单修改地址 Request VO +@Data +public class TradeOrderUpdateAddressReqVO { + + // 订单编号 - 必填,示例:1 + @NotNull(message = "订单编号不能为空") + private Long id; + + // 收件人名称 - 必填,示例:z张三 + @NotEmpty(message = "收件人名称不能为空") + private String receiverName; + + // 收件人手机 - 必填,示例:19988188888 + @NotEmpty(message = "收件人手机不能为空") + private String receiverMobile; + + // 收件人地区编号 - 必填,示例:7310 + @NotNull(message = "收件人地区编号不能为空") + private Integer receiverAreaId; + + // 收件人详细地址 - 必填,示例:昆明市五华区xxx小区xxx + @NotEmpty(message = "收件人详细地址不能为空") + private String receiverDetailAddress; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderUpdatePriceReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderUpdatePriceReqVO.java new file mode 100644 index 0000000..50dc106 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/admin/order/vo/TradeOrderUpdatePriceReqVO.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.controller.admin.order.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 管理后台 - 订单改价 Request VO +@Data +public class TradeOrderUpdatePriceReqVO { + + // 订单编号 - 必填,示例:1024 + @NotNull(message = "订单编号不能为空") + private Long id; + + // 订单调价,单位:分。正数,加价;负数,减价 - 必填,示例:-100 + @NotNull(message = "订单调价价格不能为空") + private Integer adjustPrice; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/AppAfterSaleController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/AppAfterSaleController.java new file mode 100644 index 0000000..59c2df7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/AppAfterSaleController.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.trade.controller.app.aftersale; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleRespVO; +import com.tashow.cloud.trade.convert.aftersale.AfterSaleConvert; +import com.tashow.cloud.trade.service.aftersale.AfterSaleService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +// 用户 App - 交易售后 +@RestController +@RequestMapping("/trade/after-sale") +@Validated +@Slf4j +public class AppAfterSaleController { + + @Resource + private AfterSaleService afterSaleService; + + @GetMapping(value = "/page") + // 获得售后分页 + public CommonResult> getAfterSalePage(PageParam pageParam) { + return success(AfterSaleConvert.INSTANCE.convertPage02( + afterSaleService.getAfterSalePage(getLoginUserId(), pageParam))); + } + + @GetMapping(value = "/get") + // 获得售后订单 + // id: 售后编号 - 必填,示例:1 + public CommonResult getAfterSale(@RequestParam("id") Long id) { + return success(AfterSaleConvert.INSTANCE.convert(afterSaleService.getAfterSale(getLoginUserId(), id))); + } + + @PostMapping(value = "/create") + // 申请售后 + public CommonResult createAfterSale(@RequestBody AppAfterSaleCreateReqVO createReqVO) { + return success(afterSaleService.createAfterSale(getLoginUserId(), createReqVO)); + } + + @PutMapping(value = "/delivery") + // 退回货物 + public CommonResult deliveryAfterSale(@RequestBody AppAfterSaleDeliveryReqVO deliveryReqVO) { + afterSaleService.deliveryAfterSale(getLoginUserId(), deliveryReqVO); + return success(true); + } + + @DeleteMapping(value = "/cancel") + // 取消售后 + // id: 售后编号 - 必填,示例:1 + public CommonResult cancelAfterSale(@RequestParam("id") Long id) { + afterSaleService.cancelAfterSale(getLoginUserId(), id); + return success(true); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/AppAfterSaleLogController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/AppAfterSaleLogController.java new file mode 100644 index 0000000..bb546db --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/AppAfterSaleLogController.java @@ -0,0 +1,42 @@ +package com.tashow.cloud.trade.controller.app.aftersale; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.trade.controller.app.aftersale.vo.log.AppAfterSaleLogRespVO; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import com.tashow.cloud.trade.service.aftersale.AfterSaleLogService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 用户 App - 售后日志 +@RestController +@RequestMapping("/trade/after-sale-log") +@Validated +@Slf4j +public class AppAfterSaleLogController { + + @Resource + private AfterSaleLogService afterSaleLogService; + + @GetMapping("/list") + // 获得售后日志列表 + // afterSaleId: 售后编号 - 必填,示例:1 + public CommonResult> getAfterSaleLogList( + @RequestParam("afterSaleId") Long afterSaleId) { + List logs = afterSaleLogService.getAfterSaleLogList(afterSaleId); + return success(BeanUtils.toBean(logs, AppAfterSaleLogRespVO.class)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java new file mode 100644 index 0000000..5b2b209 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleCreateReqVO.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.trade.controller.app.aftersale.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleWayEnum; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +// 用户 App - 交易售后创建 Request VO +@Data +public class AppAfterSaleCreateReqVO { + + // 订单项编号 - 必填,示例:1024 + @NotNull(message = "订单项编号不能为空") + private Long orderItemId; + + // 售后方式 - 必填,示例:1 + @NotNull(message = "售后方式不能为空") + @InEnum(value = AfterSaleWayEnum.class, message = "售后方式必须是 {value}") + private Integer way; + + // 退款金额 - 必填,示例:100 + @NotNull(message = "退款金额不能为空") + @Min(value = 1, message = "退款金额必须大于 0") + private Integer refundPrice; + + // 申请原因 - 必填,示例:1 + @NotNull(message = "申请原因不能为空") + private String applyReason; + + // 补充描述,示例:商品质量不好 + private String applyDescription; + + // 补充凭证图片,示例:https://www.iocoder.cn/1.png, https://www.iocoder.cn/2.png + private List applyPicUrls; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleDeliveryReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleDeliveryReqVO.java new file mode 100644 index 0000000..258a6b4 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleDeliveryReqVO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.trade.controller.app.aftersale.vo; + +// 移除Swagger相关导入 +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +// 用户 App - 交易售后退回货物 Request VO +@Data +public class AppAfterSaleDeliveryReqVO { + + // 售后编号 - 必填,示例:1024 + @NotNull(message = "售后编号不能为空") + private Long id; + + // 退货物流公司编号 - 必填,示例:1 + @NotNull(message = "退货物流公司编号不能为空") + private Long logisticsId; + + // 退货物流单号 - 必填,示例:SF123456789 + @NotNull(message = "退货物流单号不能为空") + private String logisticsNo; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleRespVO.java new file mode 100644 index 0000000..48afcaa --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/AppAfterSaleRespVO.java @@ -0,0 +1,110 @@ +package com.tashow.cloud.trade.controller.app.aftersale.vo; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +// 用户 App - 交易售后 Response VO +@Data +public class AppAfterSaleRespVO { + + // 售后编号 - 必填,示例:1024 + private Long id; + + // 售后流水号 - 必填,示例:1146347329394184195 + private String no; + + // 售后状态 - 必填,示例:1 + private Integer status; + + // 售后方式 - 必填,示例:1 + private Integer way; + + // 售后类型 - 必填,示例:1 + private Integer type; + + // 申请原因 - 必填,示例:1 + private String applyReason; + + // 补充描述 - 必填,示例:1 + private String applyDescription; + + // 补充凭证图片 - 必填,示例:1 + private List applyPicUrls; + + // 创建时间 - 必填 + private LocalDateTime createTime; + + // 更新时间 - 必填 + private LocalDateTime updateTime; + + // ========== 交易订单相关 ========== + + // 交易订单编号 - 必填,示例:1 + private Long orderId; + + // 交易订单流水号 - 必填,示例:1 + private String orderNo; + + // 交易订单项编号 - 必填,示例:1 + private Long orderItemId; + + // 商品 SPU 编号 - 必填,示例:1 + private Long spuId; + + // 商品 SPU 名称 - 必填,示例:1 + private String spuName; + + // 商品 SKU 编号 - 必填,示例:1 + private Long skuId; + + /** + * 属性数组 + */ + private List properties; + + // 商品图片 - 必填,示例:https://www.iocoder.cn/01.jpg + private String picUrl; + + // 退货商品数量 - 必填,示例:1 + private Integer count; + + // ========== 审批相关 ========== + + /** + * 审批备注 + * + * 注意,只有审批不通过才会填写 + */ + private String auditReason; + + // ========== 退款相关 ========== + + // 退款金额,单位:分,示例:100 + private Integer refundPrice; + + // 退款时间 + private LocalDateTime refundTime; + + // ========== 退货相关 ========== + + // 退货物流公司编号,示例:1 + private Long logisticsId; + + // 退货物流单号,示例:SF123456789 + private String logisticsNo; + + // 退货时间 + private LocalDateTime deliveryTime; + + // 收货时间 + private LocalDateTime receiveTime; + + // 收货备注 + private String receiveReason; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/log/AppAfterSaleLogRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/log/AppAfterSaleLogRespVO.java new file mode 100644 index 0000000..afb7071 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/aftersale/vo/log/AppAfterSaleLogRespVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.trade.controller.app.aftersale.vo.log; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; + +// 管理后台 - App 交易售后日志 Response VO +@Data +public class AppAfterSaleLogRespVO { + + // 编号 - 必填,示例:20669 + private Long id; + + // 操作明细 - 必填,示例:维权完成,退款金额:¥37776.00 + private String content; + + // 创建时间 - 必填 + private LocalDateTime createTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/package-info.java new file mode 100644 index 0000000..efe3170 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/package-info.java @@ -0,0 +1,4 @@ +/** + * 基础包,放一些通用的 VO 类 + */ +package com.tashow.cloud.trade.controller.app.base; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java new file mode 100644 index 0000000..ac44fa5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.controller.app.base.property; + +// 移除Swagger相关导入 +import lombok.Data; + +// 用户 App - 商品属性值的明细 Response VO +@Data +public class AppProductPropertyValueDetailRespVO { + + // 属性的编号 - 必填,示例:1 + private Long propertyId; + + // 属性的名称 - 必填,示例:颜色 + private String propertyName; + + // 属性值的编号 - 必填,示例:1024 + private Long valueId; + + // 属性值的名称 - 必填,示例:红色 + private String valueName; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/sku/AppProductSkuBaseRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/sku/AppProductSkuBaseRespVO.java new file mode 100644 index 0000000..7a38fc8 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/sku/AppProductSkuBaseRespVO.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.trade.controller.app.base.sku; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; +import lombok.Data; + +import java.util.List; + +/** + * 商品 SKU 基础 Response VO + * + * @author 芋道源码 + */ +@Data +public class AppProductSkuBaseRespVO { + + // 主键 - 必填,示例:1024 + private Long id; + + // 图片地址,示例:https://www.iocoder.cn/xx.png + private String picUrl; + + // 销售价格,单位:分 - 必填,示例:100 + private Integer price; + + // 库存 - 必填,示例:1 + private Integer stock; + + /** + * 属性数组 + */ + private List properties; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java new file mode 100644 index 0000000..454a450 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.trade.controller.app.base.spu; + +// 移除Swagger相关导入 +import lombok.Data; + +/** + * 商品 SPU 基础 Response VO + * + * @author 芋道源码 + */ +@Data +public class AppProductSpuBaseRespVO { + + // 主键 - 必填,示例:1024 + private Long id; + + // 商品 SPU 名字 - 必填,示例:芋道 + private String name; + + // 商品主图地址,示例:https://www.iocoder.cn/xx.png + private String picUrl; + + // 商品分类编号,示例:1 + private Long categoryId; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/AppDeliverExpressController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/AppDeliverExpressController.java new file mode 100644 index 0000000..ed01587 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/AppDeliverExpressController.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.trade.controller.app.delivery; + + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.trade.controller.app.delivery.vo.express.AppDeliveryExpressRespVO; +import com.tashow.cloud.trade.convert.delivery.DeliveryExpressConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +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 java.util.Comparator; +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 用户 App - 快递公司 +@RestController +@RequestMapping("/trade/delivery/express") +@Validated +public class AppDeliverExpressController { + + @Resource + private DeliveryExpressService deliveryExpressService; + + @GetMapping("/list") + // 获得快递公司列表 + @PermitAll + public CommonResult> getDeliveryExpressList() { + List list = deliveryExpressService.getDeliveryExpressListByStatus(CommonStatusEnum.ENABLE.getStatus()); + list.sort(Comparator.comparing(DeliveryExpressDO::getSort)); + return success(DeliveryExpressConvert.INSTANCE.convertList03(list)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/AppDeliverPickUpStoreController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/AppDeliverPickUpStoreController.java new file mode 100644 index 0000000..c210e86 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/AppDeliverPickUpStoreController.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.trade.controller.app.delivery; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.trade.controller.app.delivery.vo.pickup.AppDeliveryPickUpStoreRespVO; +import com.tashow.cloud.trade.convert.delivery.DeliveryPickUpStoreConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import com.tashow.cloud.trade.service.delivery.DeliveryPickUpStoreService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.tashow.cloud.common.pojo.CommonResult.success; + + +// 用户 App - 自提门店 +@RestController +@RequestMapping("/trade/delivery/pick-up-store") +@Validated +public class AppDeliverPickUpStoreController { + + @Resource + private DeliveryPickUpStoreService deliveryPickUpStoreService; + + @GetMapping("/list") + // 获得自提门店列表 + // 参数: latitude - 精度,示例:110; longitude - 纬度,示例:120 + @PermitAll + public CommonResult> getDeliveryPickUpStoreList( + @RequestParam(value = "latitude", required = false) Double latitude, + @RequestParam(value = "longitude", required = false) Double longitude) { + List list = deliveryPickUpStoreService.getDeliveryPickUpStoreListByStatus( + CommonStatusEnum.ENABLE.getStatus()); + return success(DeliveryPickUpStoreConvert.INSTANCE.convertList(list, latitude, longitude)); + } + + @GetMapping("/get") + // 获得自提门店 + // id: 门店编号 + @PermitAll + public CommonResult getOrder(@RequestParam("id") Long id) { + DeliveryPickUpStoreDO store = deliveryPickUpStoreService.getDeliveryPickUpStore(id); + return success(DeliveryPickUpStoreConvert.INSTANCE.convert03(store)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java new file mode 100644 index 0000000..04fef39 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/config/AppDeliveryConfigRespVO.java @@ -0,0 +1,17 @@ +package com.tashow.cloud.trade.controller.app.delivery.vo.config; + +// 移除Swagger相关导入 +import lombok.Data; + +// TODO 芋艿:后续要实现下,配送配置;后续融合到 AppTradeConfigRespVO 中 +// 用户 App - 配送配置 Response VO +@Data +public class AppDeliveryConfigRespVO { + + // 腾讯地图 KEY - 必填,示例:123456 + private String tencentLbsKey; + + // 是否开启自提 - 必填,示例:true + private Boolean pickUpEnable; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/express/AppDeliveryExpressRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/express/AppDeliveryExpressRespVO.java new file mode 100644 index 0000000..8f9accf --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/express/AppDeliveryExpressRespVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.trade.controller.app.delivery.vo.express; + +// 移除Swagger相关导入 +import lombok.Data; + +// 用户 App - 快递公司 Response VO +@Data +public class AppDeliveryExpressRespVO { + + // 编号 - 必填,示例:1 + private Long id; + + // 门店名称 - 必填,示例:顺丰 + private String name; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java new file mode 100644 index 0000000..4889f0c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java @@ -0,0 +1,40 @@ +package com.tashow.cloud.trade.controller.app.delivery.vo.pickup; + +// 移除Swagger相关导入 +import lombok.Data; + +// 用户 App - 自提门店 Response VO +@Data +public class AppDeliveryPickUpStoreRespVO { + + // 编号 - 必填,示例:23128 + private Long id; + + // 门店名称 - 必填,示例:李四 + private String name; + + // 门店 logo - 必填,示例:https://www.iocoder.cn/1.png + private String logo; + + // 门店手机 - 必填,示例:15601892312 + private String phone; + + // 区域编号 - 必填,示例:18733 + private Integer areaId; + + // 地区名字 - 必填,示例:上海上海市普陀区 + private String areaName; + + // 门店详细地址 - 必填,示例:复旦大学路 188 号 + private String detailAddress; + + // 纬度 - 必填,示例:5.88 + private Double latitude; + + // 经度 - 必填,示例:6.99 + private Double longitude; + + // 距离,单位:千米,示例:100 // 只有在用户传递了经纬度时,才进行计算 + private Double distance; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/AppTradeOrderController.http b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/AppTradeOrderController.http new file mode 100644 index 0000000..f51ddff --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/AppTradeOrderController.http @@ -0,0 +1,69 @@ +### /trade-order/settlement 获得订单结算信息(基于商品) +GET {{appApi}}/trade/order/settlement?type=0&items[0].skuId=1&items[0].count=2&items[1].skuId=2&items[1].count=3&couponId=1 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +### /trade-order/settlement 获得订单结算信息(基于购物车) +GET {{appApi}}/trade/order/settlement?type=0&items[0].cartId=50&couponId=1 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +### /trade-order/create 创建订单(基于商品)【快递】 +POST {{appApi}}/trade/order/create +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "pointStatus": true, + "deliveryType": 1, + "addressId": 21, + "items": [ + { + "skuId": 1, + "count": 2 + } + ], + "remark": "我是备注" +} + +### /trade-order/create 创建订单(基于商品)【自提】 +POST {{appApi}}/trade/order/create +Content-Type: application/json +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +{ + "pointStatus": true, + "deliveryType": 2, + "pickUpStoreId": 1, + "items": [ + { + "skuId": 1, + "count": 2 + } + ], + "remark": "我是备注", + "receiverName": "土豆", + "receiverMobile": "15601691300" +} + +### 获得订单交易的分页 +GET {{appApi}}/trade/order/page?pageNo=1&pageSize=10 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +### 获得订单交易的详细 +GET {{appApi}}/trade/order/get-detail?id=21 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +### 获得交易订单的物流轨迹 +GET {{appApi}}/trade/order/get-express-track-list?id=70 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} + +### /trade-order/settlement-product 获得商品结算信息 +GET {{appApi}}/trade/order/settlement-product?spuIds=633 +Authorization: Bearer {{appToken}} +tenant-id: {{appTenantId}} \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/AppTradeOrderController.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/AppTradeOrderController.java new file mode 100644 index 0000000..f842109 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/AppTradeOrderController.java @@ -0,0 +1,197 @@ +package com.tashow.cloud.trade.controller.app.order; + +import com.google.common.collect.Maps; +import com.tashow.cloud.common.pojo.CommonResult; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.payapi.api.notify.dto.PayOrderNotifyReqDTO; +import com.tashow.cloud.trade.controller.app.order.vo.*; +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; +import com.tashow.cloud.trade.convert.order.TradeOrderConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.framework.order.config.TradeOrderProperties; +import com.tashow.cloud.trade.service.aftersale.AfterSaleService; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressService; +import com.tashow.cloud.trade.service.order.TradeOrderQueryService; +import com.tashow.cloud.trade.service.order.TradeOrderUpdateService; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderStatusEnum; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.pojo.CommonResult.success; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.security.security.core.util.SecurityFrameworkUtils.getLoginUserId; + + +// 用户 App - 交易订单 +@RestController +@RequestMapping("/trade/order") +@Validated +@Slf4j +public class AppTradeOrderController { + + @Resource + private TradeOrderUpdateService tradeOrderUpdateService; + @Resource + private TradeOrderQueryService tradeOrderQueryService; + @Resource + private DeliveryExpressService deliveryExpressService; + @Resource + private AfterSaleService afterSaleService; +// @Resource +// private TradePriceService priceService; + + @Resource + private TradeOrderProperties tradeOrderProperties; + + @GetMapping("/settlement") + // 获得订单结算信息 + public CommonResult settlementOrder(@Valid AppTradeOrderSettlementReqVO settlementReqVO) { + return success(tradeOrderUpdateService.settlementOrder(getLoginUserId(), settlementReqVO)); + } + + @GetMapping("/settlement-product") + // 获得商品结算信息 - 用于商品列表、商品详情,获得参与活动后的价格信息 + // spuIds: 商品 SPU 编号数组 + @PermitAll + public CommonResult> settlementProduct(@RequestParam("spuIds") List spuIds) { + return success( null);//priceService.calculateProductPrice(getLoginUserId(), spuIds)); + } + + @PostMapping("/create") + // 创建订单 + public CommonResult createOrder(@Valid @RequestBody AppTradeOrderCreateReqVO createReqVO) { + TradeOrderDO order = tradeOrderUpdateService.createOrder(getLoginUserId(), createReqVO); + return success(new AppTradeOrderCreateRespVO().setId(order.getId()).setPayOrderId(order.getPayOrderId())); + } + + @PostMapping("/update-paid") + // 更新订单为已支付 // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll + public CommonResult updateOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) { + tradeOrderUpdateService.updateOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()), + notifyReqDTO.getPayOrderId()); + return success(true); + } + + @GetMapping("/get-detail") + // 获得交易订单 + // id: 交易订单编号 + // sync: 是否同步支付状态,示例:true + public CommonResult getOrderDetail(@RequestParam("id") Long id, + @RequestParam(value = "sync", required = false) Boolean sync) { + // 1.1 查询订单 + TradeOrderDO order = tradeOrderQueryService.getOrder(getLoginUserId(), id); + if (order == null) { + return success(null); + } + // 1.2 sync 仅在等待支付 + if (Boolean.TRUE.equals(sync) + && TradeOrderStatusEnum.isUnpaid(order.getOrderStatus()) && !order.getPayStatus()) { + tradeOrderUpdateService.syncOrderPayStatusQuietly(order.getId(), order.getPayOrderId()); + // 重新查询,因为同步后,可能会有变化 + order = tradeOrderQueryService.getOrder(id); + } + + // 2.1 查询订单项 + List orderItems = tradeOrderQueryService.getOrderItemListByOrderId(order.getId()); + // 2.2 查询物流公司 + DeliveryExpressDO express = order.getLogisticsId() != null && order.getLogisticsId() > 0 ? + deliveryExpressService.getDeliveryExpress(order.getLogisticsId()) : null; + // 2.3 最终组合 + return success(TradeOrderConvert.INSTANCE.convert02(order, orderItems, tradeOrderProperties, express)); + } + + @GetMapping("/get-express-track-list") + // 获得交易订单的物流轨迹 + // id: 交易订单编号 + public CommonResult> getOrderExpressTrackList(@RequestParam("id") Long id) { + return success(TradeOrderConvert.INSTANCE.convertList02( + tradeOrderQueryService.getExpressTrackList(id, getLoginUserId()))); + } + + @GetMapping("/page") + // 获得交易订单分页 + public CommonResult> getOrderPage(AppTradeOrderPageReqVO reqVO) { + // 查询订单 + PageResult pageResult = tradeOrderQueryService.getOrderPage(getLoginUserId(), reqVO); + // 查询订单项 + List orderItems = tradeOrderQueryService.getOrderItemListByOrderId( + convertSet(pageResult.getList(), TradeOrderDO::getId)); + // 最终组合 + return success(TradeOrderConvert.INSTANCE.convertPage02(pageResult, orderItems)); + } + + @GetMapping("/get-count") + // 获得交易订单数量 + public CommonResult> getOrderCount() { + Map orderCount = Maps.newLinkedHashMapWithExpectedSize(5); + // 全部 + orderCount.put("allCount", tradeOrderQueryService.getOrderCount(getLoginUserId(), null, null)); + // 待付款(未支付) + orderCount.put("unpaidCount", tradeOrderQueryService.getOrderCount(getLoginUserId(), + TradeOrderStatusEnum.UNPAID.getStatus(), null)); + // 待发货 + orderCount.put("undeliveredCount", tradeOrderQueryService.getOrderCount(getLoginUserId(), + TradeOrderStatusEnum.UNDELIVERED.getStatus(), null)); + // 待收货 + orderCount.put("deliveredCount", tradeOrderQueryService.getOrderCount(getLoginUserId(), + TradeOrderStatusEnum.DELIVERED.getStatus(), null)); + // 待评价 + orderCount.put("uncommentedCount", tradeOrderQueryService.getOrderCount(getLoginUserId(), + TradeOrderStatusEnum.COMPLETED.getStatus(), false)); + // 售后数量 + orderCount.put("afterSaleCount", afterSaleService.getApplyingAfterSaleCount(getLoginUserId())); + return success(orderCount); + } + + @PutMapping("/receive") + // 确认交易订单收货 + // id: 交易订单编号 + public CommonResult receiveOrder(@RequestParam("id") Long id) { + tradeOrderUpdateService.receiveOrderByMember(getLoginUserId(), id); + return success(true); + } + + @DeleteMapping("/cancel") + // 取消交易订单 + // id: 交易订单编号 + public CommonResult cancelOrder(@RequestParam("id") Long id) { + tradeOrderUpdateService.cancelOrderByMember(getLoginUserId(), id); + return success(true); + } + + @DeleteMapping("/delete") + // 删除交易订单 + // id: 交易订单编号 + public CommonResult deleteOrder(@RequestParam("id") Long id) { + tradeOrderUpdateService.deleteOrder(getLoginUserId(), id); + return success(true); + } + + // ========== 订单项 ========== + + @GetMapping("/item/get") + // 获得交易订单项 + // id: 交易订单项编号 + public CommonResult getOrderItem(@RequestParam("id") Long id) { + TradeOrderItemDO item = tradeOrderQueryService.getOrderItem(getLoginUserId(), id); + return success(TradeOrderConvert.INSTANCE.convert03(item)); + } + + @PostMapping("/item/create-comment") + // 创建交易订单项的评价 + public CommonResult createOrderItemComment(@RequestBody AppTradeOrderItemCommentCreateReqVO createReqVO) { + return success(tradeOrderUpdateService.createOrderItemCommentByMember(getLoginUserId(), createReqVO)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppOrderExpressTrackRespDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppOrderExpressTrackRespDTO.java new file mode 100644 index 0000000..d98fdd5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppOrderExpressTrackRespDTO.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 快递查询的轨迹 Resp DTO + * + * @author jason + */ +// 用户 App - 快递查询的轨迹 Response VO +@Data +public class AppOrderExpressTrackRespDTO { + + // 发生时间 - 必填 + private LocalDateTime time; + + // 快递状态 - 必填,示例:已签收 + private String content; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java new file mode 100644 index 0000000..734c601 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderCreateReqVO.java @@ -0,0 +1,21 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; +// 移除Swagger相关导入 +import jakarta.validation.constraints.AssertTrue; +import lombok.Data; + +// 用户 App - 交易订单创建 Request VO +@Data +public class AppTradeOrderCreateReqVO extends AppTradeOrderSettlementReqVO { + + // 备注,示例:这个是我的订单哟 + private String remark; + + @AssertTrue(message = "配送方式不能为空") + @JsonIgnore + public boolean isDeliveryTypeNotNull() { + return getDeliveryType() != null; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderCreateRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderCreateRespVO.java new file mode 100644 index 0000000..374710e --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderCreateRespVO.java @@ -0,0 +1,16 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + +// 移除Swagger相关导入 +import lombok.Data; + +// 用户 App - 交易订单创建 Response VO +@Data +public class AppTradeOrderCreateRespVO { + + // 订单编号 - 必填,示例:1024 + private Long id; + + // 支付订单编号 - 必填,示例:1024 + private Long payOrderId; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java new file mode 100644 index 0000000..6b50eb2 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderDetailRespVO.java @@ -0,0 +1,151 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + + +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +// 用户 App - 订单交易的明细 Response VO +@Data +public class AppTradeOrderDetailRespVO { + + // ========== 订单基本信息 ========== + + // 订单编号 - 必填,示例:1024 + private Long id; + + // 订单流水号 - 必填,示例:1146347329394184195 + private String no; + + // 订单类型 - 必填,示例:0 + private Integer type; + + // 下单时间 - 必填 + private LocalDateTime createTime; + + // 用户备注 - 必填,示例:你猜 + private String userRemark; + + // 订单状态 - 必填,示例:1 + private Integer status; + + // 购买的商品数量 - 必填,示例:10 + private Integer productCount; + + // 订单完成时间 + private LocalDateTime finishTime; + + // 订单取消时间 + private LocalDateTime cancelTime; + + // 是否评价 - 必填,示例:true + private Boolean commentStatus; + + // ========== 价格 + 支付基本信息 ========== + + // 是否已支付 - 必填,示例:true + private Boolean payStatus; + + // 支付订单编号 - 必填,示例:1024 + private Long payOrderId; + + // 付款时间 + private LocalDateTime payTime; + + // 付款超时时间 - 必填 + private LocalDateTime payExpireTime; + + // 支付渠道,示例:wx_lite_pay + private String payChannelCode; + // 支付渠道名,示例:微信小程序支付 + private String payChannelName; + + // 商品原价(总) - 必填,示例:1000 + private Integer totalPrice; + + // 订单优惠(总) - 必填,示例:100 + private Integer discountPrice; + + // 运费金额 - 必填,示例:100 + private Integer deliveryPrice; + + // 订单调价(总) - 必填,示例:100 + private Integer adjustPrice; + + // 应付金额(总) - 必填,示例:1000 + private Integer payPrice; + + // ========== 收件 + 物流基本信息 ========== + + // 配送方式 - 必填,示例:1 + private Integer deliveryType; + + // 发货物流公司编号,示例:10 + private Long logisticsId; + + // 发货物流名称,示例:顺丰快递 + private String logisticsName; + + // 发货物流单号,示例:1024 + private String logisticsNo; + + // 发货时间 + private LocalDateTime deliveryTime; + + // 收货时间 + private LocalDateTime receiveTime; + + // 收件人名称 - 必填,示例:张三 + private String receiverName; + + // 收件人手机 - 必填,示例:13800138000 + private String receiverMobile; + + // 收件人地区编号 - 必填,示例:110000 + private Integer receiverAreaId; + + // 收件人地区名字 - 必填,示例:上海 上海市 普陀区 + private String receiverAreaName; + + // 收件人详细地址 - 必填,示例:中关村大街 1 号 + private String receiverDetailAddress; + + // 自提门店编号,示例:1088 + private Long pickUpStoreId; + + // 自提核销码,示例:40964096 + private String pickUpVerifyCode; + + // ========== 售后基本信息 ========== + + // 售后状态,示例:0 + private Integer refundStatus; + + // 退款金额,单位:分,示例:100 + private Integer refundPrice; + + // ========== 营销基本信息 ========== + + // 优惠劵编号,示例:1024 + private Long couponId; + + // 优惠劵减免金额 - 必填,示例:100 + private Integer couponPrice; + + // 积分抵扣的金额 - 必填,示例:100 + private Integer pointPrice; + + // VIP 减免金额 - 必填,示例:888 + private Integer vipPrice; + + // 拼团记录编号,示例:100 + private Long combinationRecordId; + + /** + * 订单项数组 + */ + private List items; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java new file mode 100644 index 0000000..640bcf2 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderPageItemRespVO.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +// 用户 App - 订单交易的分页项 Response VO +@Data +public class AppTradeOrderPageItemRespVO { + + // 订单编号 - 必填,示例:1024 + private Long id; + + // 订单流水号 - 必填,示例:1146347329394184195 + private String no; + + // 订单类型 - 必填,示例:0 + private Integer type; + + // 订单状态 - 必填,示例:1 + private Integer status; + + // 购买的商品数量 - 必填,示例:10 + private Integer productCount; + + // 是否评价 - 必填,示例:true + private Boolean commentStatus; + + // 创建时间 - 必填 + private LocalDateTime createTime; + + // ========== 价格 + 支付基本信息 ========== + + // 支付订单编号 - 必填,示例:1024 + private Long payOrderId; + + // 应付金额,单位:分 - 必填,示例:1000 + private Integer payPrice; + + // ========== 收件 + 物流基本信息 ========== + + // 配送方式 - 必填,示例:1 + private Integer deliveryType; + + /** + * 订单项数组 + */ + private List items; + + // ========== 营销基本信息 ========== + + // 拼团记录编号,示例:100 + private Long combinationRecordId; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java new file mode 100644 index 0000000..a70cd61 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java @@ -0,0 +1,20 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + + +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderStatusEnum; +import lombok.Data; + +// 交易订单分页 Request VO +@Data +public class AppTradeOrderPageReqVO extends PageParam { + + // 订单状态,示例:1 + @InEnum(value = TradeOrderStatusEnum.class, message = "订单状态必须是 {value}") + private Integer status; + + // 是否评价,示例:true + private Boolean commentStatus; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java new file mode 100644 index 0000000..64fb7de --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderSettlementReqVO.java @@ -0,0 +1,108 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + +import cn.hutool.core.util.ObjUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.tashow.cloud.common.validation.InEnum; +import com.tashow.cloud.common.validation.Mobile; +import com.tashow.cloud.tradeapi.enums.delivery.DeliveryTypeEnum; +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +// 用户 App - 交易订单结算 Request VO +@Data +@Valid +public class AppTradeOrderSettlementReqVO { + + // 商品项数组 - 必填 + @NotEmpty(message = "商品不能为空") + private List items; + + // 优惠劵编号,示例:1024 + private Long couponId; + + // 是否使用积分 - 必填,示例:true + @NotNull(message = "是否使用积分不能为空") + private Boolean pointStatus; + + // ========== 配送相关相关字段 ========== + // 配送方式 - 必填,示例:1 + @InEnum(value = DeliveryTypeEnum.class, message = "配送方式不正确") + private Integer deliveryType; + + // 收件地址编号,示例:1 + private Long addressId; + + // 自提门店编号,示例:1088 + private Long pickUpStoreId; + // 收件人名称,示例:芋艿 // 选择门店自提时,该字段为联系人名 + private String receiverName; + // 收件人手机,示例:15601691300 // 选择门店自提时,该字段为联系人手机 + @Mobile(message = "收件人手机格式不正确") + private String receiverMobile; + + // ========== 秒杀活动相关字段 ========== + // 秒杀活动编号,示例:1024 + private Long seckillActivityId; + + // ========== 拼团活动相关字段 ========== + // 拼团活动编号,示例:1024 + private Long combinationActivityId; + + // 拼团团长编号,示例:2048 + private Long combinationHeadId; + + // ========== 砍价活动相关字段 ========== + // 砍价记录编号,示例:123 + private Long bargainRecordId; + + // ========== 积分商城活动相关字段 ========== + // 积分商城活动编号,示例:123 + private Long pointActivityId; + + @AssertTrue(message = "活动商品每次只能购买一种规格") + @JsonIgnore + public boolean isValidActivityItems() { + // 校验是否是活动订单 + if (ObjUtil.isAllEmpty(seckillActivityId, combinationActivityId, combinationHeadId, bargainRecordId)) { + return true; + } + // 校验订单项是否超出 + return items.size() == 1; + } + + @Data + // 用户 App - 商品项 + @Valid + public static class Item { + + // 商品 SKU 编号,示例:2048 + @NotNull(message = "商品 SKU 编号不能为空") + private Long skuId; + + // 购买数量,示例:1 + @Min(value = 1, message = "购买数量最小值为 {value}") + private Integer count; + + // 购物车项的编号,示例:1024 + private Long cartId; + + @AssertTrue(message = "商品不正确") + @JsonIgnore + public boolean isValid() { + // 组合一:skuId + count 使用商品 SKU + if (skuId != null && count != null) { + return true; + } + // 组合二:cartId 使用购物车项 + return cartId != null; + } + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java new file mode 100644 index 0000000..9c62bbf --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java @@ -0,0 +1,125 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + + +import com.tashow.cloud.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +// 用户 App - 交易订单结算信息 Response VO +@Data +public class AppTradeOrderSettlementRespVO { + + // 交易类型 - 必填,示例:1 // 对应 TradeOrderTypeEnum 枚举 + private Integer type; + + // 购物项数组 - 必填 + private List items; + + // 费用 - 必填 + private Price price; + + // 收件地址 - 必填 + private Address address; + + // 已使用的积分 - 必填,示例:10 + private Integer usePoint; + + // 总积分 - 必填,示例:10 + private Integer totalPoint; + + + // 购物项 + @Data + public static class Item { + + // ========== SPU 信息 ========== + + // 品类编号 - 必填,示例:2048 + private Long categoryId; + // SPU 编号 - 必填,示例:2048 + private Long spuId; + // SPU 名字 - 必填,示例:Apple iPhone 12 + private String spuName; + + // ========== SKU 信息 ========== + + // SKU 编号 - 必填,示例:1024 + private Integer skuId; + // 价格,单位:分 - 必填,示例:100 + private Integer price; + // 图片地址 - 必填,示例:https://www.iocoder.cn/1.png + private String picUrl; + + // 属性数组 - 必填,示例:100 + private List properties; + + // ========== 购物车信息 ========== + + // 购物车编号 - 必填,示例:100 + private Long cartId; + + // 购买数量 - 必填,示例:1 + private Integer count; + + } + + // 费用(合计) + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Price { + + // 商品原价(总),单位:分 - 必填,示例:500 + private Integer totalPrice; + + // 订单优惠(总),单位:分 - 必填,示例:66 + private Integer discountPrice; + + // 运费金额,单位:分 - 必填,示例:50 + private Integer deliveryPrice; + + // 优惠劵减免金额,单位:分 - 必填,示例:100 + private Integer couponPrice; + + // 积分抵扣的金额,单位:分 - 必填,示例:50 + private Integer pointPrice; + + // VIP 减免金额,单位:分 + private Integer vipPrice; + + //实际支付金额(总),单位:分 + private Integer payPrice; + + } + + //地址信息 + @Data + public static class Address { + + //编号 + private Long id; + + //收件人名称 + private String name; + + //手机号 + private String mobile; + + //地区编号 + private Long areaId; + //地区名字 + private String areaName; + + //详细地址 + private String detailAddress; + + //是否默认收件地址 + private Boolean defaultStatus; + + } + + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeProductSettlementRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeProductSettlementRespVO.java new file mode 100644 index 0000000..c2b7503 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/AppTradeProductSettlementRespVO.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.trade.controller.app.order.vo; + +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +// 用户 App - 商品结算信息 Response VO +@Data +public class AppTradeProductSettlementRespVO { + + // SPU 商品编号 - 必填,示例:1 + private Long spuId; + + // SKU 价格信息数组 - 必填,示例:1 + private List skus; + + // 满减送活动信息 - 必填,示例:1 + private RewardActivity rewardActivity; + + // SKU 价格信息 + @Data + public static class Sku implements Serializable { + + // 商品 SKU 编号 - 必填,示例:1 + private Long id; + + // 优惠后价格,单位:分 - 必填,示例:100 + private Integer promotionPrice; + + // 营销类型 - 必填,示例:4 + private Integer promotionType; // 对应 PromotionTypeEnum 枚举,目前只有 4 和 6 两种 + + // 营销编号 - 必填 + private Long promotionId; // 目前只有限时折扣活动的编号 + + // 活动结束时间 - 必填 + private LocalDateTime promotionEndTime; + + } + + // 满减送活动信息 + @Data + public static class RewardActivity { + + // 满减活动编号 - 必填,示例:1 + private Long id; + + // 条件类型 - 必填,示例:1 + private Integer conditionType; + + // 优惠规则的数组 - 必填 + private List rules; + + } + + // 优惠规则 + @Data + public static class RewardActivityRule { + + // 优惠门槛 - 必填,示例:100 // 1. 满 N 元,单位:分; 2. 满 N 件 + private Integer limit; + + // 优惠价格 - 必填,示例:100 + private Integer discountPrice; + + // 是否包邮 - 必填,示例:true + private Boolean freeDelivery; + + // 赠送的积分 - 必填,示例:100 + private Integer point; + + // 赠送的优惠劵编号的数组 + private Map giveCouponTemplateCounts; + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/item/AppTradeOrderItemCommentCreateReqVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/item/AppTradeOrderItemCommentCreateReqVO.java new file mode 100644 index 0000000..de40c5f --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/item/AppTradeOrderItemCommentCreateReqVO.java @@ -0,0 +1,37 @@ +package com.tashow.cloud.trade.controller.app.order.vo.item; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +// 用户 App - 商品评价创建 Request VO +@Data +public class AppTradeOrderItemCommentCreateReqVO { + + // 是否匿名 - 必填,示例:true + @NotNull(message = "是否匿名不能为空") + private Boolean anonymous; + + // 交易订单项编号 - 必填,示例:2312312 + @NotNull(message = "交易订单项编号不能为空") + private Long orderItemId; + + // 描述星级 1-5 分 - 必填,示例:5 + @NotNull(message = "描述星级 1-5 分不能为空") + private Integer descriptionScores; + + // 服务星级 1-5 分 - 必填,示例:5 + @NotNull(message = "服务星级 1-5 分不能为空") + private Integer benefitScores; + + // 评论内容 - 必填,示例:穿身上很漂亮诶(*^▽^*) + @NotNull(message = "评论内容不能为空") + private String content; + + // 评论图片地址数组,以逗号分隔最多上传 9 张 - 必填,示例:[https://www.iocoder.cn/xx.png] + @Size(max = 9, message = "评论图片地址数组长度不能超过 9 张") + private List picUrls; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/item/AppTradeOrderItemRespVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/item/AppTradeOrderItemRespVO.java new file mode 100644 index 0000000..20b6f55 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/app/order/vo/item/AppTradeOrderItemRespVO.java @@ -0,0 +1,62 @@ +package com.tashow.cloud.trade.controller.app.order.vo.item; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; +import lombok.Data; + +import java.util.List; + +// 用户 App - 订单交易项 Response VO +@Data +public class AppTradeOrderItemRespVO { + + // 编号 - 必填,示例:1 + private Long id; + + // 订单编号 - 必填,示例:1024 + private Long orderId; + + // 商品 SPU 编号 - 必填,示例:1 + private Long spuId; + // 商品 SPU 名称 - 必填,示例:芋道源码 + private String spuName; + + // 商品 SKU 编号 - 必填,示例:1 + private Long skuId; + + /** + * 属性数组 + */ + private List properties; + + // 商品图片 - 必填,示例:https://www.iocoder.cn/1.png + private String picUrl; + + // 购买数量 - 必填,示例:1 + private Integer count; + + // 是否评价 - 必填,示例:true + private Boolean commentStatus; + + // ========== 价格 + 支付基本信息 ========== + + // 商品原价(单) - 必填,示例:100 + private Integer price; + + // 应付金额(总),单位:分 - 必填,示例:50 + private Integer payPrice; + + // ========== 营销基本信息 ========== + + // TODO 芋艿:在捉摸一下 + + // ========== 售后基本信息 ========== + + // 售后编号,示例:1024 + private Long afterSaleId; + + // 售后状态 - 必填,示例:1 + private Integer afterSaleStatus; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/package-info.java new file mode 100644 index 0000000..516cd24 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/controller/package-info.java @@ -0,0 +1,6 @@ +/** + * 提供 RESTful API 给前端: + * 1. admin 包:提供给管理后台 yudao-ui-admin 前端项目 + * 2. app 包:提供给用户 APP yudao-ui-app 前端项目,它的 Controller 和 VO 都要添加 App 前缀,用于和管理后台进行区分 + */ +package com.tashow.cloud.trade.controller; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/aftersale/AfterSaleConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/aftersale/AfterSaleConvert.java new file mode 100644 index 0000000..f472e94 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/aftersale/AfterSaleConvert.java @@ -0,0 +1,84 @@ +package com.tashow.cloud.trade.convert.aftersale; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.memberapi.api.user.dto.MemberUserRespDTO; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSaleDetailRespVO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSaleRespPageItemVO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.log.AfterSaleLogRespVO; +import com.tashow.cloud.trade.controller.admin.base.member.user.MemberUserRespVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderBaseVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleRespVO; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleDO; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.framework.order.config.TradeOrderProperties; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface AfterSaleConvert { + + AfterSaleConvert INSTANCE = Mappers.getMapper(AfterSaleConvert.class); + + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(target = "createTime", ignore = true), + @Mapping(target = "updateTime", ignore = true), + @Mapping(target = "creator", ignore = true), + @Mapping(target = "updater", ignore = true), + }) + AfterSaleDO convert(AppAfterSaleCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItem); + + @Mappings({ + @Mapping(source = "afterSale.orderId", target = "merchantOrderId"), + @Mapping(source = "afterSale.id", target = "merchantRefundId"), + @Mapping(source = "afterSale.applyReason", target = "reason"), + @Mapping(source = "afterSale.refundPrice", target = "price"), + @Mapping(source = "orderProperties.payAppKey", target = "appKey") + }) + PayRefundCreateReqDTO convert(String userIp, AfterSaleDO afterSale, TradeOrderProperties orderProperties); + + MemberUserRespVO convert(MemberUserRespDTO bean); + + PageResult convertPage(PageResult page); + + default PageResult convertPage(PageResult pageResult, + Map memberUsers) { + PageResult voPageResult = convertPage(pageResult); + // 处理会员 + voPageResult.getList().forEach(afterSale -> afterSale.setUser( + convert(memberUsers.get(afterSale.getUserId())))); + return voPageResult; + } + + AppAfterSaleRespVO convert(AfterSaleDO bean); + + PageResult convertPage02(PageResult page); + + default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem, + MemberUserRespDTO user, List logs) { + AfterSaleDetailRespVO respVO = convert02(afterSale); + // 处理用户信息 + respVO.setUser(convert(user)); + // 处理订单信息 + respVO.setOrder(convert(order)); + respVO.setOrderItem(convert02(orderItem)); + // 处理售后日志 + respVO.setLogs(convertList1(logs)); + return respVO; + } + + List convertList1(List list); + AfterSaleDetailRespVO convert02(AfterSaleDO bean); + AfterSaleDetailRespVO.OrderItem convert02(TradeOrderItemDO bean); + TradeOrderBaseVO convert(TradeOrderDO bean); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/aftersale/AfterSaleLogConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/aftersale/AfterSaleLogConvert.java new file mode 100644 index 0000000..2beeb60 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/aftersale/AfterSaleLogConvert.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.trade.convert.aftersale; + +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import com.tashow.cloud.trade.service.aftersale.bo.AfterSaleLogCreateReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AfterSaleLogConvert { + + AfterSaleLogConvert INSTANCE = Mappers.getMapper(AfterSaleLogConvert.class); + + AfterSaleLogDO convert(AfterSaleLogCreateReqBO bean); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryExpressConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryExpressConvert.java new file mode 100644 index 0000000..f0d3b94 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryExpressConvert.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.convert.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.*; +import com.tashow.cloud.trade.controller.app.delivery.vo.express.AppDeliveryExpressRespVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DeliveryExpressConvert { + + DeliveryExpressConvert INSTANCE = Mappers.getMapper(DeliveryExpressConvert.class); + + DeliveryExpressDO convert(DeliveryExpressCreateReqVO bean); + + DeliveryExpressDO convert(DeliveryExpressUpdateReqVO bean); + + DeliveryExpressRespVO convert(DeliveryExpressDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + + List convertList1(List list); + + List convertList03(List list); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryExpressTemplateConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryExpressTemplateConvert.java new file mode 100644 index 0000000..58bd5fb --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryExpressTemplateConvert.java @@ -0,0 +1,94 @@ +package com.tashow.cloud.trade.convert.delivery; + +import com.google.common.collect.Maps; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.*; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO; +import com.tashow.cloud.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMultiMap; +import static com.tashow.cloud.common.util.collection.CollectionUtils.findFirst; + + +@Mapper +public interface DeliveryExpressTemplateConvert { + + DeliveryExpressTemplateConvert INSTANCE = Mappers.getMapper(DeliveryExpressTemplateConvert.class); + + // ========== Template ========== + + DeliveryExpressTemplateDO convert(DeliveryExpressTemplateCreateReqVO bean); + + DeliveryExpressTemplateDO convert(DeliveryExpressTemplateUpdateReqVO bean); + + DeliveryExpressTemplateRespVO convert(DeliveryExpressTemplateDO bean); + + DeliveryExpressTemplateDetailRespVO convert2(DeliveryExpressTemplateDO bean); + + List convertList(List list); + + List convertList1(List list); + + PageResult convertPage(PageResult page); + + default DeliveryExpressTemplateDetailRespVO convert(DeliveryExpressTemplateDO bean, + List chargeList, + List freeList) { + DeliveryExpressTemplateDetailRespVO respVO = convert2(bean); + respVO.setCharges(convertTemplateChargeList(chargeList)); + respVO.setFrees(convertTemplateFreeList(freeList)); + return respVO; + } + + // ========== Template Charge ========== + + DeliveryExpressTemplateChargeDO convertTemplateCharge(Long templateId, Integer chargeMode, DeliveryExpressTemplateChargeBaseVO vo); + + DeliveryExpressTemplateRespBO.Charge convertTemplateCharge(DeliveryExpressTemplateChargeDO bean); + + default List convertTemplateChargeList(Long templateId, Integer chargeMode, List list) { + return CollectionUtils.convertList(list, vo -> convertTemplateCharge(templateId, chargeMode, vo)); + } + + // ========== Template Free ========== + + DeliveryExpressTemplateFreeDO convertTemplateFree(Long templateId, DeliveryExpressTemplateFreeBaseVO vo); + + DeliveryExpressTemplateRespBO.Free convertTemplateFree(DeliveryExpressTemplateFreeDO bean); + + List convertTemplateChargeList(List list); + + List convertTemplateFreeList(List list); + + default List convertTemplateFreeList(Long templateId, List list) { + return CollectionUtils.convertList(list, vo -> convertTemplateFree(templateId, vo)); + } + + default Map convertMap(Integer areaId, List templateList, + List chargeList, + List freeList) { + Map> templateIdChargeMap = convertMultiMap(chargeList, + DeliveryExpressTemplateChargeDO::getTemplateId); + Map> templateIdFreeMap = convertMultiMap(freeList, + DeliveryExpressTemplateFreeDO::getTemplateId); + // 组合运费模板配置 RespBO + Map result = Maps.newHashMapWithExpectedSize(templateList.size()); + templateList.forEach(template -> { + DeliveryExpressTemplateRespBO bo = new DeliveryExpressTemplateRespBO() + .setChargeMode(template.getChargeMode()) + .setCharge(convertTemplateCharge(findFirst(templateIdChargeMap.get(template.getId()), charge -> charge.getAreaIds().contains(areaId)))) + .setFree(convertTemplateFree(findFirst(templateIdFreeMap.get(template.getId()), free -> free.getAreaIds().contains(areaId)))); + result.put(template.getId(), bo); + }); + return result; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryPickUpStoreConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryPickUpStoreConvert.java new file mode 100644 index 0000000..bc9fb1d --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/delivery/DeliveryPickUpStoreConvert.java @@ -0,0 +1,55 @@ +package com.tashow.cloud.trade.convert.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.ip.AreaUtils; +import com.tashow.cloud.common.util.number.NumberUtils; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreCreateReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreRespVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreSimpleRespVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreUpdateReqVO; +import com.tashow.cloud.trade.controller.app.delivery.vo.pickup.AppDeliveryPickUpStoreRespVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface DeliveryPickUpStoreConvert { + + DeliveryPickUpStoreConvert INSTANCE = Mappers.getMapper(DeliveryPickUpStoreConvert.class); + + DeliveryPickUpStoreDO convert(DeliveryPickUpStoreCreateReqVO bean); + + DeliveryPickUpStoreDO convert(DeliveryPickUpStoreUpdateReqVO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList1(List list); + @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName") + DeliveryPickUpStoreSimpleRespVO convert02(DeliveryPickUpStoreDO bean); + + @Named("convertAreaIdToAreaName") + default String convertAreaIdToAreaName(Integer areaId) { + return AreaUtils.format(areaId); + } + + default List convertList(List list, + Double latitude, Double longitude) { + return CollectionUtils.convertList(list, store -> { + AppDeliveryPickUpStoreRespVO storeVO = convert03(store); + if (latitude != null && longitude != null) { + storeVO.setDistance(NumberUtils.getDistance(latitude, longitude, storeVO.getLatitude(), storeVO.getLongitude())); + } + return storeVO; + }); + } + @Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName") + AppDeliveryPickUpStoreRespVO convert03(DeliveryPickUpStoreDO bean); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/TradeOrderConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/TradeOrderConvert.java new file mode 100644 index 0000000..42b2728 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/TradeOrderConvert.java @@ -0,0 +1,268 @@ +package com.tashow.cloud.trade.convert.order; + +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.common.util.ip.AreaUtils; +import com.tashow.cloud.common.util.string.StrUtils; +import com.tashow.cloud.excel.dict.core.DictFrameworkUtils; +import com.tashow.cloud.memberapi.api.address.dto.MemberAddressRespDTO; +import com.tashow.cloud.memberapi.api.user.dto.MemberUserRespDTO; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.payapi.enums.DictTypeConstants; +import com.tashow.cloud.trade.controller.admin.base.member.user.MemberUserRespVO; +import com.tashow.cloud.trade.controller.admin.order.vo.*; +import com.tashow.cloud.trade.controller.app.order.vo.*; +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemRespVO; +import com.tashow.cloud.trade.convert.order.dto.CombinationRecordCreateReqDTO; +import com.tashow.cloud.trade.convert.order.dto.ProductSkuRespDTO; +import com.tashow.cloud.trade.convert.order.dto.ProductSkuUpdateStockReqDTO; +import com.tashow.cloud.trade.convert.order.dto.ProductSpuRespDTO; +import com.tashow.cloud.trade.dal.dataobject.cart.CartDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; +import com.tashow.cloud.trade.framework.order.config.TradeOrderProperties; +import com.tashow.cloud.trade.service.brokerage.bo.BrokerageAddReqBO; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateReqBO; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateRespBO; +import com.tashow.cloud.tradeapi.api.order.dto.TradeOrderRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMap; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertMultiMap; +import static com.tashow.cloud.common.util.date.LocalDateTimeUtils.addTime; + + +@Mapper +public interface TradeOrderConvert { + + TradeOrderConvert INSTANCE = Mappers.getMapper(TradeOrderConvert.class); + + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(source = "userId", target = "userId"), + @Mapping(target = "merchantRemark", ignore = true), + @Mapping(source = "createReqVO.remark", target = "userRemark"), + }) + TradeOrderDO convert(Long userId, AppTradeOrderCreateReqVO createReqVO); + + TradeOrderRespDTO convert(TradeOrderDO orderDO); + + default List convertList(TradeOrderDO tradeOrderDO) { + return null;//CollectionUtils.convertList(calculateRespBO.getItems(), item -> { +// TradeOrderItemDO orderItem = convert(item); +// orderItem.setOrderId(tradeOrderDO.getId()); +// orderItem.setUserId(tradeOrderDO.getUserId()); +// orderItem.setAfterSaleStatus(TradeOrderItemAfterSaleStatusEnum.NONE.getStatus()); +// orderItem.setCommentStatus(false); +// return orderItem; +// }); + } + + TradeOrderItemDO convert(TradePriceCalculateRespBO.OrderItem item); + + default ProductSkuUpdateStockReqDTO convert(List list) { + List items = CollectionUtils.convertList(list, item -> + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(item.getCount())); + return new ProductSkuUpdateStockReqDTO(items); + } + + default ProductSkuUpdateStockReqDTO convertNegative(List list) { + List items = CollectionUtils.convertList(list, item -> + new ProductSkuUpdateStockReqDTO.Item().setId(item.getSkuId()).setIncrCount(-item.getCount())); + return new ProductSkuUpdateStockReqDTO(items); + } + + default PayOrderCreateReqDTO convert(TradeOrderDO order, List orderItems, + TradeOrderProperties orderProperties) { + PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO() + .setAppKey(orderProperties.getPayAppKey()).setUserIp(order.getUserIp()); + // 商户相关字段 + createReqDTO.setMerchantOrderId(String.valueOf(order.getId())); + String subject = orderItems.get(0).getSpuName(); + subject = StrUtils.maxLength(subject, PayOrderCreateReqDTO.SUBJECT_MAX_LENGTH); // 避免超过 32 位 + createReqDTO.setSubject(subject); + createReqDTO.setBody(subject); // TODO 芋艿:临时写死 + // 订单相关字段 + createReqDTO.setPrice(order.getPayPrice()).setExpireTime(addTime(orderProperties.getPayExpireTime())); + return createReqDTO; + } + + default PageResult convertPage(PageResult pageResult, + List orderItems, + Map memberUserMap) { + Map> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId); + // 转化 List + List orderVOs = CollectionUtils.convertList(pageResult.getList(), order -> { + List xOrderItems = orderItemMap.get(order.getId()); + TradeOrderPageRespVO orderVO = convert(order, xOrderItems); + // 处理收货地址 + return orderVO; + }); + return new PageResult<>(orderVOs, pageResult.getTotal()); + } + + MemberUserRespVO convertUser(MemberUserRespDTO memberUserRespDTO); + + TradeOrderPageRespVO convert(TradeOrderDO order, List items); + +// ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean); + + default TradeOrderDetailRespVO convert(TradeOrderDO order, List orderItems, + List orderLogs, + MemberUserRespDTO user, MemberUserRespDTO brokerageUser) { + TradeOrderDetailRespVO orderVO = convert2(order, orderItems); +// // 处理收货地址 +// orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId())); +// // 处理用户信息 +// orderVO.setUser(convert(user)); +// orderVO.setBrokerageUser(convert(brokerageUser)); +// // 处理日志 +// orderVO.setLogs(convertList03(orderLogs)); + return orderVO; + } + List convertList03(List orderLogs); + + TradeOrderDetailRespVO convert2(TradeOrderDO order, List items); + + MemberUserRespVO convert(MemberUserRespDTO bean); + + default PageResult convertPage02(PageResult pageResult, + List orderItems) { + Map> orderItemMap = convertMultiMap(orderItems, TradeOrderItemDO::getOrderId); + // 转化 List + List orderVOs = CollectionUtils.convertList(pageResult.getList(), order -> { + List xOrderItems = orderItemMap.get(order.getId()); + return convert02(order, xOrderItems); + }); + return new PageResult<>(orderVOs, pageResult.getTotal()); + } + + AppTradeOrderPageItemRespVO convert02(TradeOrderDO order, List items); + +// AppProductPropertyValueDetailRespVO convert02(ProductPropertyValueDetailRespDTO bean); + + default AppTradeOrderDetailRespVO convert02(TradeOrderDO order, List orderItems, + TradeOrderProperties tradeOrderProperties, + DeliveryExpressDO express) { + AppTradeOrderDetailRespVO orderVO = convert3(order, orderItems); + orderVO.setPayExpireTime(order.getCreateTime().plus(tradeOrderProperties.getPayExpireTime())); + if (StrUtil.isNotEmpty(order.getPayChannelCode())) { + orderVO.setPayChannelName(DictFrameworkUtils.getDictDataLabel(DictTypeConstants.CHANNEL_CODE, order.getPayChannelCode())); + } + // 处理收货地址 + orderVO.setReceiverAreaName(AreaUtils.format(order.getReceiverAreaId())); + if (express != null) { + orderVO.setLogisticsId(express.getId()).setLogisticsName(express.getName()); + } + return orderVO; + } + + AppTradeOrderDetailRespVO convert3(TradeOrderDO order, List items); + + AppTradeOrderItemRespVO convert03(TradeOrderItemDO bean); + + @Mappings({ + @Mapping(target = "skuId", source = "tradeOrderItemDO.skuId"), + @Mapping(target = "orderId", source = "tradeOrderItemDO.orderId"), + @Mapping(target = "orderItemId", source = "tradeOrderItemDO.id"), + @Mapping(target = "descriptionScores", source = "createReqVO.descriptionScores"), + @Mapping(target = "benefitScores", source = "createReqVO.benefitScores"), + @Mapping(target = "content", source = "createReqVO.content"), + @Mapping(target = "picUrls", source = "createReqVO.picUrls"), + @Mapping(target = "anonymous", source = "createReqVO.anonymous"), + @Mapping(target = "userId", source = "tradeOrderItemDO.userId") + }) +// ProductCommentCreateReqDTO convert04(AppTradeOrderItemCommentCreateReqVO createReqVO, TradeOrderItemDO tradeOrderItemDO); +// +// TradePriceCalculateReqBO convert(AppTradeOrderSettlementReqVO settlementReqVO); +// + default TradePriceCalculateReqBO convert(Long userId, AppTradeOrderSettlementReqVO settlementReqVO, + List cartList) { + TradePriceCalculateReqBO reqBO = new TradePriceCalculateReqBO().setUserId(userId) + .setItems(new ArrayList<>(settlementReqVO.getItems().size())) + .setCouponId(settlementReqVO.getCouponId()).setPointStatus(settlementReqVO.getPointStatus()) + // 物流信息 + .setDeliveryType(settlementReqVO.getDeliveryType()).setAddressId(settlementReqVO.getAddressId()) + .setPickUpStoreId(settlementReqVO.getPickUpStoreId()) + // 各种活动 + .setSeckillActivityId(settlementReqVO.getSeckillActivityId()) + .setBargainRecordId(settlementReqVO.getBargainRecordId()) + .setCombinationActivityId(settlementReqVO.getCombinationActivityId()) + .setCombinationHeadId(settlementReqVO.getCombinationHeadId()) + .setPointActivityId(settlementReqVO.getPointActivityId()); + // 商品项的构建 + Map cartMap = convertMap(cartList, CartDO::getId); + for (AppTradeOrderSettlementReqVO.Item item : settlementReqVO.getItems()) { + // 情况一:skuId + count + if (item.getSkuId() != null) { + reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(item.getSkuId()).setCount(item.getCount()) + .setSelected(true)); // true 的原因,下单一定选中 + continue; + } + // 情况二:cartId + CartDO cart = cartMap.get(item.getCartId()); + if (cart == null) { + continue; + } + reqBO.getItems().add(new TradePriceCalculateReqBO.Item().setSkuId(cart.getSkuId()).setCount(cart.getCount()) + .setCartId(item.getCartId()).setSelected(true)); // true 的原因,下单一定选中 + } + return reqBO; + } +// + default AppTradeOrderSettlementRespVO convert(TradePriceCalculateRespBO calculate, MemberAddressRespDTO address) { + AppTradeOrderSettlementRespVO respVO = convert0(calculate, address); + if (address != null) { + respVO.getAddress().setAreaName(AreaUtils.format(address.getAreaId())); + } + return respVO; + } +// + AppTradeOrderSettlementRespVO convert0(TradePriceCalculateRespBO calculate, MemberAddressRespDTO address); + + List convertList02(List list); + + TradeOrderDO convert(TradeOrderUpdateAddressReqVO reqVO); + + TradeOrderDO convert(TradeOrderUpdatePriceReqVO reqVO); + + TradeOrderDO convert(TradeOrderRemarkReqVO reqVO); + + default BrokerageAddReqBO convert(MemberUserRespDTO user, TradeOrderItemDO item, + ProductSpuRespDTO spu, ProductSkuRespDTO sku) { + BrokerageAddReqBO bo = new BrokerageAddReqBO().setBizId(String.valueOf(item.getId())).setSourceUserId(item.getUserId()) + .setBasePrice(item.getPayPrice()) + .setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuId())) + .setFirstFixedPrice(0).setSecondFixedPrice(0); + if (BooleanUtil.isTrue(spu.getSubCommissionType())) { + bo.setFirstFixedPrice(sku.getFirstBrokeragePrice()).setSecondFixedPrice(sku.getSecondBrokeragePrice()); + } + return bo; + } + + @Named("convertList04") + List convertList04(List list); + + @Mappings({ + @Mapping(target = "spuId", source = "item.spuId"), + @Mapping(target = "skuId", source = "item.skuId"), + @Mapping(target = "count", source = "item.count"), + @Mapping(target = "orderId", source = "order.id"), + @Mapping(target = "userId", source = "order.userId"), + }) + CombinationRecordCreateReqDTO convert(TradeOrderDO order, TradeOrderItemDO item); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/TradeOrderLogConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/TradeOrderLogConvert.java new file mode 100644 index 0000000..dfe7aee --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/TradeOrderLogConvert.java @@ -0,0 +1,15 @@ +package com.tashow.cloud.trade.convert.order; + +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.service.order.bo.TradeOrderLogCreateReqBO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface TradeOrderLogConvert { + + TradeOrderLogConvert INSTANCE = Mappers.getMapper(TradeOrderLogConvert.class); + + TradeOrderLogDO convert(TradeOrderLogCreateReqBO bean); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/CombinationRecordCreateReqDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/CombinationRecordCreateReqDTO.java new file mode 100644 index 0000000..1db81d0 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/CombinationRecordCreateReqDTO.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.trade.convert.order.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 拼团记录的创建 Request DTO + * + * @author HUIHUI + */ +@Data +public class CombinationRecordCreateReqDTO { + + /** + * 拼团活动编号 + */ + @NotNull(message = "拼团活动编号不能为空") + private Long activityId; + /** + * spu 编号 + */ + @NotNull(message = "spu 编号不能为空") + private Long spuId; + /** + * sku 编号 + */ + @NotNull(message = "sku 编号不能为空") + private Long skuId; + /** + * 购买的商品数量 + */ + @NotNull(message = "购买数量不能为空") + private Integer count; + /** + * 订单编号 + */ + @NotNull(message = "订单编号不能为空") + private Long orderId; + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 团长编号 + */ + private Long headId; + /** + * 拼团商品单价 + */ + @NotNull(message = "拼团商品单价不能为空") + private Integer combinationPrice; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/OrderDetailVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/OrderDetailVO.java new file mode 100644 index 0000000..7c43a85 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/OrderDetailVO.java @@ -0,0 +1,75 @@ +package com.tashow.cloud.trade.convert.order.dto; + +import lombok.Data; + +import java.util.Date; + +// 订单详情 VO +@Data +public class OrderDetailVO { + + // 订单信息 + // 订单状态 - 必填,示例:待付款/待确定/待服务/服务中/待评价/已完成 + private String orderStatus; + // 订单编号 - 必填,示例:BY66587788 + private String orderCode; + // 订单类型 - 必填,示例:宠物寄养(用来区分类目/业务) + private String orderType; + // 订单来源 - 必填,示例:官方APP/微信小程序/支付宝小程序/抖音小程序/H5/网页端 + private String orderSource; + // 创建时间 - 必填,示例:2025-07-01 12:00:00 + private Date createTime; + // 完成时间 - 必填,示例:2025-07-01 12:00:00 + private Date completeTime; + // 取消时间 - 必填,示例:2025-07-01 12:00:00 + private Date cancelTime; + + // 支付信息 + // 订单金额 - 必填,示例:1000 + private Integer orderAmount; + // 优惠金额 - 必填,示例:200 + private Integer discountAmount; + // 实付金额 - 必填,示例:700 + private Integer actualPayAmount; + // 支付方式 - 必填,示例:微信支付/支付宝支付/云闪付/银行卡 + private String payMethod; + // 支付时间 - 必填,示例:2025-07-01 12:00:00 + private Date payTime; + // 交易流水 - 必填,示例:557898747(主订单流水) + private String transactionId; + + // 售后信息 + // 退款状态 - 必填,示例:待审核/待退款/已退款/已拒绝 + private String refundStatus; + // 售后编号 - 必填,示例:450878 + private String afterSaleCode; + // 售后类型 - 必填,示例:仅退款/退货退款/可扩展增加使用 + private String afterSaleType; + // 退款类型 - 必填,示例:订单退款/差价退款/运费退款/可扩展增加 + private String refundType; + // 退款原因 - 必填,示例:不想要了 + private String refundReason; + // 退款说明 - 必填,示例:这里是用户填写的退款描述 + private String refundRemark; + // 退款金额 - 必填 + private Integer refundAmount; + // 申请人 - 必填,示例:用户/商家/平台+ID + private String applicant; + // 审核人 - 必填,示例:姓名+ID + private String auditor; + // 审核时间 - 必填,示例:2025-07-01 12:00:00 + private Date auditTime; + // 退款方式 - 必填,示例:系统自动退款 + private String refundMethod; + // 退款至 - 必填,示例:原支付方式返还 + private String refundTo; + // 关闭时间 - 必填,示例:2025-07-01 12:00:00 + private Date closeTime; + + // 用户信息 + // 用户昵称 - 必填,示例:钱多多 + private String userNickname; + // 用户ID - 必填,示例:666 + private Long userId; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductDetailVO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductDetailVO.java new file mode 100644 index 0000000..4fb8399 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductDetailVO.java @@ -0,0 +1,66 @@ +package com.tashow.cloud.trade.convert.order.dto; + +import lombok.Data; + +import java.util.Date; +import java.util.List; + +// 商品详情 VO +@Data +public class ProductDetailVO { + + // 商品信息 + // 商品商家 - 必填,示例:百业到家官方自营旗舰店 + private String merchantName; + // 商品名称 - 必填,示例:商品名称商品名称商品名称 + private String productName; + // SPU ID - 必填,示例:778899 + private Long spuId; + // 规格 - 必填,示例:标准,单独火化,基础清洁 + private String specifications; + // SKU ID - 必填,示例:112255 + private Long skuId; + // 服务内容补充说明 - 必填,示例:服务内容补充说明服务内容补充说明服务内容补充说明服务内容补充说明服务内容补充说明服务内容补充说明 + private String serviceDescription; + + // 遗体信息 + // 宠物名字 - 必填,示例:钱多多 + private String petName; + // 宠物品种 - 必填,示例:阿拉斯加 + private String petBreed; + // 体重/体重 - 必填,示例:大型犬-50kg + private String petWeight; + // 离世时间 - 必填,示例:2025-07-01 12:00:00 + private Date deathTime; + // 离世原因 - 必填,示例:自然离世 + private String deathReason; + // 遗体照片 - 必填 + private List corpsePhotos; + + // 预约信息 + // 预约类型 - 必填,示例:常规/加急 + private String appointmentType; + // 更改规则 - 必填,示例:服务开始前4小时,可修改1次 + private String changeRule; + // 预约时间 - 必填,示例:2025.07.14-14:00-16:00/ + private String appointmentTime; + // 上门服务时间 - 必填,示例:2小时上门 + private String serviceTime; + // 修改时间 - 必填,示例:2025.07.14-14:00-16:00 + private Date modifyTime; + // 联系方式 - 必填,示例:张**,186****6151 + private String contactInfo; + // 服务地址 - 必填,示例:福州台江区万达广场达中心 506单元 + private String serviceAddress; + // 服务备注 - 必填,示例:家有萌"虎",小心猫咪 + private String serviceRemark; + + // 付款信息 + // SKU售价 - 必填,示例:¥500 + private Integer skuPrice; + // 优惠金额 - 必填,示例:¥00(使用优惠后平铺扣减的金额) + private Integer discountAmount; + // 实付金额 - 必填,示例:¥400 + private Integer actualPayAmount; + +} \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSkuRespDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSkuRespDTO.java new file mode 100644 index 0000000..73f814b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSkuRespDTO.java @@ -0,0 +1,45 @@ +package com.tashow.cloud.trade.convert.order.dto; + +// 移除Swagger相关导入 + +import com.tashow.cloud.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO; +import lombok.Data; + +import java.util.List; + +// RPC 服务 - 商品 SKU 信息 Response DTO +@Data +public class ProductSkuRespDTO { + + // 商品 SKU 编号 - 必填,示例:1024 + private Long id; + // SPU 编号 - 必填,示例:2048 + private Long spuId; + + // 属性数组 - 必填 + private List properties; + + // 销售价格,单位:分 - 必填,示例:100 + private Integer price; + // 市场价,单位:分 - 必填,示例:200 + private Integer marketPrice; + // 成本价,单位:分 - 必填,示例:300 + private Integer costPrice; + // SKU 的条形码 - 必填,示例:123456789 + private String barCode; + // 图片地址 - 必填,示例:https://www.iocoder.cn/xxx.jpg + private String picUrl; + + // 库存 - 必填,示例:100 + private Integer stock; + // 商品重量,单位:kg 千克 - 必填,示例:1.5 + private Double weight; + // 商品体积,单位:m^3 平米 - 必填,示例:3.0 + private Double volume; + + // 一级分销的佣金,单位:分 - 必填,示例:550 + private Integer firstBrokeragePrice; + // 二级分销的佣金,单位:分 - 必填,示例:250 + private Integer secondBrokeragePrice; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSkuUpdateStockReqDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSkuUpdateStockReqDTO.java new file mode 100644 index 0000000..6c63631 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSkuUpdateStockReqDTO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.convert.order.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +//RPC 服务 - 商品 SKU 更新库存 Request DTO +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductSkuUpdateStockReqDTO { + + //商品 SKU 数组 + @NotNull(message = "商品 SKU 不能为空") + private List items; + + @Data + public static class Item { + + //商品 SKU 编号 + @NotNull(message = "商品 SKU 编号不能为空") + private Long id; + + //库存变化数量 + @NotNull(message = "库存变化数量不能为空") + private Integer incrCount; // 正数:增加库存;负数:扣减库存 + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSpuRespDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSpuRespDTO.java new file mode 100644 index 0000000..38237e5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/order/dto/ProductSpuRespDTO.java @@ -0,0 +1,111 @@ +package com.tashow.cloud.trade.convert.order.dto; + +import lombok.Data; + +import java.util.List; + +// TODO @LeeYan9: ProductSpuRespDTO + +/** + * 商品 SPU 信息 Response DTO + * + * @author LeeYan9 + * @since 2022-08-26 + */ +@Data +public class ProductSpuRespDTO { + + /** + * 商品 SPU 编号,自增 + */ + private Long id; + + // ========== 基本信息 ========= + + /** + * 商品名称 + */ + private String name; + /** + * 单位 + * + * 对应 product_unit 数据字典 + */ + private Integer unit; + + /** + * 商品分类编号 + */ + private Long categoryId; + /** + * 商品封面图 + */ + private String picUrl; + + /** + * 商品状态 + *

+ * 枚举 {@link ProductSpuStatusEnum} + */ + private Integer status; + + // ========== SKU 相关字段 ========= + + /** + * 规格类型 + * + * false - 单规格 + * true - 多规格 + */ + private Boolean specType; + /** + * 商品价格,单位使用:分 + */ + private Integer price; + /** + * 市场价,单位使用:分 + */ + private Integer marketPrice; + /** + * 成本价,单位使用:分 + */ + private Integer costPrice; + /** + * 库存 + */ + private Integer stock; + + // ========== 物流相关字段 ========= + + /** + * 配送方式数组 + * + * 对应 DeliveryTypeEnum 枚举 + */ + private List deliveryTypes; + + /** + * 物流配置模板编号 + * + * 对应 TradeDeliveryExpressTemplateDO 的 id 编号 + */ + private Long deliveryTemplateId; + + // ========== 营销相关字段 ========= + + /** + * 赠送积分 + */ + private Integer giveIntegral; + + // ========== 分销相关字段 ========= + + /** + * 分销类型 + * + * false - 默认 + * true - 自行设置 + */ + private Boolean subCommissionType; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/package-info.java new file mode 100644 index 0000000..963fa07 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/convert/package-info.java @@ -0,0 +1 @@ +package com.tashow.cloud.trade.convert; \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/aftersale/AfterSaleDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/aftersale/AfterSaleDO.java new file mode 100644 index 0000000..d808137 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/aftersale/AfterSaleDO.java @@ -0,0 +1,203 @@ +package com.tashow.cloud.trade.dal.dataobject.aftersale; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleStatusEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleTypeEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleWayEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 售后订单,用于处理 {@link TradeOrderDO} 交易订单的退款退货流程 + * + * @author 芋道源码 + */ +@TableName(value = "trade_after_sale", autoResultMap = true) +@KeySequence("trade_after_sale_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class AfterSaleDO extends BaseDO { + + /** + * 售后编号,主键自增 + */ + private Long id; + /** + * 售后单号 + * + * 例如说,1146347329394184195 + */ + private String no; + /** + * 退款状态 + * + * 枚举 {@link AfterSaleStatusEnum} + */ + private Integer status; + /** + * 售后方式 + * + * 枚举 {@link AfterSaleWayEnum} + */ + private Integer way; + /** + * 售后类型 + * + * 枚举 {@link AfterSaleTypeEnum} + */ + private Integer type; + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 编号 + */ + private Long userId; + /** + * 申请原因 + * + * type = 退款,对应 trade_after_sale_refund_reason 类型 + * type = 退货退款,对应 trade_after_sale_refund_and_return_reason 类型 + */ + private String applyReason; + /** + * 补充描述 + */ + private String applyDescription; + /** + * 补充凭证图片 + * + * 数组,以逗号分隔 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List applyPicUrls; + + // ========== 交易订单相关 ========== + /** + * 交易订单编号 + * + * 关联 {@link TradeOrderDO#getId()} + */ + private Long orderId; + /** + * 订单流水号 + * + * 冗余 {@link TradeOrderDO#getNo()} + */ + private String orderNo; + /** + * 交易订单项编号 + * + * 关联 {@link TradeOrderItemDO#getId()} + */ + private Long orderItemId; + /** + * 商品 SPU 编号 + * + * 关联 ProductSpuDO 的 id 字段 + * 冗余 {@link TradeOrderItemDO#getSpuId()} + */ + private Long spuId; + /** + * 商品 SPU 名称 + * + * 关联 ProductSkuDO 的 name 字段 + * 冗余 {@link TradeOrderItemDO#getSpuName()} + */ + private String spuName; + /** + * 商品 SKU 编号 + * + * 关联 ProductSkuDO 的编号 + */ + private Long skuId; + /** + * 属性数组,JSON 格式 + * + * 冗余 {@link TradeOrderItemDO#getProperties()} + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List properties; + /** + * 商品图片 + * + * 冗余 {@link TradeOrderItemDO#getPicUrl()} + */ + private String picUrl; + /** + * 退货商品数量 + */ + private Integer count; + + // ========== 审批相关 ========== + + /** + * 审批时间 + */ + private LocalDateTime auditTime; + /** + * 审批人 + * + * 关联 AdminUserDO 的 id 编号 + */ + private Long auditUserId; + /** + * 审批备注 + * + * 注意,只有审批不通过才会填写 + */ + private String auditReason; + + // ========== 退款相关 ========== + /** + * 退款金额,单位:分。 + */ + private Integer refundPrice; + /** + * 支付退款编号 + * + * 对接 pay-module-biz 支付服务的退款订单编号,即 PayRefundDO 的 id 编号 + */ + private Long payRefundId; + /** + * 退款时间 + */ + private LocalDateTime refundTime; + + // ========== 退货相关 ========== + /** + * 退货物流公司编号 + * + * 关联 LogisticsDO 的 id 编号 + */ + private Long logisticsId; + /** + * 退货物流单号 + */ + private String logisticsNo; + /** + * 退货时间 + */ + private LocalDateTime deliveryTime; + /** + * 收货时间 + */ + private LocalDateTime receiveTime; + /** + * 收货备注 + * + * 注意,只有拒绝收货才会填写 + */ + private String receiveReason; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/aftersale/AfterSaleLogDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/aftersale/AfterSaleLogDO.java new file mode 100644 index 0000000..7af6ae8 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/aftersale/AfterSaleLogDO.java @@ -0,0 +1,71 @@ +package com.tashow.cloud.trade.dal.dataobject.aftersale; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleOperateTypeEnum; +import lombok.*; + +/** + * 交易售后日志 DO + * + * @author 芋道源码 + */ +@TableName("trade_after_sale_log") +@KeySequence("trade_after_sale_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AfterSaleLogDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + * + * 关联 1:AdminUserDO 的 id 字段 + * 关联 2:MemberUserDO 的 id 字段 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 售后编号 + * + * 关联 {@link AfterSaleDO#getId()} + */ + private Long afterSaleId; + /** + * 操作前状态 + */ + private Integer beforeStatus; + /** + * 操作后状态 + */ + private Integer afterStatus; + + /** + * 操作类型 + * + * 枚举 {@link AfterSaleOperateTypeEnum} + */ + private Integer operateType; + /** + * 操作明细 + */ + private String content; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/cart/CartDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/cart/CartDO.java new file mode 100644 index 0000000..654eb51 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/cart/CartDO.java @@ -0,0 +1,61 @@ +package com.tashow.cloud.trade.dal.dataobject.cart; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 购物车的商品信息 DO + * + * 每个商品,对应一条记录,通过 {@link #spuId} 和 {@link #skuId} 关联 + * + * @author 芋道源码 + */ +@TableName("trade_cart") +@KeySequence("trade_cart_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class CartDO extends BaseDO { + + // ========= 基础字段 BEGIN ========= + + /** + * 编号,唯一自增 + */ + private Long id; + + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 编号 + */ + private Long userId; + + // ========= 商品信息 ========= + + /** + * 商品 SPU 编号 + * + * 关联 ProductSpuDO 的 id 编号 + */ + private Long spuId; + /** + * 商品 SKU 编号 + * + * 关联 ProductSkuDO 的 id 编号 + */ + private Long skuId; + /** + * 商品购买数量 + */ + private Integer count; + /** + * 是否选中 + */ + private Boolean selected; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressDO.java new file mode 100644 index 0000000..57cc98d --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressDO.java @@ -0,0 +1,60 @@ +package com.tashow.cloud.trade.dal.dataobject.delivery; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.Data; + +/** + * 快递公司 DO + * + * @author jason + */ +@TableName(value ="trade_delivery_express") +@KeySequence("trade_delivery_express_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class DeliveryExpressDO extends BaseDO { + + /** + * 编号,自增 + */ + @TableId + private Long id; + + /** + * 快递公司 code + */ + private String code; + + /** + * 快递公司名称 + */ + private String name; + + /** + * 快递公司 logo + */ + private String logo; + + /** + * 排序 + */ + private Integer sort; + + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + + // TODO 芋艿:c 和结算相关的字段,后续在看 + // partnerId 是否需要月结账号 + // partnerKey 是否需要月结密码 + // net 是否需要取件网店 + // account 账号 + // password 网点名称 + // isShow 是否显示 +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateChargeDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateChargeDO.java new file mode 100644 index 0000000..d6e0a04 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateChargeDO.java @@ -0,0 +1,67 @@ +package com.tashow.cloud.trade.dal.dataobject.delivery; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.mybatis.mybatis.core.type.IntegerListTypeHandler; +import lombok.Data; + +import java.util.List; + +/** + * 快递运费模板计费配置 DO + * + * @author jason + */ +@TableName(value ="trade_delivery_express_template_charge", autoResultMap = true) +@KeySequence("trade_delivery_express_template_charge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class DeliveryExpressTemplateChargeDO extends BaseDO { + + /** + * 编号,自增 + */ + @TableId + private Long id; + + /** + * 配送模板编号 + * + * 关联 {@link DeliveryExpressTemplateDO#getId()} + */ + private Long templateId; + + /** + * 配送区域编号列表 + */ + @TableField(typeHandler = IntegerListTypeHandler.class) + private List areaIds; + + /** + * 配送计费方式 + * + * 冗余 {@link DeliveryExpressTemplateDO#getChargeMode()} + */ + private Integer chargeMode; + + /** + * 首件数量(件数,重量,或体积) + */ + private Double startCount; + /** + * 起步价,单位:分 + */ + private Integer startPrice; + + /** + * 续件数量(件, 重量,或体积) + */ + private Double extraCount; + /** + * 额外价,单位:分 + */ + private Integer extraPrice; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateDO.java new file mode 100644 index 0000000..05676db --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateDO.java @@ -0,0 +1,43 @@ +package com.tashow.cloud.trade.dal.dataobject.delivery; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.tradeapi.enums.delivery.DeliveryExpressChargeModeEnum; +import lombok.Data; + +/** + * 快递运费模板 DO + * + * @author jason + */ +@TableName("trade_delivery_express_template") +@KeySequence("trade_delivery_express_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class DeliveryExpressTemplateDO extends BaseDO { + + /** + * 编号,自增 + */ + @TableId + private Long id; + + /** + * 模板名称 + */ + private String name; + + /** + * 配送计费方式 + * + * 枚举 {@link DeliveryExpressChargeModeEnum} + */ + private Integer chargeMode; + + /** + * 排序 + */ + private Integer sort; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java new file mode 100644 index 0000000..9f7d391 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryExpressTemplateFreeDO.java @@ -0,0 +1,57 @@ +package com.tashow.cloud.trade.dal.dataobject.delivery; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.mybatis.mybatis.core.type.IntegerListTypeHandler; +import lombok.Data; + +import java.util.List; + +/** + * 快递运费模板包邮配置 DO + * + * @author jason + */ +@TableName(value ="trade_delivery_express_template_free", autoResultMap = true) +@KeySequence("trade_delivery_express_template_free_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class DeliveryExpressTemplateFreeDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 配送模板编号 + * + * 关联 {@link DeliveryExpressTemplateDO#getId()} + */ + private Long templateId; + + + /** + * 配送区域编号列表 + */ + @TableField(typeHandler = IntegerListTypeHandler.class) + private List areaIds; + + /** + * 包邮金额,单位:分 + * + * 订单总金额 > 包邮金额时,才免运费 + */ + private Integer freePrice; + + /** + * 包邮件数 + * + * 订单总件数 > 包邮件数时,才免运费 + */ + private Integer freeCount; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryPickUpStoreDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryPickUpStoreDO.java new file mode 100644 index 0000000..801bdf3 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/delivery/DeliveryPickUpStoreDO.java @@ -0,0 +1,98 @@ +package com.tashow.cloud.trade.dal.dataobject.delivery; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.mybatis.mybatis.core.type.LongListTypeHandler; +import com.tashow.cloud.systemapi.api.user.dto.AdminUserRespDTO; +import lombok.Data; + +import java.time.LocalTime; +import java.util.List; + +/** + * 自提门店 DO + * + * @author jason + */ +@TableName(value ="trade_delivery_pick_up_store", autoResultMap = true) +@KeySequence("trade_delivery_pick_up_store_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +public class DeliveryPickUpStoreDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + + /** + * 门店名称 + */ + private String name; + + /** + * 门店简介 + */ + private String introduction; + + /** + * 门店手机 + */ + private String phone; + + /** + * 区域编号 + */ + private Integer areaId; + + /** + * 门店详细地址 + */ + private String detailAddress; + + /** + * 门店 logo + */ + private String logo; + + /** + * 营业开始时间 + */ + private LocalTime openingTime; + + /** + * 营业结束时间 + */ + private LocalTime closingTime; + + /** + * 纬度 + */ + private Double latitude; + /** + * 经度 + */ + private Double longitude; + + /** + * 核销员工用户编号数组 + * + * 订单自提核销时,只有对应门店的店员才能核销 + * + * 关联 {@link AdminUserRespDTO#getId()} 管理员编号 + */ + @TableField(typeHandler = LongListTypeHandler.class) + private List verifyUserIds; + + /** + * 门店状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderDO.java new file mode 100644 index 0000000..9a677ba --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderDO.java @@ -0,0 +1,251 @@ +package com.tashow.cloud.trade.dal.dataobject.order; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.TerminalEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import com.tashow.cloud.tradeapi.enums.delivery.DeliveryTypeEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderCancelTypeEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderRefundStatusEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderStatusEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderTypeEnum; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 交易订单 DO + * + * @author 芋道源码 + */ +@TableName(value = "tz_trade_order", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TradeOrderDO extends BaseDO { + + /** + * 发货物流公司编号 - 空(无需发货) + */ + public static final Long LOGISTICS_ID_NULL = 0L; + + // ========== 订单基本信息 ========== + /** + * 订单编号,主键自增 + */ + private Long id; + /** + * 订单流水号 + * + * 例如说,1146347329394184195 + */ + private String orderNum; + /** + * 订单类型 + * + * 枚举 {@link TradeOrderTypeEnum} + */ + private Integer orderType; + /** + * 订单来源 + * + * 枚举 {@link TerminalEnum} + */ + private Integer orderTerminal; + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 编号 + */ + private Long userId; + private String userMobile; + private String userName; + /** + * 用户 IP + */ + private String userIp; + /** + * 用户备注 + */ + private String userRemark; + /** + * 订单状态 + * + * 枚举 {@link TradeOrderStatusEnum} + */ + private Integer orderStatus; + private Integer financeStatus; + /** + * 订单完成时间 + */ + private LocalDateTime finishTime; + /** + * 订单取消时间 + */ + private LocalDateTime cancelTime; + /** + * 取消类型 + * + * 枚举 {@link TradeOrderCancelTypeEnum} + */ + private Integer cancelType; + /** + * 商家备注 + */ + private String merchantRemark; + private String merchantName; + private Long orderCategoryId; + /** + * 是否评价 + * + * true - 已评价 + * false - 未评价 + */ + private Boolean commentStatus; + + // ========== 价格 + 支付基本信息 ========== + + // 价格文档 - 淘宝:https://open.taobao.com/docV3.htm?docId=108471&docType=1 + // 价格文档 - 京东到家:https://openo2o.jddj.com/api/getApiDetail/182/4d1494c5e7ac4679bfdaaed950c5bc7f.htm + // 价格文档 - 有赞:https://doc.youzanyun.com/detail/API/0/906 + + /** + * 支付订单编号 + * + * 对接 pay-module-biz 支付服务的支付订单编号,即 PayOrderDO 的 id 编号 + */ + private Long payOrderId; + /** + * 是否已支付 + * + * true - 已经支付过 + * false - 没有支付过 + */ + private Boolean payStatus; + /** + * 付款时间 + */ + private LocalDateTime payTime; + /** + * 支付渠道 + * + * 对应 PayChannelEnum 枚举 + */ + private String payChannelCode; + + /** + * 商品原价,单位:分 + * + * totalPrice = {@link TradeOrderItemDO#getPrice()} * {@link TradeOrderItemDO#getCount()} 求和 + * + * 对应 taobao 的 trade.total_fee 字段 + */ + private Integer totalPrice; + /** + * 优惠金额,单位:分 + * + * 对应 taobao 的 order.discount_fee 字段 + */ + private Integer discountPrice; + /** + * 运费金额,单位:分 + */ + private Integer deliveryPrice; + /** + * 订单调价,单位:分 + * + * 正数,加价;负数,减价 + */ + private Integer adjustPrice; + /** + * 应付金额(总),单位:分 + * + * = {@link #totalPrice} + * - {@link #discountPrice} + * + {@link #deliveryPrice} + * + {@link #adjustPrice} + */ + private Integer payPrice; + + // ========== 收件 + 物流基本信息 ========== + /** + * 配送方式 + * + * 枚举 {@link DeliveryTypeEnum} + */ + private Integer deliveryType; + /** + * 发货物流公司编号 + * + * 如果无需发货,则 logisticsId 设置为 0。原因是,不想再添加额外字段 + * + * 关联 {@link DeliveryExpressDO#getId()} + */ + private Long logisticsId; + /** + * 发货物流单号 + * + * 如果无需发货,则 logisticsNo 设置 ""。原因是,不想再添加额外字段 + */ + private String logisticsNo; + /** + * 发货时间 + */ + private LocalDateTime deliveryTime; + + /** + * 收货时间 + */ + private LocalDateTime receiveTime; + /** + * 收件人名称 + */ + private String receiverName; + /** + * 收件人手机 + */ + private String receiverMobile; + /** + * 收件人地区编号 + */ + private Integer receiverAreaId; + /** + * 收件人详细地址 + */ + private String receiverDetailAddress; + + /** + * 自提门店编号 + * + * 关联 {@link DeliveryPickUpStoreDO#getId()} + */ + private Long pickUpStoreId; + /** + * 自提核销码 + */ + private String pickUpVerifyCode; + + // ========== 售后基本信息 ========== + /** + * 售后状态 + * + * 枚举 {@link TradeOrderRefundStatusEnum} + */ + private Integer refundStatus; + /** + * 退款金额,单位:分 + * + * 注意,退款并不会影响 {@link #payPrice} 实际支付金额 + * 也就说,一个订单最终产生多少金额的收入 = payPrice - refundPrice + */ + private Integer refundPrice; + + private Integer afterSaleStatus; + private Integer subType; + private LocalDateTime subTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderItemDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderItemDO.java new file mode 100644 index 0000000..3d2198a --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderItemDO.java @@ -0,0 +1,173 @@ +package com.tashow.cloud.trade.dal.dataobject.order; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.Version; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 交易订单项 DO + * + * @author 芋道源码 + */ +@TableName(value = "trade_order_item", autoResultMap = true) +@Data +@Accessors(chain = true) +@EqualsAndHashCode(callSuper = true) +public class TradeOrderItemDO extends BaseDO { + + // ========== 订单项基本信息 ========== + /** + * 编号 + */ + private Long id; + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 编号 + */ + private Long userId; + /** + * 订单编号 + * + * 关联 {@link TradeOrderDO#getId()} + */ + private Long orderId; + /** + * 购物车项编号 + */ + private Long cartId; + + // ========== 商品基本信息; 冗余较多字段,减少关联查询 ========== + /** + * 商品 SPU 编号 + * + * 关联 ProductSkuDO 的 spuId 编号 + */ + private Long spuId; + /** + * 商品 SPU 名称 + * + * 冗余 ProductSkuDO 的 spuName 编号 + */ + private String spuName; + + private Integer spuType; + /** + * 商品 SKU 编号 + * + * 关联 ProductSkuDO 的 id 编号 + */ + private Long skuId; + private String skuName; + /** + * 属性数组,JSON 格式 + * + * 冗余 ProductSkuDO 的 properties 字段 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List properties; + /** + * 商品图片 + */ + private String picUrl; + /** + * 购买数量 + */ + private Integer count; + + // ========== 价格 + 支付基本信息 ========== + //商品成本 + private Integer expensePrice; + + /** + * 商品原价(单),单位:分 + * + * 对应 ProductSkuDO 的 price 字段 + * 对应 taobao 的 order.price 字段 + */ + private Integer price; + /** + * 优惠金额(总),单位:分 + * + * 对应 taobao 的 order.discount_fee 字段 + */ + private Integer discountPrice; + /** + * 运费金额(总),单位:分 + */ + private Integer deliveryPrice; + /** + * 订单调价(总),单位:分 + * + * 正数,加价;负数,减价 + */ + private Integer adjustPrice; + /** + * 应付金额(总),单位:分 + * + * = {@link #price} * {@link #count} + * - {@link #discountPrice} + * + {@link #deliveryPrice} + * + {@link #adjustPrice} + */ + private Integer payPrice; + //实收金额 + private Integer livePrice; + + private Integer orderType; + private LocalDateTime orderTime; + private String serveAddress; + private String serveInfo; + private String serveExtInfo; + private String priceExtInfo; + + @Version + private Integer version; + + + + /** + * 商品属性 + */ + @Data + public static class Property implements Serializable { + + /** + * 属性编号 + * + * 关联 ProductPropertyDO 的 id 编号 + */ + private Long propertyId; + /** + * 属性名字 + * + * 关联 ProductPropertyDO 的 name 字段 + */ + private String propertyName; + + /** + * 属性值编号 + * + * 关联 ProductPropertyValueDO 的 id 编号 + */ + private Long valueId; + /** + * 属性值名字 + * + * 关联 ProductPropertyValueDO 的 name 字段 + */ + private String valueName; + + } + +} + diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderLogDO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderLogDO.java new file mode 100644 index 0000000..878551a --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/dataobject/order/TradeOrderLogDO.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.trade.dal.dataobject.order; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderOperateTypeEnum; +import lombok.*; + +/** + * 订单日志 DO + * + * @author 陈賝 + */ +@TableName("trade_order_log") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TradeOrderLogDO extends BaseDO { + + /** + * 用户类型 - 系统 + * + * 例如说:Job 自动过期订单时,通过系统自动操作 + */ + public static final Integer USER_TYPE_SYSTEM = 0; + /** + * 用户编号 - 系统 + */ + public static final Long USER_ID_SYSTEM = 0L; + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 用户编号 + * + * 关联 AdminUserDO 的 id 字段、或者 MemberUserDO 的 id 字段 + */ + private Long userId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + + /** + * 订单号 + * + * 关联 {@link TradeOrderDO#getId()} + */ + private Long orderId; + /** + * 操作前状态 + */ + private Integer beforeStatus; + /** + * 操作后状态 + */ + private Integer afterStatus; + + /** + * 操作类型 + * + * {@link TradeOrderOperateTypeEnum} + */ + private Integer operateType; + /** + * 订单日志信息 + */ + private String content; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/aftersale/AfterSaleLogMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/aftersale/AfterSaleLogMapper.java new file mode 100644 index 0000000..1b6ca67 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/aftersale/AfterSaleLogMapper.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.dal.mysql.aftersale; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface AfterSaleLogMapper extends BaseMapperX { + + default List selectListByAfterSaleId(Long afterSaleId) { + return selectList(new LambdaQueryWrapper() + .eq(AfterSaleLogDO::getAfterSaleId, afterSaleId) + .orderByDesc(AfterSaleLogDO::getCreateTime)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/aftersale/AfterSaleMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/aftersale/AfterSaleMapper.java new file mode 100644 index 0000000..1a5ca22 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/aftersale/AfterSaleMapper.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.trade.dal.mysql.aftersale; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; + +@Mapper +public interface AfterSaleMapper extends BaseMapperX { + + default PageResult selectPage(AfterSalePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(AfterSaleDO::getUserId, reqVO.getUserId()) + .likeIfPresent(AfterSaleDO::getNo, reqVO.getNo()) + .eqIfPresent(AfterSaleDO::getStatus, reqVO.getStatus()) + .eqIfPresent(AfterSaleDO::getType, reqVO.getType()) + .eqIfPresent(AfterSaleDO::getWay, reqVO.getWay()) + .likeIfPresent(AfterSaleDO::getOrderNo, reqVO.getOrderNo()) + .likeIfPresent(AfterSaleDO::getSpuName, reqVO.getSpuName()) + .betweenIfPresent(AfterSaleDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(AfterSaleDO::getId)); + } + + default PageResult selectPage(Long userId, PageParam pageParam) { + return selectPage(pageParam, new LambdaQueryWrapperX() + .eqIfPresent(AfterSaleDO::getUserId, userId) + .orderByDesc(AfterSaleDO::getId)); + } + + default int updateByIdAndStatus(Long id, Integer status, AfterSaleDO update) { + return update(update, new LambdaUpdateWrapper() + .eq(AfterSaleDO::getId, id).eq(AfterSaleDO::getStatus, status)); + } + + default AfterSaleDO selectByIdAndUserId(Long id, Long userId) { + return selectOne(AfterSaleDO::getId, id, + AfterSaleDO::getUserId, userId); + } + + default Long selectCountByUserIdAndStatus(Long userId, Collection statuses) { + return selectCount(new LambdaQueryWrapperX() + .eq(AfterSaleDO::getUserId, userId) + .in(AfterSaleDO::getStatus, statuses)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressMapper.java new file mode 100644 index 0000000..2ae173c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressMapper.java @@ -0,0 +1,48 @@ +package com.tashow.cloud.trade.dal.mysql.delivery; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressExportReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressPageReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface DeliveryExpressMapper extends BaseMapperX { + + default PageResult selectPage(DeliveryExpressPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DeliveryExpressDO::getCode, reqVO.getCode()) + .likeIfPresent(DeliveryExpressDO::getName, reqVO.getName()) + .eqIfPresent(DeliveryExpressDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(DeliveryExpressDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(DeliveryExpressDO::getSort)); + } + + default List selectList(DeliveryExpressExportReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(DeliveryExpressDO::getCode, reqVO.getCode()) + .likeIfPresent(DeliveryExpressDO::getName, reqVO.getName()) + .eqIfPresent(DeliveryExpressDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(DeliveryExpressDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(DeliveryExpressDO::getSort)); + } + + default DeliveryExpressDO selectByCode(String code) { + return selectOne(new LambdaQueryWrapper() + .eq(DeliveryExpressDO::getCode, code)); + } + + default List selectListByStatus(Integer status) { + return selectList(DeliveryExpressDO::getStatus, status); + } + +} + + + + diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateChargeMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateChargeMapper.java new file mode 100644 index 0000000..2be755b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateChargeMapper.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.dal.mysql.delivery; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface DeliveryExpressTemplateChargeMapper extends BaseMapperX { + + default List selectListByTemplateId(Long templateId){ + return selectList(new LambdaQueryWrapper() + .eq(DeliveryExpressTemplateChargeDO::getTemplateId, templateId)); + } + + default int deleteByTemplateId(Long templateId){ + return delete(new LambdaQueryWrapper() + .eq(DeliveryExpressTemplateChargeDO::getTemplateId, templateId)); + } + + default List selectByTemplateIds(Collection templateIds) { + return selectList(DeliveryExpressTemplateChargeDO::getTemplateId, templateIds); + } + +} + + + + diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateFreeMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateFreeMapper.java new file mode 100644 index 0000000..8ce3679 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateFreeMapper.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.trade.dal.mysql.delivery; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface DeliveryExpressTemplateFreeMapper extends BaseMapperX { + + default List selectListByTemplateId(Long templateId) { + return selectList(new LambdaQueryWrapper() + .eq(DeliveryExpressTemplateFreeDO::getTemplateId, templateId)); + } + + default int deleteByTemplateId(Long templateId) { + return delete(new LambdaQueryWrapper() + .eq(DeliveryExpressTemplateFreeDO::getTemplateId, templateId)); + } + + default List selectListByTemplateIds(Collection templateIds) { + return selectList(DeliveryExpressTemplateFreeDO::getTemplateId, templateIds); + } +} + + + + diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateMapper.java new file mode 100644 index 0000000..4c50ead --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryExpressTemplateMapper.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.trade.dal.mysql.delivery; + + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplatePageReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface DeliveryExpressTemplateMapper extends BaseMapperX { + + default PageResult selectPage(DeliveryExpressTemplatePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DeliveryExpressTemplateDO::getName, reqVO.getName()) + .eqIfPresent(DeliveryExpressTemplateDO::getChargeMode, reqVO.getChargeMode()) + .betweenIfPresent(DeliveryExpressTemplateDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(DeliveryExpressTemplateDO::getSort)); + } + + default DeliveryExpressTemplateDO selectByName(String name) { + return selectOne(DeliveryExpressTemplateDO::getName,name); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryPickUpStoreMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryPickUpStoreMapper.java new file mode 100644 index 0000000..adfecdf --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/delivery/DeliveryPickUpStoreMapper.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.dal.mysql.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStorePageReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface DeliveryPickUpStoreMapper extends BaseMapperX { + + default PageResult selectPage(DeliveryPickUpStorePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(DeliveryPickUpStoreDO::getName, reqVO.getName()) + .eqIfPresent(DeliveryPickUpStoreDO::getPhone, reqVO.getPhone()) + .eqIfPresent(DeliveryPickUpStoreDO::getAreaId, reqVO.getAreaId()) + .eqIfPresent(DeliveryPickUpStoreDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(DeliveryPickUpStoreDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(DeliveryPickUpStoreDO::getId)); + } + + default List selectListByStatus(Integer status) { + return selectList(DeliveryPickUpStoreDO::getStatus, status); + } + +} + + + + diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderItemMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderItemMapper.java new file mode 100644 index 0000000..6f47448 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderItemMapper.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.trade.dal.mysql.order; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Mapper +public interface TradeOrderItemMapper extends BaseMapperX { + + default int updateAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, + Long afterSaleId) { + return 1;// update(new TradeOrderItemDO().setAfterSaleStatus(newAfterSaleStatus).setAfterSaleId(afterSaleId), + //new LambdaUpdateWrapper<>(new TradeOrderItemDO().setId(id).setAfterSaleStatus(oldAfterSaleStatus))); + } + + default List selectListByOrderId(Long orderId) { + return selectList(TradeOrderItemDO::getOrderId, orderId); + } + + default List selectListByOrderId(Collection orderIds) { + return selectList(TradeOrderItemDO::getOrderId, orderIds); + } + + default TradeOrderItemDO selectByIdAndUserId(Long orderItemId, Long loginUserId) { + return selectOne(new LambdaQueryWrapperX() + .eq(TradeOrderItemDO::getId, orderItemId) + .eq(TradeOrderItemDO::getUserId, loginUserId)); + } + + default List selectListByOrderIdAndCommentStatus(Long orderId, Boolean commentStatus) { + return selectList(new LambdaQueryWrapperX() + .eq(TradeOrderItemDO::getOrderId, orderId) +// .eq(TradeOrderItemDO::getCommentStatus, commentStatus) + ); + } + + default int selectProductSumByOrderId(@Param("orderIds") Set orderIds) { + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("SUM(count) AS sumCount") + .in("order_id", orderIds)); // 只计算选中的 + // 获得数量 + return CollUtil.getFirst(result) != null ? MapUtil.getInt(result.get(0), "sumCount") : 0; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderLogMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderLogMapper.java new file mode 100644 index 0000000..d432fc7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderLogMapper.java @@ -0,0 +1,19 @@ +package com.tashow.cloud.trade.dal.mysql.order; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface TradeOrderLogMapper extends BaseMapperX { + + default List selectListByOrderId(Long orderId) { + return selectList(new LambdaQueryWrapper() + .eq(TradeOrderLogDO::getOrderId, orderId) + .orderByDesc(TradeOrderLogDO::getCreateTime)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderMapper.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderMapper.java new file mode 100644 index 0000000..bbd0364 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/mysql/order/TradeOrderMapper.java @@ -0,0 +1,121 @@ +package com.tashow.cloud.trade.dal.mysql.order; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.mybatis.mybatis.core.mapper.BaseMapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.LambdaQueryWrapperX; +import com.tashow.cloud.mybatis.mybatis.core.query.MPJLambdaWrapperX; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderPageReqVO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderTypeEnum; +import org.apache.ibatis.annotations.Mapper; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Mapper +public interface TradeOrderMapper extends BaseMapperX { + + default int updateByIdAndStatus(Long id, Integer status, TradeOrderDO update) { + return update(update, new LambdaUpdateWrapper() + .eq(TradeOrderDO::getId, id).eq(TradeOrderDO::getOrderStatus, status)); + } + + default TradeOrderDO selectByIdAndUserId(Long id, Long userId) { + return selectOne(TradeOrderDO::getId, id, TradeOrderDO::getUserId, userId); + } + + default PageResult selectPage(TradeOrderPageReqVO reqVO, Set userIds) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(TradeOrderDO::getMerchantName, reqVO.getMerchantName()) + .eqIfPresent(TradeOrderDO::getOrderCategoryId, reqVO.getOrderCategoryId()) + .eqIfPresent(TradeOrderDO::getOrderTerminal, reqVO.getOrderTerminal()) + .eqIfPresent(TradeOrderDO::getFinanceStatus, reqVO.getFinanceStatus()) + .eqIfPresent(TradeOrderDO::getAfterSaleStatus, reqVO.getAfterSaleStatus()) + .eqIfPresent(TradeOrderDO::getSubType, reqVO.getSubType()) + .betweenIfPresent(TradeOrderDO::getSubTime, reqVO.getSubTime()) + .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime()) + .and(StrUtil.isNotBlank(reqVO.getProdSearch()), + qw->qw.like(TradeOrderDO::getOrderNum, reqVO.getProdSearch()) + .or().like(TradeOrderDO::getOrderStatus, reqVO.getProdSearch()) + .or().like(TradeOrderDO::getOrderType, reqVO.getProdSearch())) + .and(StrUtil.isNotBlank(reqVO.getUserSearch()), + qw->qw.eq(TradeOrderDO::getUserMobile, reqVO.getUserSearch()) + .or().like(TradeOrderDO::getUserName,reqVO.getProdSearch())) + .orderByDesc(TradeOrderDO::getId)); + } + + // TODO @疯狂:如果用 map 返回,要不这里直接用 TradeOrderSummaryRespVO 返回?也算合理,就当 sql 查询出这么个玩意~~ + default List> selectOrderSummaryGroupByRefundStatus(TradeOrderPageReqVO reqVO, Set userIds) { + return selectMaps(new MPJLambdaWrapperX() + .selectAs(TradeOrderDO::getRefundStatus, TradeOrderDO::getRefundStatus) // 售后状态 + .selectCount(TradeOrderDO::getId, "count") // 售后状态对应的数量 + .selectSum(TradeOrderDO::getPayPrice, "price") // 售后状态对应的支付金额 + .inIfPresent(TradeOrderDO::getUserId, userIds) + .betweenIfPresent(TradeOrderDO::getCreateTime, reqVO.getCreateTime()) + .groupBy(TradeOrderDO::getRefundStatus)); // 按售后状态分组 + } + + default PageResult selectPage(AppTradeOrderPageReqVO reqVO, Long userId) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eq(TradeOrderDO::getUserId, userId) + .eqIfPresent(TradeOrderDO::getOrderStatus, reqVO.getStatus()) + .eqIfPresent(TradeOrderDO::getCommentStatus, reqVO.getCommentStatus()) + .orderByDesc(TradeOrderDO::getId)); // TODO 芋艿:未来不同的 status,不同的排序 + } + + default Long selectCountByUserIdAndStatus(Long userId, Integer status, Boolean commentStatus) { + return selectCount(new LambdaQueryWrapperX() + .eq(TradeOrderDO::getUserId, userId) + .eqIfPresent(TradeOrderDO::getOrderStatus, status) + .eqIfPresent(TradeOrderDO::getCommentStatus, commentStatus)); + } + + default TradeOrderDO selectOrderByIdAndUserId(Long orderId, Long loginUserId) { + return selectOne(new LambdaQueryWrapperX() + .eq(TradeOrderDO::getId, orderId) + .eq(TradeOrderDO::getUserId, loginUserId)); + } + + default List selectListByStatusAndCreateTimeLt(Integer status, LocalDateTime createTime) { + return selectList(new LambdaUpdateWrapper() + .eq(TradeOrderDO::getOrderStatus, status) + .lt(TradeOrderDO::getCreateTime, createTime)); + } + + default List selectListByStatusAndDeliveryTimeLt(Integer status, LocalDateTime deliveryTime) { + return selectList(new LambdaUpdateWrapper() + .eq(TradeOrderDO::getOrderStatus, status) + .lt(TradeOrderDO::getDeliveryTime, deliveryTime)); + } + + default List selectListByStatusAndReceiveTimeLt(Integer status, LocalDateTime receive, + Boolean commentStatus) { + return selectList(new LambdaUpdateWrapper() + .eq(TradeOrderDO::getOrderStatus, status) + .lt(TradeOrderDO::getReceiveTime, receive) + .eq(TradeOrderDO::getCommentStatus, commentStatus)); + } + + default List selectListByUserIdAndActivityId(Long userId, Long activityId, TradeOrderTypeEnum type) { + LambdaQueryWrapperX queryWrapperX = new LambdaQueryWrapperX<>(); + queryWrapperX.eq(TradeOrderDO::getUserId, userId); + return selectList(queryWrapperX); + } + + default TradeOrderDO selectOneByPickUpVerifyCode(String pickUpVerifyCode) { + return selectOne(TradeOrderDO::getPickUpVerifyCode, pickUpVerifyCode); + } + + default TradeOrderDO selectByUserIdAndCombinationActivityIdAndStatus(Long userId, Long combinationActivityId, Integer status) { + return selectOne(new LambdaQueryWrapperX() + .eq(TradeOrderDO::getUserId, userId) + .eq(TradeOrderDO::getOrderStatus, status) + ); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/redis/RedisKeyConstants.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000..6876753 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/redis/RedisKeyConstants.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.trade.dal.redis; + +/** + * 交易 Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 交易序号的缓存 + * + * KEY 格式:trade_no:{prefix} + * VALUE 数据格式:编号自增 + */ + String TRADE_NO = "trade_no:"; + + /** + * 交易序号的缓存 + * + * KEY 格式:express_track:{code-logisticsNo-receiverMobile} + * VALUE 数据格式 String, 物流信息集合 + */ + String EXPRESS_TRACK = "express_track"; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/redis/no/TradeNoRedisDAO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/redis/no/TradeNoRedisDAO.java new file mode 100644 index 0000000..08f61d7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/dal/redis/no/TradeNoRedisDAO.java @@ -0,0 +1,44 @@ +package com.tashow.cloud.trade.dal.redis.no; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import com.tashow.cloud.trade.dal.redis.RedisKeyConstants; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import jakarta.annotation.Resource; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * 订单序号的 Redis DAO + * + * @author HUIHUI + */ +@Repository +public class TradeNoRedisDAO { + + public static final String TRADE_ORDER_NO_PREFIX = "o"; + + public static final String AFTER_SALE_NO_PREFIX = "r"; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 生成序号 + * + * @param prefix 前缀 + * @return 序号 + */ + public String generate(String prefix) { + // 递增序号 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN); + String key = RedisKeyConstants.TRADE_NO + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + // 设置过期时间 + stringRedisTemplate.expire(key, Duration.ofMinutes(1L)); + return noPrefix + no; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/config/AfterSaleLogConfiguration.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/config/AfterSaleLogConfiguration.java new file mode 100644 index 0000000..12c3961 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/config/AfterSaleLogConfiguration.java @@ -0,0 +1,22 @@ +package com.tashow.cloud.trade.framework.aftersale.config; + +import com.tashow.cloud.trade.framework.aftersale.core.aop.AfterSaleLogAspect; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +// TODO @chenchen:改成 aftersale 好点哈; +/** + * trade 模块的 afterSaleLog 组件的 Configuration + * + * @author 陈賝 + * @since 2023/6/18 11:09 + */ +@Configuration(proxyBeanMethods = false) +public class AfterSaleLogConfiguration { + + @Bean + public AfterSaleLogAspect afterSaleLogAspect() { + return new AfterSaleLogAspect(); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/annotations/AfterSaleLog.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/annotations/AfterSaleLog.java new file mode 100644 index 0000000..96fd894 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/annotations/AfterSaleLog.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.trade.framework.aftersale.core.annotations; + + +import com.tashow.cloud.trade.framework.aftersale.core.aop.AfterSaleLogAspect; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleOperateTypeEnum; + +import java.lang.annotation.*; + +/** + * 售后日志的注解 + * + * 写在方法上时,会自动记录售后日志 + * + * @author 陈賝 + * @since 2023/6/8 17:04 + * @see AfterSaleLogAspect + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface AfterSaleLog { + + /** + * 操作类型 + */ + AfterSaleOperateTypeEnum operateType(); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java new file mode 100644 index 0000000..c44267b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java @@ -0,0 +1,133 @@ +package com.tashow.cloud.trade.framework.aftersale.core.aop; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.framework.aftersale.core.annotations.AfterSaleLog; +import com.tashow.cloud.trade.service.aftersale.AfterSaleLogService; +import com.tashow.cloud.trade.service.aftersale.bo.AfterSaleLogCreateReqBO; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; + +import java.util.Map; + +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; +import static java.util.Collections.emptyMap; + +/** + * 售后订单的操作记录的 AOP 切面 + * + * @author 陈賝 + * @since 2023/6/13 13:54 + */ +@Slf4j +@Aspect +public class AfterSaleLogAspect { + + /** + * 用户编号 + * + * 目前的使用场景:支付回调时,需要强制设置下用户编号 + */ + private static final ThreadLocal USER_ID = new ThreadLocal<>(); + /** + * 用户类型 + */ + private static final ThreadLocal USER_TYPE = new ThreadLocal<>(); + /** + * 订单编号 + */ + private static final ThreadLocal AFTER_SALE_ID = new ThreadLocal<>(); + /** + * 操作前的状态 + */ + private static final ThreadLocal BEFORE_STATUS = new ThreadLocal<>(); + /** + * 操作后的状态 + */ + private static final ThreadLocal AFTER_STATUS = new ThreadLocal<>(); + /** + * 拓展参数 Map,用于格式化操作内容 + */ + private static final ThreadLocal> EXTS = new ThreadLocal<>(); + + @Resource + private AfterSaleLogService afterSaleLogService; + + @AfterReturning(pointcut = "@annotation(afterSaleLog)") + public void doAfterReturning(JoinPoint joinPoint, AfterSaleLog afterSaleLog) { + try { + // 1.1 操作用户 + Integer userType = getUserType(); + Long userId = getUserId(); + // 1.2 售后信息 + Long afterSaleId = AFTER_SALE_ID.get(); + if (afterSaleId == null) { // 如果未设置,只有注解,说明不需要记录日志 + return; + } + Integer beforeStatus = BEFORE_STATUS.get(); + Integer afterStatus = AFTER_STATUS.get(); + Map exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap()); + String content = StrUtil.format(afterSaleLog.operateType().getContent(), exts); + + // 2. 记录日志 + AfterSaleLogCreateReqBO createBO = new AfterSaleLogCreateReqBO() + .setUserId(userId).setUserType(userType) + .setAfterSaleId(afterSaleId).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus) + .setOperateType(afterSaleLog.operateType().getType()).setContent(content); + afterSaleLogService.createAfterSaleLog(createBO); + } catch (Exception exception) { + log.error("[doAfterReturning][afterSaleLog({}) 日志记录错误]", toJsonString(afterSaleLog), exception); + } finally { + clear(); + } + } + + /** + * 获得用户类型 + * + * 如果没有,则约定为 {@link TradeOrderLogDO#getUserType()} 系统 + * + * @return 用户类型 + */ + private static Integer getUserType() { + return ObjectUtil.defaultIfNull(WebFrameworkUtils.getLoginUserType(), TradeOrderLogDO.USER_TYPE_SYSTEM); + } + + /** + * 获得用户编号 + * + * 如果没有,则约定为 {@link TradeOrderLogDO#getUserId()} 系统 + * + * @return 用户类型 + */ + private static Long getUserId() { + return ObjectUtil.defaultIfNull(WebFrameworkUtils.getLoginUserId(), TradeOrderLogDO.USER_ID_SYSTEM); + } + + public static void setAfterSale(Long id, Integer beforeStatus, Integer afterStatus, Map exts) { + AFTER_SALE_ID.set(id); + BEFORE_STATUS.set(beforeStatus); + AFTER_STATUS.set(afterStatus); + EXTS.set(exts); + } + + public static void setUserInfo(Long userId, Integer userType) { + USER_ID.set(userId); + USER_TYPE.set(userType); + } + + private static void clear() { + USER_ID.remove(); + USER_TYPE.remove(); + AFTER_SALE_ID.remove(); + BEFORE_STATUS.remove(); + AFTER_STATUS.remove(); + EXTS.remove(); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java new file mode 100644 index 0000000..4e4d83c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.trade.framework.aftersale.core.utils; + + +import com.tashow.cloud.trade.framework.aftersale.core.aop.AfterSaleLogAspect; + +import java.util.Map; + +/** + * 操作日志工具类 + * 目前主要的作用,是提供给业务代码,记录操作明细和拓展字段 + * + * @author 芋道源码 + */ +public class AfterSaleLogUtils { + + public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus) { + setAfterSaleInfo(id, beforeStatus, afterStatus, null); + } + + public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus, + Map exts) { + AfterSaleLogAspect.setAfterSale(id, beforeStatus, afterStatus, exts); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/config/ExpressClientConfig.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/config/ExpressClientConfig.java new file mode 100644 index 0000000..35fe77b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/config/ExpressClientConfig.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.trade.framework.delivery.config; + +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClientFactory; +import com.tashow.cloud.trade.framework.delivery.core.client.impl.ExpressClientFactoryImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * 快递客户端端配置类: + * + * 1. 快递客户端工厂 {@link ExpressClientFactory} + * 2. 默认的快递客户端实现 {@link ExpressClient} + * + * @author jason + */ +@Configuration(proxyBeanMethods = false) +public class ExpressClientConfig { + + @Bean + public ExpressClientFactory expressClientFactory(TradeExpressProperties tradeExpressProperties, + RestTemplate restTemplate) { + return new ExpressClientFactoryImpl(tradeExpressProperties, restTemplate); + } + + @Bean + public ExpressClient defaultExpressClient(ExpressClientFactory expressClientFactory) { + return expressClientFactory.getDefaultExpressClient(); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/config/TradeExpressProperties.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/config/TradeExpressProperties.java new file mode 100644 index 0000000..f5fa13e --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/config/TradeExpressProperties.java @@ -0,0 +1,88 @@ +package com.tashow.cloud.trade.framework.delivery.config; + +import com.tashow.cloud.trade.framework.delivery.core.enums.ExpressClientEnum; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +// TODO @芋艿:未来要不要放数据库中?考虑 saas 多租户时,不同租户使用不同的配置? +/** + * 交易运费快递的配置项 + * + * @author jason + */ +@Component +@ConfigurationProperties(prefix = "tashow.trade.express") +@Data +@Validated +public class TradeExpressProperties { + + /** + * 快递客户端 + * + * 默认不提供,需要提醒用户配置一个快递服务商。 + */ + private ExpressClientEnum client = ExpressClientEnum.NOT_PROVIDE; + + /** + * 快递鸟配置 + */ + @Valid + private KdNiaoConfig kdNiao; + /** + * 快递 100 配置 + */ + @Valid + private Kd100Config kd100; + + /** + * 快递鸟配置项目 + */ + @Data + public static class KdNiaoConfig { + + /** + * 快递鸟用户 ID + */ + @NotEmpty(message = "快递鸟用户 ID 配置项不能为空") + private String businessId; + /** + * 快递鸟 API Key + */ + @NotEmpty(message = "快递鸟 Api Key 配置项不能为空") + private String apiKey; + + /** + * 接口指令 + * + * 1. 1002:免费版(只能查询申通、圆通快递) + * 2. 8001:付费版 + */ + @NotEmpty(message = "RequestType 配置项不能为空") + private String requestType = "1002"; + + } + + /** + * 快递 100 配置项 + */ + @Data + public static class Kd100Config { + + /** + * 快递 100 授权码 + */ + @NotEmpty(message = "快递 100 授权码配置项不能为空") + private String customer; + /** + * 快递 100 授权 key + */ + @NotEmpty(message = "快递 100 授权 Key 配置项不能为空") + private String key; + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/ExpressClient.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/ExpressClient.java new file mode 100644 index 0000000..daf116a --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/ExpressClient.java @@ -0,0 +1,24 @@ +package com.tashow.cloud.trade.framework.delivery.core.client; + + +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; + +import java.util.List; + +/** + * 快递客户端接口 + * + * @author jason + */ +public interface ExpressClient { + + /** + * 快递实时查询 + * + * @param reqDTO 查询请求参数 + */ + // TODO @jason:返回字段可以参考 https://doc.youzanyun.com/detail/API/0/5 响应的 data + List getExpressTrackList(ExpressTrackQueryReqDTO reqDTO); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/ExpressClientFactory.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/ExpressClientFactory.java new file mode 100644 index 0000000..a0a6196 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/ExpressClientFactory.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.trade.framework.delivery.core.client; + + +import com.tashow.cloud.trade.framework.delivery.core.enums.ExpressClientEnum; + +/** + * 快递客户端工厂接口:用于创建和缓存快递客户端 + * + * @author jason + */ +public interface ExpressClientFactory { + + /** + * 获取默认的快递客户端 + */ + ExpressClient getDefaultExpressClient(); + + /** + * 通过枚举获取快递客户端,如果不存在,就创建一个对应快递客户端 + * + * @param clientEnum 快递客户端枚举 + */ + ExpressClient getOrCreateExpressClient(ExpressClientEnum clientEnum); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/convert/ExpressQueryConvert.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/convert/ExpressQueryConvert.java new file mode 100644 index 0000000..3e65fdd --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/convert/ExpressQueryConvert.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.convert; + +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kd100.Kd100ExpressQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kd100.Kd100ExpressQueryRespDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +@Mapper +public interface ExpressQueryConvert { + + ExpressQueryConvert INSTANCE = Mappers.getMapper(ExpressQueryConvert.class); + + List convertList(List list); + @Mapping(source = "acceptTime", target = "time") + @Mapping(source = "acceptStation", target = "content") + ExpressTrackRespDTO convert(KdNiaoExpressQueryRespDTO.ExpressTrack track); + + List convertList2(List list); + @Mapping(source = "context", target = "content") + ExpressTrackRespDTO convert(Kd100ExpressQueryRespDTO.ExpressTrack track); + + KdNiaoExpressQueryReqDTO convert(ExpressTrackQueryReqDTO dto); + + Kd100ExpressQueryReqDTO convert2(ExpressTrackQueryReqDTO dto); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/ExpressTrackQueryReqDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/ExpressTrackQueryReqDTO.java new file mode 100644 index 0000000..6f3f871 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/ExpressTrackQueryReqDTO.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.dto; + +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import lombok.Data; + +/** + * 快递轨迹的查询 Req DTO + * + * @author jason + */ +@Data +public class ExpressTrackQueryReqDTO { + + /** + * 快递公司编码 + *

+ * 对应 {@link DeliveryExpressDO#getCode()} + */ + private String expressCode; + + /** + * 发货快递单号 + */ + private String logisticsNo; + + /** + * 收、寄件人的电话号码 + */ + private String phone; + + /** + * 自定义名称(顺丰专用) + */ + private String customerName; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/ExpressTrackRespDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/ExpressTrackRespDTO.java new file mode 100644 index 0000000..53f09d7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/ExpressTrackRespDTO.java @@ -0,0 +1,25 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 快递查询的轨迹 Resp DTO + * + * @author jason + */ +@Data +public class ExpressTrackRespDTO { + + /** + * 发生时间 + */ + private LocalDateTime time; + + /** + * 快递状态 + */ + private String content; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryReqDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryReqDTO.java new file mode 100644 index 0000000..5d5592a --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryReqDTO.java @@ -0,0 +1,33 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.dto.kd100; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 快递 100 快递查询 Req DTO + * + * @author jason + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Kd100ExpressQueryReqDTO { + + /** + * 快递公司编码 + */ + @JsonProperty("com") + private String expressCode; + + /** + * 快递单号 + */ + @JsonProperty("num") + private String logisticsNo; + + /** + * 收、寄件人的电话号码 + */ + private String phone; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryRespDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryRespDTO.java new file mode 100644 index 0000000..a101c55 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kd100/Kd100ExpressQueryRespDTO.java @@ -0,0 +1,72 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.dto.kd100; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static com.tashow.cloud.common.util.date.DateUtils.TIME_ZONE_DEFAULT; + + +/** + * 快递 100 实时快递查询 Resp DTO + * + * 参见 快递 100 文档 + * + * @author jason + */ +@Data +public class Kd100ExpressQueryRespDTO { + + /** + * 快递公司编码 + */ + @JsonProperty("com") + private String expressCompanyCode; + /** + * 快递单号 + */ + @JsonProperty("nu") + private String logisticsNo; + /** + * 快递单当前状态 + */ + private String state; + + /** + * 查询结果 + * + * 失败返回 "false" + */ + private String result; + /** + * 查询结果失败时的错误信息 + */ + private String message; + + /** + * 轨迹数组 + */ + @JsonProperty("data") + private List tracks; + + @Data + public static class ExpressTrack { + + /** + * 轨迹发生时间 + */ + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) + private LocalDateTime time; + + /** + * 轨迹描述 + */ + private String context; + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryReqDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryReqDTO.java new file mode 100644 index 0000000..e8ecce3 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryReqDTO.java @@ -0,0 +1,38 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.dto.kdniao; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 快递鸟快递查询 Req DTO + * + * @author jason + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class KdNiaoExpressQueryReqDTO { + + /** + * 快递公司编码 + */ + @JsonProperty("ShipperCode") + private String expressCode; + /** + * 快递单号 + */ + @JsonProperty("LogisticCode") + private String logisticsNo; + /** + * 订单编号 + */ + @JsonProperty("OrderCode") + private String orderNo; + + /** + * 自定义名称(顺丰专用) + */ + @JsonProperty("CustomerName") + private String customerName; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryRespDTO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryRespDTO.java new file mode 100644 index 0000000..b613769 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/dto/kdniao/KdNiaoExpressQueryRespDTO.java @@ -0,0 +1,100 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.dto.kdniao; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +import static com.tashow.cloud.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static com.tashow.cloud.common.util.date.DateUtils.TIME_ZONE_DEFAULT; + + +/** + * 快递鸟快递查询 Resp DTO + * + * 参见 快递鸟接口文档 + * + * @author jason + */ +@Data +public class KdNiaoExpressQueryRespDTO { + + /** + * 快递公司编码 + */ + @JsonProperty("ShipperCode") + private String shipperCode; + + /** + * 快递单号 + */ + @JsonProperty("LogisticCode") + private String logisticsNo; + + /** + * 订单编号 + */ + @JsonProperty("OrderCode") + private String orderNo; + + /** + * 用户 ID + */ + @JsonProperty("EBusinessID") + private String businessId; + + /** + * 普通物流状态 + * + * 0 - 暂无轨迹信息 + * 1 - 已揽收 + * 2 - 在途中 + * 3 - 签收 + * 4 - 问题件 + * 5 - 转寄 + * 6 - 清关 + */ + @JsonProperty("State") + private String state; + + /** + * 成功与否 + */ + @JsonProperty("Success") + private Boolean success; + /** + * 失败原因 + */ + @JsonProperty("Reason") + private String reason; + + /** + * 轨迹数组 + */ + @JsonProperty("Traces") + private List tracks; + + @Data + public static class ExpressTrack { + + /** + * 发生时间 + */ + @JsonProperty("AcceptTime") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + private LocalDateTime acceptTime; + + /** + * 轨迹描述 + */ + @JsonProperty("AcceptStation") + private String acceptStation; + + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/ExpressClientFactoryImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/ExpressClientFactoryImpl.java new file mode 100644 index 0000000..1e4f9f9 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/ExpressClientFactoryImpl.java @@ -0,0 +1,54 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.impl; + +import cn.hutool.core.lang.Assert; +import com.tashow.cloud.trade.framework.delivery.config.TradeExpressProperties; +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClientFactory; +import com.tashow.cloud.trade.framework.delivery.core.client.impl.kd100.Kd100ExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.client.impl.kdniao.KdNiaoExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.enums.ExpressClientEnum; +import lombok.AllArgsConstructor; +import org.springframework.web.client.RestTemplate; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 快递客户端工厂实现类 + * + * @author jason + */ +@AllArgsConstructor +public class ExpressClientFactoryImpl implements ExpressClientFactory { + + private final Map clientMap = new ConcurrentHashMap<>(8); + + private final TradeExpressProperties tradeExpressProperties; + private final RestTemplate restTemplate; + + @Override + public ExpressClient getDefaultExpressClient() { + ExpressClient defaultClient = getOrCreateExpressClient(tradeExpressProperties.getClient()); + Assert.notNull("默认的快递客户端不能为空"); + return defaultClient; + } + + @Override + public ExpressClient getOrCreateExpressClient(ExpressClientEnum clientEnum) { + return clientMap.computeIfAbsent(clientEnum, + client -> createExpressClient(client, tradeExpressProperties)); + } + + private ExpressClient createExpressClient(ExpressClientEnum queryProviderEnum, + TradeExpressProperties tradeExpressProperties) { + switch (queryProviderEnum) { + case NOT_PROVIDE: + return new NoProvideExpressClient(); + case KD_NIAO: + return new KdNiaoExpressClient(restTemplate, tradeExpressProperties.getKdNiao()); + case KD_100: + return new Kd100ExpressClient(restTemplate, tradeExpressProperties.getKd100()); + } + return null; + } +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/NoProvideExpressClient.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/NoProvideExpressClient.java new file mode 100644 index 0000000..b147888 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/NoProvideExpressClient.java @@ -0,0 +1,26 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.impl; + + +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; + +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_CLIENT_NOT_PROVIDE; + + +/** + * 未实现的快递客户端,用来提醒用户需要接入快递服务商, + * + * @author jason + */ +public class NoProvideExpressClient implements ExpressClient { + + @Override + public List getExpressTrackList(ExpressTrackQueryReqDTO reqDTO) { + throw exception(EXPRESS_CLIENT_NOT_PROVIDE); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/kd100/Kd100ExpressClient.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/kd100/Kd100ExpressClient.java new file mode 100644 index 0000000..31e3c5f --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/kd100/Kd100ExpressClient.java @@ -0,0 +1,107 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.impl.kd100; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.crypto.digest.DigestUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.trade.framework.delivery.config.TradeExpressProperties; +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kd100.Kd100ExpressQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kd100.Kd100ExpressQueryRespDTO; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_API_QUERY_ERROR; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_API_QUERY_FAILED; +import static com.tashow.cloud.trade.framework.delivery.core.client.convert.ExpressQueryConvert.INSTANCE; + +/** + * 快递 100 客户端 + * + * @author jason + */ +@Slf4j +@AllArgsConstructor +public class Kd100ExpressClient implements ExpressClient { + + private static final String REAL_TIME_QUERY_URL = "https://poll.kuaidi100.com/poll/query.do"; + + private final RestTemplate restTemplate; + private final TradeExpressProperties.Kd100Config config; + + /** + * 查询快递轨迹 + * + * @see 接口文档 + * + * @param reqDTO 查询请求参数 + * @return 快递轨迹 + */ + @Override + public List getExpressTrackList(ExpressTrackQueryReqDTO reqDTO) { + // 发起请求 + Kd100ExpressQueryReqDTO requestDTO = INSTANCE.convert2(reqDTO) + .setExpressCode(reqDTO.getExpressCode().toLowerCase()); + Kd100ExpressQueryRespDTO respDTO = httpRequest(REAL_TIME_QUERY_URL, requestDTO, + Kd100ExpressQueryRespDTO.class); + + // 处理结果 + if (Objects.equals("false", respDTO.getResult())) { + throw exception(EXPRESS_API_QUERY_FAILED, respDTO.getMessage()); + } + if (CollUtil.isEmpty(respDTO.getTracks())) { + return Collections.emptyList(); + } + return INSTANCE.convertList2(respDTO.getTracks()); + } + + /** + * 快递 100 API 请求 + * + * @param url 请求 url + * @param req 对应请求的请求参数 + * @param respClass 对应请求的响应 class + * @param 每个请求的请求结构 Req DTO + * @param 每个请求的响应结构 Resp DTO + */ + private Resp httpRequest(String url, Req req, Class respClass) { + // 请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + // 请求体 + String param = JsonUtils.toJsonString(req); + String sign = generateReqSign(param, config.getKey(), config.getCustomer()); // 签名 + MultiValueMap requestBody = new LinkedMultiValueMap<>(); + requestBody.add("customer", config.getCustomer()); + requestBody.add("sign", sign); + requestBody.add("param", param); + log.debug("[httpRequest][请求参数({})]", requestBody); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + log.debug("[httpRequest][的响应结果({})]", responseEntity); + // 处理响应 + if (!responseEntity.getStatusCode().is2xxSuccessful()) { + throw exception(EXPRESS_API_QUERY_ERROR); + } + return JsonUtils.parseObject(responseEntity.getBody(), respClass); + } + + private String generateReqSign(String param, String key, String customer) { + String plainText = String.format("%s%s%s", param, key, customer); + return HexUtil.encodeHexStr(DigestUtil.md5(plainText), false); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/kdniao/KdNiaoExpressClient.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/kdniao/KdNiaoExpressClient.java new file mode 100644 index 0000000..c2d9ff6 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/client/impl/kdniao/KdNiaoExpressClient.java @@ -0,0 +1,127 @@ +package com.tashow.cloud.trade.framework.delivery.core.client.impl.kdniao; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.net.URLEncodeUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.trade.framework.delivery.config.TradeExpressProperties; +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClient; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.kdniao.KdNiaoExpressQueryRespDTO; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.Collections; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_API_QUERY_ERROR; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_API_QUERY_FAILED; +import static com.tashow.cloud.trade.framework.delivery.core.client.convert.ExpressQueryConvert.INSTANCE; + +/** + * 快递鸟客户端 + * + * @author jason + */ +@Slf4j +@AllArgsConstructor +public class KdNiaoExpressClient implements ExpressClient { + + private static final String REAL_TIME_QUERY_URL = "https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx"; + + private final RestTemplate restTemplate; + private final TradeExpressProperties.KdNiaoConfig config; + + /** + * 查询快递轨迹【免费版】 + * + * 仅支持 3 家:申通快递、圆通速递、百世快递 + * + * @see 接口文档 + * + * @param reqDTO 查询请求参数 + * @return 快递轨迹 + */ + @Override + public List getExpressTrackList(ExpressTrackQueryReqDTO reqDTO) { + // 发起请求 + KdNiaoExpressQueryReqDTO requestDTO = INSTANCE.convert(reqDTO) + .setExpressCode(reqDTO.getExpressCode().toUpperCase()); + if (ObjUtil.equal(requestDTO.getExpressCode(), "SF") + && StrUtil.isBlank(reqDTO.getCustomerName()) + && StrUtil.length(reqDTO.getPhone()) >= 4) { + requestDTO.setCustomerName(StrUtil.subSufByLength(reqDTO.getPhone(), 4)); + } + KdNiaoExpressQueryRespDTO respDTO = httpRequest(REAL_TIME_QUERY_URL, config.getRequestType(), + requestDTO, KdNiaoExpressQueryRespDTO.class); + + // 处理结果 + if (respDTO == null || !respDTO.getSuccess()) { + throw exception(EXPRESS_API_QUERY_FAILED, respDTO == null ? "" : respDTO.getReason()); + } + if (CollUtil.isEmpty(respDTO.getTracks())) { + return Collections.emptyList(); + } + return INSTANCE.convertList(respDTO.getTracks()); + } + + /** + * 快递鸟 API 请求 + * + * @param url 请求 url + * @param requestType 对应的请求指令 (快递鸟的 RequestType) + * @param req 对应请求的请求参数 + * @param respClass 对应请求的响应 class + * @param 每个请求的请求结构 Req DTO + * @param 每个请求的响应结构 Resp DTO + */ + private Resp httpRequest(String url, String requestType, Req req, Class respClass) { + // 请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + // 请求体 + String reqData = JsonUtils.toJsonString(req); + String dataSign = generateDataSign(reqData, config.getApiKey()); + MultiValueMap requestBody = new LinkedMultiValueMap<>(); + requestBody.add("RequestData", reqData); + requestBody.add("DataType", "2"); + requestBody.add("EBusinessID", config.getBusinessId()); + requestBody.add("DataSign", dataSign); + requestBody.add("RequestType", requestType); + log.debug("[httpRequest][RequestType({}) 的请求参数({})]", requestType, requestBody); + + // 发送请求 + HttpEntity> requestEntity = new HttpEntity<>(requestBody, headers); + ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); + log.debug("[httpRequest][RequestType({}) 的响应结果({})", requestType, responseEntity); + // 处理响应 + if (!responseEntity.getStatusCode().is2xxSuccessful()) { + throw exception(EXPRESS_API_QUERY_ERROR); + } + return JsonUtils.parseObject(responseEntity.getBody(), respClass); + } + + /** + * 快递鸟生成请求签名 + * + * 参见 签名说明 + * + * @param reqData 请求实体 + * @param apiKey api Key + */ + private String generateDataSign(String reqData, String apiKey) { + String plainText = String.format("%s%s", reqData, apiKey); + return URLEncodeUtil.encode(Base64.encode(DigestUtil.md5Hex(plainText))); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/enums/ExpressClientEnum.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/enums/ExpressClientEnum.java new file mode 100644 index 0000000..d505136 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/delivery/core/enums/ExpressClientEnum.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.trade.framework.delivery.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 快递客户端枚举 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum ExpressClientEnum { + + NOT_PROVIDE("not-provide","未提供"), + KD_NIAO("kd-niao", "快递鸟"), + KD_100("kd-100", "快递100"); + + /** + * 快递服务商唯一编码 + */ + private final String code; + /** + * 快递服务商名称 + */ + private final String name; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/config/TradeOrderConfig.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/config/TradeOrderConfig.java new file mode 100644 index 0000000..6559535 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/config/TradeOrderConfig.java @@ -0,0 +1,13 @@ +package com.tashow.cloud.trade.framework.order.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author LeeYan9 + * @since 2022-09-15 + */ +@Configuration +@EnableConfigurationProperties(TradeOrderProperties.class) +public class TradeOrderConfig { +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/config/TradeOrderProperties.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/config/TradeOrderProperties.java new file mode 100644 index 0000000..0d0c33b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/config/TradeOrderProperties.java @@ -0,0 +1,50 @@ +package com.tashow.cloud.trade.framework.order.config; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +import java.time.Duration; + +/** + * 交易订单的配置项 + * + * @author LeeYan9 + * @since 2022-09-15 + */ +@ConfigurationProperties(prefix = "tashow.trade.order") +@Data +@Validated +public class TradeOrderProperties { + + private static final String PAY_APP_KEY_DEFAULT = "mall"; + + /** + * 支付应用标识 + * + * 在 pay 模块的 [支付管理 -> 应用信息] 里添加 + */ + @NotEmpty(message = "Pay 应用标识不能为空") + private String payAppKey = PAY_APP_KEY_DEFAULT; + + /** + * 支付超时时间 + */ + @NotNull(message = "支付超时时间不能为空") + private Duration payExpireTime; + + /** + * 收货超时时间 + */ + @NotNull(message = "收货超时时间不能为空") + private Duration receiveExpireTime; + + /** + * 评论超时时间 + */ + @NotNull(message = "评论超时时间不能为空") + private Duration commentExpireTime; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/annotations/TradeOrderLog.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/annotations/TradeOrderLog.java new file mode 100644 index 0000000..b3398ed --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/annotations/TradeOrderLog.java @@ -0,0 +1,32 @@ +package com.tashow.cloud.trade.framework.order.core.annotations; + + +import com.tashow.cloud.trade.framework.order.core.aop.TradeOrderLogAspect; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderOperateTypeEnum; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; + +/** + * 交易订单的操作日志 AOP 注解 + * + * @author 陈賝 + * @since 2023/7/6 15:37 + * @see TradeOrderLogAspect + */ +@Target({METHOD, ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface TradeOrderLog { + + /** + * 操作类型 + */ + TradeOrderOperateTypeEnum operateType(); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/aop/TradeOrderLogAspect.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/aop/TradeOrderLogAspect.java new file mode 100644 index 0000000..546bff5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/aop/TradeOrderLogAspect.java @@ -0,0 +1,136 @@ +package com.tashow.cloud.trade.framework.order.core.aop; + + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.framework.order.core.annotations.TradeOrderLog; +import com.tashow.cloud.trade.service.order.TradeOrderLogService; +import com.tashow.cloud.trade.service.order.bo.TradeOrderLogCreateReqBO; +import com.tashow.cloud.web.web.core.util.WebFrameworkUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; +import static java.util.Collections.emptyMap; + +/** + * 交易订单的操作日志的记录 AOP 切面 + * + * @author 陈賝 + * @since 2023/6/13 13:54 + */ +@Component +@Aspect +@Slf4j +public class TradeOrderLogAspect { + + /** + * 用户编号 + * + * 目前的使用场景:支付回调时,需要强制设置下用户编号 + */ + private static final ThreadLocal USER_ID = new ThreadLocal<>(); + /** + * 用户类型 + */ + private static final ThreadLocal USER_TYPE = new ThreadLocal<>(); + /** + * 订单编号 + */ + private static final ThreadLocal ORDER_ID = new ThreadLocal<>(); + /** + * 操作前的状态 + */ + private static final ThreadLocal BEFORE_STATUS = new ThreadLocal<>(); + /** + * 操作后的状态 + */ + private static final ThreadLocal AFTER_STATUS = new ThreadLocal<>(); + /** + * 拓展参数 Map,用于格式化操作内容 + */ + private static final ThreadLocal> EXTS = new ThreadLocal<>(); + + @Resource + private TradeOrderLogService orderLogService; + + @AfterReturning("@annotation(orderLog)") + public void doAfterReturning(JoinPoint joinPoint, TradeOrderLog orderLog) { + try { + // 1.1 操作用户 + Integer userType = getUserType(); + Long userId = getUserId(); + // 1.2 订单信息 + Long orderId = ORDER_ID.get(); + if (orderId == null) { // 如果未设置,只有注解,说明不需要记录日志 + return; + } + Integer beforeStatus = BEFORE_STATUS.get(); + Integer afterStatus = AFTER_STATUS.get(); + Map exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap()); + String content = StrUtil.format(orderLog.operateType().getContent(), exts); + + // 2. 记录日志 + TradeOrderLogCreateReqBO createBO = new TradeOrderLogCreateReqBO() + .setUserId(userId).setUserType(userType) + .setOrderId(orderId).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus) + .setOperateType(orderLog.operateType().getType()).setContent(content); + orderLogService.createOrderLog(createBO); + } catch (Exception ex) { + log.error("[doAfterReturning][orderLog({}) 订单日志错误]", toJsonString(orderLog), ex); + } finally { + clear(); + } + } + + /** + * 获得用户类型 + * + * 如果没有,则约定为 {@link TradeOrderLogDO#getUserType()} 系统 + * + * @return 用户类型 + */ + private static Integer getUserType() { + return ObjectUtil.defaultIfNull(WebFrameworkUtils.getLoginUserType(), TradeOrderLogDO.USER_TYPE_SYSTEM); + } + + /** + * 获得用户编号 + * + * 如果没有,则约定为 {@link TradeOrderLogDO#getUserId()} 系统 + * + * @return 用户类型 + */ + private static Long getUserId() { + return ObjectUtil.defaultIfNull(WebFrameworkUtils.getLoginUserId(), TradeOrderLogDO.USER_ID_SYSTEM); + } + + public static void setOrderInfo(Long id, Integer beforeStatus, Integer afterStatus, Map exts) { + ORDER_ID.set(id); + BEFORE_STATUS.set(beforeStatus); + AFTER_STATUS.set(afterStatus); + EXTS.set(exts); + } + + public static void setUserInfo(Long userId, Integer userType) { + USER_ID.set(userId); + USER_TYPE.set(userType); + } + + private static void clear() { + USER_ID.remove(); + USER_TYPE.remove(); + ORDER_ID.remove(); + BEFORE_STATUS.remove(); + AFTER_STATUS.remove(); + EXTS.remove(); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/utils/TradeOrderLogUtils.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/utils/TradeOrderLogUtils.java new file mode 100644 index 0000000..f07b689 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/order/core/utils/TradeOrderLogUtils.java @@ -0,0 +1,28 @@ +package com.tashow.cloud.trade.framework.order.core.utils; + + +import com.tashow.cloud.trade.framework.order.core.aop.TradeOrderLogAspect; + +import java.util.Map; + +/** + * 交易订单的操作日志 Utils + * + * @author 芋道源码 + */ +public class TradeOrderLogUtils { + + public static void setOrderInfo(Long id, Integer beforeStatus, Integer afterStatus) { + TradeOrderLogAspect.setOrderInfo(id, beforeStatus, afterStatus, null); + } + + public static void setOrderInfo(Long id, Integer beforeStatus, Integer afterStatus, + Map exts) { + TradeOrderLogAspect.setOrderInfo(id, beforeStatus, afterStatus, exts); + } + + public static void setUserInfo(Long userId, Integer userType) { + TradeOrderLogAspect.setUserInfo(userId, userType); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/package-info.java new file mode 100644 index 0000000..c1fb394 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/package-info.java @@ -0,0 +1,6 @@ +/** + * 属于 trade 模块的 framework 封装 + * + * @author 芋道源码 + */ +package com.tashow.cloud.trade.framework; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/rpc/config/RpcConfiguration.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/rpc/config/RpcConfiguration.java new file mode 100644 index 0000000..a42b4b9 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/rpc/config/RpcConfiguration.java @@ -0,0 +1,23 @@ +package com.tashow.cloud.trade.framework.rpc.config; + +import com.tashow.cloud.memberapi.api.address.MemberAddressApi; +import com.tashow.cloud.memberapi.api.user.MemberUserApi; +import com.tashow.cloud.payapi.api.order.PayOrderApi; +import com.tashow.cloud.payapi.api.refund.PayRefundApi; +import com.tashow.cloud.payapi.api.transfer.PayTransferApi; +import com.tashow.cloud.payapi.api.wallet.PayWalletApi; +import com.tashow.cloud.systemapi.api.notify.NotifyMessageSendApi; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.SocialUserApi; +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = { + MemberUserApi.class, MemberAddressApi.class, + PayOrderApi.class, PayRefundApi.class, PayTransferApi.class, PayWalletApi.class, + AdminUserApi.class, NotifyMessageSendApi.class, SocialClientApi.class, SocialUserApi.class +}) +public class RpcConfiguration { +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/rpc/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/rpc/package-info.java new file mode 100644 index 0000000..a2ded57 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/rpc/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.trade.framework.rpc; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/security/config/SecurityConfiguration.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/security/config/SecurityConfiguration.java new file mode 100644 index 0000000..eef7d02 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/security/config/SecurityConfiguration.java @@ -0,0 +1,39 @@ +package com.tashow.cloud.trade.framework.security.config; + +import com.tashow.cloud.security.security.config.AuthorizeRequestsCustomizer; +import com.tashow.cloud.tradeapi.enums.ApiConstants; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; + +/** + * Trade 模块的 Security 配置 + */ +@Configuration("tradeSecurityConfiguration") +public class SecurityConfiguration { + + @Bean("tradeAuthorizeRequestsCustomizer") + public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { + return new AuthorizeRequestsCustomizer() { + + @Override + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + // Swagger 接口文档 + registry.requestMatchers("/v3/api-docs/**").permitAll() + .requestMatchers("/webjars/**").permitAll() + .requestMatchers("/swagger-ui").permitAll() + .requestMatchers("/swagger-ui/**").permitAll(); + // Spring Boot Actuator 的安全配置 + registry.requestMatchers("/actuator").anonymous() + .requestMatchers("/actuator/**").anonymous(); + // Druid 监控 + registry.requestMatchers("/druid/**").anonymous(); + // RPC 服务的安全配置 + registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + } + + }; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/security/core/package-info.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/security/core/package-info.java new file mode 100644 index 0000000..5aa553c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/framework/security/core/package-info.java @@ -0,0 +1,4 @@ +/** + * 占位 + */ +package com.tashow.cloud.trade.framework.security.core; diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleLogService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleLogService.java new file mode 100644 index 0000000..2e957e7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleLogService.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.trade.service.aftersale; + + +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import com.tashow.cloud.trade.service.aftersale.bo.AfterSaleLogCreateReqBO; + +import java.util.List; + +/** + * 交易售后日志 Service 接口 + * + * @author 陈賝 + * @since 2023/6/12 14:18 + */ +public interface AfterSaleLogService { + + /** + * 创建售后日志 + * + * @param createReqBO 日志记录 + * @author 陈賝 + * @since 2023/6/12 14:18 + */ + void createAfterSaleLog(AfterSaleLogCreateReqBO createReqBO); + + /** + * 获取售后日志 + * + * @param afterSaleId 售后编号 + * @return 售后日志 + */ + List getAfterSaleLogList(Long afterSaleId); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleLogServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleLogServiceImpl.java new file mode 100644 index 0000000..48e0e63 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleLogServiceImpl.java @@ -0,0 +1,36 @@ +package com.tashow.cloud.trade.service.aftersale; + +import com.tashow.cloud.trade.convert.aftersale.AfterSaleLogConvert; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleLogDO; +import com.tashow.cloud.trade.dal.mysql.aftersale.AfterSaleLogMapper; +import com.tashow.cloud.trade.service.aftersale.bo.AfterSaleLogCreateReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +/** + * 交易售后日志 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class AfterSaleLogServiceImpl implements AfterSaleLogService { + + @Resource + private AfterSaleLogMapper afterSaleLogMapper; + + @Override + public void createAfterSaleLog(AfterSaleLogCreateReqBO createReqBO) { + AfterSaleLogDO afterSaleLog = AfterSaleLogConvert.INSTANCE.convert(createReqBO); + afterSaleLogMapper.insert(afterSaleLog); + } + + @Override + public List getAfterSaleLogList(Long afterSaleId) { + return afterSaleLogMapper.selectListByAfterSaleId(afterSaleId); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleService.java new file mode 100644 index 0000000..3fb2e77 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleService.java @@ -0,0 +1,128 @@ +package com.tashow.cloud.trade.service.aftersale; + + +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleDO; + +/** + * 售后订单 Service 接口 + * + * @author 芋道源码 + */ +public interface AfterSaleService { + + /** + * 【管理员】获得售后订单分页 + * + * @param pageReqVO 分页查询 + * @return 售后订单分页 + */ + PageResult getAfterSalePage(AfterSalePageReqVO pageReqVO); + + /** + * 【会员】获得售后订单分页 + * + * @param userId 用户编号 + * @param pageParam 分页参数 + * @return 售后订单分页 + */ + PageResult getAfterSalePage(Long userId, PageParam pageParam); + + /** + * 【会员】获得售后单 + * + * @param userId 用户编号 + * @param id 售后编号 + * @return 售后订单 + */ + AfterSaleDO getAfterSale(Long userId, Long id); + + /** + * 【管理员】获得售后单 + * + * @param id 售后编号 + * @return 售后订单 + */ + AfterSaleDO getAfterSale(Long id); + + /** + * 【会员】创建售后订单 + * + * @param userId 会员用户编号 + * @param createReqVO 创建 Request 信息 + * @return 售后编号 + */ + Long createAfterSale(Long userId, AppAfterSaleCreateReqVO createReqVO); + + /** + * 【管理员】同意售后订单 + * + * @param userId 管理员用户编号 + * @param id 售后编号 + */ + void agreeAfterSale(Long userId, Long id); + + /** + * 【管理员】拒绝售后订单 + * + * @param userId 管理员用户编号 + * @param auditReqVO 审批 Request 信息 + */ + void disagreeAfterSale(Long userId, AfterSaleDisagreeReqVO auditReqVO); + + /** + * 【会员】退回货物 + * + * @param userId 会员用户编号 + * @param deliveryReqVO 退货 Request 信息 + */ + void deliveryAfterSale(Long userId, AppAfterSaleDeliveryReqVO deliveryReqVO); + + /** + * 【管理员】确认收货 + * + * @param userId 管理员编号 + * @param id 售后编号 + */ + void receiveAfterSale(Long userId, Long id); + + /** + * 【管理员】拒绝收货 + * + * @param userId 管理员用户编号 + * @param refuseReqVO 拒绝收货 Request 信息 + */ + void refuseAfterSale(Long userId, AfterSaleRefuseReqVO refuseReqVO); + + /** + * 【管理员】确认退款 + * + * @param userId 管理员用户编号 + * @param userIp 管理员用户 IP + * @param id 售后编号 + */ + void refundAfterSale(Long userId, String userIp, Long id); + + /** + * 【会员】取消售后 + * + * @param userId 会员用户编号 + * @param id 售后编号 + */ + void cancelAfterSale(Long userId, Long id); + + /** + * 【会员】获得正在进行中的售后订单数量 + * + * @param userId 用户编号 + * @return 数量 + */ + Long getApplyingAfterSaleCount(Long userId); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleServiceImpl.java new file mode 100644 index 0000000..a18efa6 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/AfterSaleServiceImpl.java @@ -0,0 +1,418 @@ +package com.tashow.cloud.trade.service.aftersale; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.tashow.cloud.common.pojo.PageParam; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.ObjectUtils; +import com.tashow.cloud.payapi.api.refund.PayRefundApi; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; +import com.tashow.cloud.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; +import com.tashow.cloud.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO; +import com.tashow.cloud.trade.convert.aftersale.AfterSaleConvert; +import com.tashow.cloud.trade.dal.dataobject.aftersale.AfterSaleDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.dal.mysql.aftersale.AfterSaleMapper; +import com.tashow.cloud.trade.dal.redis.no.TradeNoRedisDAO; +import com.tashow.cloud.trade.framework.aftersale.core.annotations.AfterSaleLog; +import com.tashow.cloud.trade.framework.aftersale.core.utils.AfterSaleLogUtils; +import com.tashow.cloud.trade.framework.order.config.TradeOrderProperties; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressService; +import com.tashow.cloud.trade.service.order.TradeOrderQueryService; +import com.tashow.cloud.trade.service.order.TradeOrderUpdateService; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleOperateTypeEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleStatusEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleTypeEnum; +import com.tashow.cloud.tradeapi.enums.aftersale.AfterSaleWayEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderItemAfterSaleStatusEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.*; + + +/** + * 售后订单 Service 实现类 + * + * @author 芋道源码 + */ +@Slf4j +@Service +@Validated +public class AfterSaleServiceImpl implements AfterSaleService { + + @Resource + @Lazy // 延迟加载,避免循环依赖 + private TradeOrderUpdateService tradeOrderUpdateService; + @Resource + private TradeOrderQueryService tradeOrderQueryService; + @Resource + private DeliveryExpressService deliveryExpressService; + + @Resource + private AfterSaleMapper tradeAfterSaleMapper; + @Resource + private TradeNoRedisDAO tradeNoRedisDAO; + + @Resource + private TradeOrderProperties tradeOrderProperties; + + @Resource + private PayRefundApi payRefundApi; + + @Override + public PageResult getAfterSalePage(AfterSalePageReqVO pageReqVO) { + return tradeAfterSaleMapper.selectPage(pageReqVO); + } + + @Override + public PageResult getAfterSalePage(Long userId, PageParam pageParam) { + return tradeAfterSaleMapper.selectPage(userId, pageParam); + } + + @Override + public AfterSaleDO getAfterSale(Long userId, Long id) { + return tradeAfterSaleMapper.selectByIdAndUserId(id, userId); + } + + @Override + public AfterSaleDO getAfterSale(Long id) { + return tradeAfterSaleMapper.selectById(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.MEMBER_CREATE) + public Long createAfterSale(Long userId, AppAfterSaleCreateReqVO createReqVO) { + // 第一步,前置校验 + TradeOrderItemDO tradeOrderItem = validateOrderItemApplicable(userId, createReqVO); + + // 第二步,存储售后订单 + AfterSaleDO afterSale = createAfterSale(createReqVO, tradeOrderItem); + return afterSale.getId(); + } + + /** + * 校验交易订单项是否可以申请售后 + * + * @param userId 用户编号 + * @param createReqVO 售后创建信息 + * @return 交易订单项 + */ + private TradeOrderItemDO validateOrderItemApplicable(Long userId, AppAfterSaleCreateReqVO createReqVO) { + // 校验订单项存在 + TradeOrderItemDO orderItem = tradeOrderQueryService.getOrderItem(userId, createReqVO.getOrderItemId()); + if (orderItem == null) { + throw exception(ORDER_ITEM_NOT_FOUND); + } + // 已申请售后,不允许再发起售后申请 + if (!TradeOrderItemAfterSaleStatusEnum.isNone(1)) { + throw exception(AFTER_SALE_CREATE_FAIL_ORDER_ITEM_APPLIED); + } + // 申请的退款金额,不能超过商品的价格 + if (createReqVO.getRefundPrice() > orderItem.getPayPrice()) { + throw exception(AFTER_SALE_CREATE_FAIL_REFUND_PRICE_ERROR); + } + + // 校验订单存在 + TradeOrderDO order = tradeOrderQueryService.getOrder(userId, orderItem.getOrderId()); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // TODO 芋艿:超过一定时间,不允许售后 + // 已取消,无法发起售后 + if (TradeOrderStatusEnum.isCanceled(order.getOrderStatus())) { + throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_CANCELED); + } + // 未支付,无法发起售后 + if (!TradeOrderStatusEnum.havePaid(order.getOrderStatus())) { + throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_PAID); + } + // 如果是【退货退款】的情况,需要额外校验是否发货 + if (createReqVO.getWay().equals(AfterSaleWayEnum.RETURN_AND_REFUND.getWay()) + && !TradeOrderStatusEnum.haveDelivered(order.getOrderStatus())) { + throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED); + } + return orderItem; + } + + private AfterSaleDO createAfterSale(AppAfterSaleCreateReqVO createReqVO, + TradeOrderItemDO orderItem) { + // 创建售后单 + AfterSaleDO afterSale = AfterSaleConvert.INSTANCE.convert(createReqVO, orderItem); + afterSale.setNo(tradeNoRedisDAO.generate(TradeNoRedisDAO.AFTER_SALE_NO_PREFIX)); + afterSale.setStatus(AfterSaleStatusEnum.APPLY.getStatus()); + // 标记是售中还是售后 + TradeOrderDO order = tradeOrderQueryService.getOrder(orderItem.getUserId(), orderItem.getOrderId()); + afterSale.setOrderNo(order.getOrderNum()); // 记录 orderNo 订单流水,方便后续检索 + afterSale.setType(TradeOrderStatusEnum.isCompleted(order.getOrderStatus()) + ? AfterSaleTypeEnum.AFTER_SALE.getType() : AfterSaleTypeEnum.IN_SALE.getType()); + tradeAfterSaleMapper.insert(afterSale); + + // 更新交易订单项的售后状态 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCreate(orderItem.getId(), afterSale.getId()); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), null, + AfterSaleStatusEnum.APPLY.getStatus()); + + // TODO 发送售后消息 + return afterSale; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_AGREE_APPLY) + public void agreeAfterSale(Long userId, Long id) { + // 校验售后单存在,并状态未审批 + AfterSaleDO afterSale = validateAfterSaleAuditable(id); + + // 更新售后单的状态 + // 情况一:退款:标记为 WAIT_REFUND 状态。后续等退款发起成功后,在标记为 COMPLETE 状态 + // 情况二:退货退款:需要等用户退货后,才能发起退款 + Integer newStatus = afterSale.getWay().equals(AfterSaleWayEnum.REFUND.getWay()) ? + AfterSaleStatusEnum.WAIT_REFUND.getStatus() : AfterSaleStatusEnum.SELLER_AGREE.getStatus(); + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.APPLY.getStatus(), new AfterSaleDO() + .setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus); + + // TODO 发送售后消息 + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_DISAGREE_APPLY) + public void disagreeAfterSale(Long userId, AfterSaleDisagreeReqVO auditReqVO) { + // 校验售后单存在,并状态未审批 + AfterSaleDO afterSale = validateAfterSaleAuditable(auditReqVO.getId()); + + // 更新售后单的状态 + Integer newStatus = AfterSaleStatusEnum.SELLER_DISAGREE.getStatus(); + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.APPLY.getStatus(), new AfterSaleDO() + .setStatus(newStatus).setAuditUserId(userId).setAuditTime(LocalDateTime.now()) + .setAuditReason(auditReqVO.getAuditReason())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus); + + // TODO 发送售后消息 + + // 更新交易订单项的售后状态为【未申请】 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId()); + } + + /** + * 校验售后单是否可审批(同意售后、拒绝售后) + * + * @param id 售后编号 + * @return 售后单 + */ + private AfterSaleDO validateAfterSaleAuditable(Long id) { + AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id); + if (afterSale == null) { + throw exception(AFTER_SALE_NOT_FOUND); + } + if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.APPLY.getStatus())) { + throw exception(AFTER_SALE_AUDIT_FAIL_STATUS_NOT_APPLY); + } + return afterSale; + } + + private void updateAfterSaleStatus(Long id, Integer status, AfterSaleDO updateObj) { + int updateCount = tradeAfterSaleMapper.updateByIdAndStatus(id, status, updateObj); + if (updateCount == 0) { + throw exception(AFTER_SALE_UPDATE_STATUS_FAIL); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.MEMBER_DELIVERY) + public void deliveryAfterSale(Long userId, AppAfterSaleDeliveryReqVO deliveryReqVO) { + // 校验售后单存在,并状态未退货 + AfterSaleDO afterSale = tradeAfterSaleMapper.selectByIdAndUserId(deliveryReqVO.getId(), userId); + if (afterSale == null) { + throw exception(AFTER_SALE_NOT_FOUND); + } + if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.SELLER_AGREE.getStatus())) { + throw exception(AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE); + } + DeliveryExpressDO express = deliveryExpressService.validateDeliveryExpress(deliveryReqVO.getLogisticsId()); + + // 更新售后单的物流信息 + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.SELLER_AGREE.getStatus(), new AfterSaleDO() + .setStatus(AfterSaleStatusEnum.BUYER_DELIVERY.getStatus()) + .setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo()) + .setDeliveryTime(LocalDateTime.now())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), + AfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), + MapUtil.builder().put("deliveryName", express.getName()) + .put("logisticsNo", deliveryReqVO.getLogisticsNo()).build()); + + // TODO 发送售后消息 + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_AGREE_RECEIVE) + public void receiveAfterSale(Long userId, Long id) { + // 校验售后单存在,并状态为已退货 + AfterSaleDO afterSale = validateAfterSaleReceivable(id); + + // 更新售后单的状态 + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new AfterSaleDO() + .setStatus(AfterSaleStatusEnum.WAIT_REFUND.getStatus()).setReceiveTime(LocalDateTime.now())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), + AfterSaleStatusEnum.WAIT_REFUND.getStatus()); + + // TODO 发送售后消息 + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_DISAGREE_RECEIVE) + public void refuseAfterSale(Long userId, AfterSaleRefuseReqVO refuseReqVO) { + // 校验售后单存在,并状态为已退货 + AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(refuseReqVO.getId()); + if (afterSale == null) { + throw exception(AFTER_SALE_NOT_FOUND); + } + if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) { + throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY); + } + + // 更新售后单的状态 + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.BUYER_DELIVERY.getStatus(), new AfterSaleDO() + .setStatus(AfterSaleStatusEnum.SELLER_REFUSE.getStatus()).setReceiveTime(LocalDateTime.now()) + .setReceiveReason(refuseReqVO.getRefuseMemo())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), + AfterSaleStatusEnum.SELLER_REFUSE.getStatus(), + MapUtil.of("reason", refuseReqVO.getRefuseMemo())); + + // TODO 发送售后消息 + + // 更新交易订单项的售后状态为【未申请】 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId()); + } + + /** + * 校验售后单是否可收货,即处于买家已发货 + * + * @param id 售后编号 + * @return 售后单 + */ + private AfterSaleDO validateAfterSaleReceivable(Long id) { + AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id); + if (afterSale == null) { + throw exception(AFTER_SALE_NOT_FOUND); + } + if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) { + throw exception(AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY); + } + return afterSale; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_REFUND) + public void refundAfterSale(Long userId, String userIp, Long id) { + // 校验售后单的状态,并状态待退款 + AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id); + if (afterSale == null) { + throw exception(AFTER_SALE_NOT_FOUND); + } + if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.WAIT_REFUND.getStatus())) { + throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND); + } + + // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起 + createPayRefund(userIp, afterSale); + + // 更新售后单的状态为【已完成】 + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO() + .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), + AfterSaleStatusEnum.COMPLETE.getStatus()); + + // TODO 发送售后消息 + + // 更新交易订单项的售后状态为【已完成】 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice()); + } + + private void createPayRefund(String userIp, AfterSaleDO afterSale) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + + @Override + public void afterCommit() { + // 创建退款单 + PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties) + .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));; + Long payRefundId = payRefundApi.createRefund(createReqDTO).getCheckedData(); + // 更新售后单的退款单号 + tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId)); + } + }); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.MEMBER_CANCEL) + public void cancelAfterSale(Long userId, Long id) { + // 校验售后单的状态,并状态待退款 + AfterSaleDO afterSale = tradeAfterSaleMapper.selectByIdAndUserId(id, userId); + if (afterSale == null) { + throw exception(AFTER_SALE_NOT_FOUND); + } + if (!ObjectUtils.equalsAny(afterSale.getStatus(), AfterSaleStatusEnum.APPLY.getStatus(), + AfterSaleStatusEnum.SELLER_AGREE.getStatus(), + AfterSaleStatusEnum.BUYER_DELIVERY.getStatus())) { + throw exception(AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY); + } + + // 更新售后单的状态为【已取消】 + updateAfterSaleStatus(afterSale.getId(), afterSale.getStatus(), new AfterSaleDO() + .setStatus(AfterSaleStatusEnum.BUYER_CANCEL.getStatus())); + + // 记录售后日志 + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), + AfterSaleStatusEnum.BUYER_CANCEL.getStatus()); + + // TODO 发送售后消息 + + // 更新交易订单项的售后状态为【未申请】 + tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId()); + } + + @Override + public Long getApplyingAfterSaleCount(Long userId) { + return tradeAfterSaleMapper.selectCountByUserIdAndStatus(userId, AfterSaleStatusEnum.APPLYING_STATUSES); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/bo/AfterSaleLogCreateReqBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/bo/AfterSaleLogCreateReqBO.java new file mode 100644 index 0000000..de91efa --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/aftersale/bo/AfterSaleLogCreateReqBO.java @@ -0,0 +1,56 @@ +package com.tashow.cloud.trade.service.aftersale.bo; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 售后日志的创建 Request BO + * + * @author 陈賝 + * @since 2023/6/19 09:54 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AfterSaleLogCreateReqBO { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 售后编号 + */ + @NotNull(message = "售后编号不能为空") + private Long afterSaleId; + /** + * 操作前状态 + */ + private Integer beforeStatus; + /** + * 操作后状态 + */ + @NotNull(message = "操作后的状态不能为空") + private Integer afterStatus; + + /** + * 操作类型 + */ + @NotNull(message = "操作类型不能为空") + private Integer operateType; + /** + * 操作明细 + */ + @NotEmpty(message = "操作明细不能为空") + private String content; +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/BrokerageAddReqBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/BrokerageAddReqBO.java new file mode 100644 index 0000000..d37bb0c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/BrokerageAddReqBO.java @@ -0,0 +1,52 @@ +package com.tashow.cloud.trade.service.brokerage.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 佣金 增加 Request BO + * + * @author owen + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BrokerageAddReqBO { + + /** + * 业务编号 + */ + @NotBlank(message = "业务编号不能为空") + private String bizId; + /** + * 佣金基数 + */ + @NotNull(message = "佣金基数不能为空") + private Integer basePrice; + /** + * 一级佣金(固定) + */ + @NotNull(message = "一级佣金(固定)不能为空") + private Integer firstFixedPrice; + /** + * 二级佣金(固定) + */ + private Integer secondFixedPrice; + + /** + * 来源用户编号 + */ + @NotNull(message = "来源用户编号不能为空") + private Long sourceUserId; + + /** + * 佣金记录标题 + */ + @NotEmpty(message = "佣金记录标题不能为空") + private String title; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/BrokerageWithdrawSummaryRespBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/BrokerageWithdrawSummaryRespBO.java new file mode 100644 index 0000000..2356a06 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/BrokerageWithdrawSummaryRespBO.java @@ -0,0 +1,31 @@ +package com.tashow.cloud.trade.service.brokerage.bo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 佣金提现合计 BO + * + * @author owen + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class BrokerageWithdrawSummaryRespBO { + + /** + * 用户编号 + */ + private Long userId; + + /** + * 提现次数 + */ + private Integer count; + /** + * 提现金额 + */ + private Integer price; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/UserBrokerageSummaryRespBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/UserBrokerageSummaryRespBO.java new file mode 100644 index 0000000..b313b92 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/brokerage/bo/UserBrokerageSummaryRespBO.java @@ -0,0 +1,30 @@ +package com.tashow.cloud.trade.service.brokerage.bo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 用户佣金合计 BO + * + * @author owen + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserBrokerageSummaryRespBO { + + /** + * 用户编号 + */ + private Long userId; + /** + * 推广数量 + */ + private Integer count; + /** + * 佣金总额 + */ + private Integer price; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressService.java new file mode 100644 index 0000000..be34045 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressService.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.trade.service.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressExportReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressPageReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressUpdateReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 快递公司 Service 接口 + * + * @author jason + */ +public interface DeliveryExpressService { + + /** + * 创建快递公司 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDeliveryExpress(@Valid DeliveryExpressCreateReqVO createReqVO); + + /** + * 更新快递公司 + * + * @param updateReqVO 更新信息 + */ + void updateDeliveryExpress(@Valid DeliveryExpressUpdateReqVO updateReqVO); + + /** + * 删除快递公司 + * + * @param id 编号 + */ + void deleteDeliveryExpress(Long id); + + /** + * 获得快递公司 + * + * @param id 编号 + * @return 快递公司 + */ + DeliveryExpressDO getDeliveryExpress(Long id); + + /** + * 校验快递公司是否合法 + * + * @param id 编号 + * @return 快递公司 + */ + DeliveryExpressDO validateDeliveryExpress(Long id); + + /** + * 获得快递公司分页 + * + * @param pageReqVO 分页查询 + * @return 快递公司分页 + */ + PageResult getDeliveryExpressPage(DeliveryExpressPageReqVO pageReqVO); + + /** + * 获得快递公司列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 快递公司列表 + */ + List getDeliveryExpressList(DeliveryExpressExportReqVO exportReqVO); + + /** + * 获取指定状态的快递公司列表 + * + * @param status 状态 + * @return 快递公司列表 + */ + List getDeliveryExpressListByStatus(Integer status); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressServiceImpl.java new file mode 100644 index 0000000..c0f1ea3 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressServiceImpl.java @@ -0,0 +1,115 @@ +package com.tashow.cloud.trade.service.delivery; + +import com.tashow.cloud.common.enums.CommonStatusEnum; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressCreateReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressExportReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressPageReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.express.DeliveryExpressUpdateReqVO; +import com.tashow.cloud.trade.convert.delivery.DeliveryExpressConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.mysql.delivery.DeliveryExpressMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.*; + + +/** + * 快递公司 Service 实现类 + * + * @author jason + */ +@Service +@Validated +public class DeliveryExpressServiceImpl implements DeliveryExpressService { + + @Resource + private DeliveryExpressMapper deliveryExpressMapper; + + @Override + public Long createDeliveryExpress(DeliveryExpressCreateReqVO createReqVO) { + //校验编码是否唯一 + validateExpressCodeUnique(createReqVO.getCode(), null); + // 插入 + DeliveryExpressDO deliveryExpress = DeliveryExpressConvert.INSTANCE.convert(createReqVO); + deliveryExpressMapper.insert(deliveryExpress); + // 返回 + return deliveryExpress.getId(); + } + + @Override + public void updateDeliveryExpress(DeliveryExpressUpdateReqVO updateReqVO) { + // 校验存在 + validateDeliveryExpressExists(updateReqVO.getId()); + //校验编码是否唯一 + validateExpressCodeUnique(updateReqVO.getCode(), updateReqVO.getId()); + // 更新 + DeliveryExpressDO updateObj = DeliveryExpressConvert.INSTANCE.convert(updateReqVO); + deliveryExpressMapper.updateById(updateObj); + } + + @Override + public void deleteDeliveryExpress(Long id) { + // 校验存在 + validateDeliveryExpressExists(id); + // 删除 + deliveryExpressMapper.deleteById(id); + } + + private void validateExpressCodeUnique(String code, Long id) { + DeliveryExpressDO express = deliveryExpressMapper.selectByCode(code); + if (express == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的快递公司 + if (id == null) { + throw exception(EXPRESS_CODE_DUPLICATE); + } + if (!express.getId().equals(id)) { + throw exception(EXPRESS_CODE_DUPLICATE); + } + } + private void validateDeliveryExpressExists(Long id) { + if (deliveryExpressMapper.selectById(id) == null) { + throw exception(EXPRESS_NOT_EXISTS); + } + } + + @Override + public DeliveryExpressDO getDeliveryExpress(Long id) { + return deliveryExpressMapper.selectById(id); + } + + @Override + public DeliveryExpressDO validateDeliveryExpress(Long id) { + DeliveryExpressDO deliveryExpress = deliveryExpressMapper.selectById(id); + if (deliveryExpress == null) { + throw exception(EXPRESS_NOT_EXISTS); + } + if (deliveryExpress.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) { + throw exception(EXPRESS_STATUS_NOT_ENABLE); + } + return deliveryExpress; + } + + @Override + public PageResult getDeliveryExpressPage(DeliveryExpressPageReqVO pageReqVO) { + return deliveryExpressMapper.selectPage(pageReqVO); + } + + @Override + public List getDeliveryExpressList(DeliveryExpressExportReqVO exportReqVO) { + return deliveryExpressMapper.selectList(exportReqVO); + } + + @Override + public List getDeliveryExpressListByStatus(Integer status) { + return deliveryExpressMapper.selectListByStatus(status); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressTemplateService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressTemplateService.java new file mode 100644 index 0000000..af6a722 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressTemplateService.java @@ -0,0 +1,95 @@ +package com.tashow.cloud.trade.service.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateCreateReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateDetailRespVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplatePageReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.DeliveryExpressTemplateUpdateReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; +import com.tashow.cloud.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 快递运费模板 Service 接口 + * + * @author jason + */ +public interface DeliveryExpressTemplateService { + + /** + * 创建快递运费模板 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDeliveryExpressTemplate(@Valid DeliveryExpressTemplateCreateReqVO createReqVO); + + /** + * 更新快递运费模板 + * + * @param updateReqVO 更新信息 + */ + void updateDeliveryExpressTemplate(@Valid DeliveryExpressTemplateUpdateReqVO updateReqVO); + + /** + * 删除快递运费模板 + * + * @param id 编号 + */ + void deleteDeliveryExpressTemplate(Long id); + + /** + * 获得快递运费模板 + * + * @param id 编号 + * @return 快递运费模板详情 + */ + DeliveryExpressTemplateDetailRespVO getDeliveryExpressTemplate(Long id); + + /** + * 获得快递运费模板列表 + * + * @param ids 编号 + * @return 快递运费模板列表 + */ + List getDeliveryExpressTemplateList(Collection ids); + + /** + * 获得快递运费模板列表 + * + * @return 快递运费模板列表 + */ + List getDeliveryExpressTemplateList(); + + /** + * 获得快递运费模板分页 + * + * @param pageReqVO 分页查询 + * @return 快递运费模板分页 + */ + PageResult getDeliveryExpressTemplatePage(DeliveryExpressTemplatePageReqVO pageReqVO); + + /** + * 校验快递运费模板 + * + * 如果校验不通过,抛出 {@link com.tashow.cloud.common.exception.ServiceException} 异常 + * + * @param templateId 模板编号 + * @return 快递运费模板 + */ + DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId); + + /** + * 基于运费模板编号数组和收件人地址区域编号,获取匹配运费模板 + * + * @param ids 编号列表 + * @param areaId 区域编号 + * @return Map (templateId -> 运费模板设置) + */ + Map getExpressTemplateMapByIdsAndArea(Collection ids, Integer areaId); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java new file mode 100644 index 0000000..8dd350c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryExpressTemplateServiceImpl.java @@ -0,0 +1,221 @@ +package com.tashow.cloud.trade.service.delivery; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.trade.controller.admin.delivery.vo.expresstemplate.*; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateChargeDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressTemplateFreeDO; +import com.tashow.cloud.trade.dal.mysql.delivery.DeliveryExpressTemplateChargeMapper; +import com.tashow.cloud.trade.dal.mysql.delivery.DeliveryExpressTemplateFreeMapper; +import com.tashow.cloud.trade.dal.mysql.delivery.DeliveryExpressTemplateMapper; +import com.tashow.cloud.trade.service.delivery.bo.DeliveryExpressTemplateRespBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertList; +import static com.tashow.cloud.trade.convert.delivery.DeliveryExpressTemplateConvert.INSTANCE; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NAME_DUPLICATE; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_TEMPLATE_NOT_EXISTS; + +/** + * 快递运费模板 Service 实现类 + * + * @author jason + */ +@Service +@Validated +public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTemplateService { + + @Resource + private DeliveryExpressTemplateMapper expressTemplateMapper; + @Resource + private DeliveryExpressTemplateChargeMapper expressTemplateChargeMapper; + @Resource + private DeliveryExpressTemplateFreeMapper expressTemplateFreeMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createDeliveryExpressTemplate(DeliveryExpressTemplateCreateReqVO createReqVO) { + // 校验模板名是否唯一 + validateTemplateNameUnique(createReqVO.getName(), null); + + // 插入 + DeliveryExpressTemplateDO template = INSTANCE.convert(createReqVO); + expressTemplateMapper.insert(template); + // 插入运费模板计费表 + if (CollUtil.isNotEmpty(createReqVO.getCharges())) { + expressTemplateChargeMapper.insertBatch( + INSTANCE.convertTemplateChargeList(template.getId(), createReqVO.getChargeMode(), createReqVO.getCharges()) + ); + } + // 插入运费模板包邮表 + if (CollUtil.isNotEmpty(createReqVO.getFrees())) { + expressTemplateFreeMapper.insertBatch( + INSTANCE.convertTemplateFreeList(template.getId(), createReqVO.getFrees()) + ); + } + return template.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateDeliveryExpressTemplate(DeliveryExpressTemplateUpdateReqVO updateReqVO) { + // 校验存在 + validateDeliveryExpressTemplateExists(updateReqVO.getId()); + // 校验模板名是否唯一 + validateTemplateNameUnique(updateReqVO.getName(), updateReqVO.getId()); + + // 更新运费从表 + updateExpressTemplateCharge(updateReqVO.getId(), updateReqVO.getChargeMode(), updateReqVO.getCharges()); + // 更新包邮从表 + updateExpressTemplateFree(updateReqVO.getId(), updateReqVO.getFrees()); + // 更新模板主表 + DeliveryExpressTemplateDO updateObj = INSTANCE.convert(updateReqVO); + expressTemplateMapper.updateById(updateObj); + } + + private void updateExpressTemplateFree(Long templateId, List frees) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = expressTemplateFreeMapper.selectListByTemplateId(templateId); + List newList = INSTANCE.convertTemplateFreeList(templateId, frees); + List> diffList = CollectionUtils.diffList(oldList, newList, + (oldVal, newVal) -> ObjectUtil.equal(oldVal.getId(), newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + expressTemplateFreeMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + expressTemplateFreeMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + expressTemplateFreeMapper.deleteBatchIds(convertList(diffList.get(2), DeliveryExpressTemplateFreeDO::getId)); + } + } + + private void updateExpressTemplateCharge(Long templateId, Integer chargeMode, List charges) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = expressTemplateChargeMapper.selectListByTemplateId(templateId); + List newList = INSTANCE.convertTemplateChargeList(templateId, chargeMode, charges); +// List> diffList = diffList(oldList, newList, (oldVal, newVal) -> { +// boolean same = ObjectUtil.equal(oldVal.getId(), newVal.getId()); +// if (same) { +// newVal.setChargeMode(chargeMode); // 更新下收费模式 +// } +// return same; +// }); + +// // 第二步,批量添加、修改、删除 +// if (CollUtil.isNotEmpty(diffList.get(0))) { +// expressTemplateChargeMapper.insertBatch(diffList.get(0)); +// } +// if (CollUtil.isNotEmpty(diffList.get(1))) { +// expressTemplateChargeMapper.updateBatch(diffList.get(1)); +// } +// if (CollUtil.isNotEmpty(diffList.get(2))) { +// expressTemplateChargeMapper.deleteBatchIds(convertList(diffList.get(2), DeliveryExpressTemplateChargeDO::getId)); +// } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteDeliveryExpressTemplate(Long id) { + // 校验存在 + validateDeliveryExpressTemplateExists(id); + + // 删除主表 + expressTemplateMapper.deleteById(id); + // 删除运费从表 + expressTemplateChargeMapper.deleteByTemplateId(id); + // 删除包邮从表 + expressTemplateFreeMapper.deleteByTemplateId(id); + } + + /** + * 校验运费模板名是否唯一 + * + * @param name 模板名称 + * @param id 运费模板编号,可以为 null + */ + private void validateTemplateNameUnique(String name, Long id) { + DeliveryExpressTemplateDO template = expressTemplateMapper.selectByName(name); + if (template == null) { + return; + } + // 如果 id 为空 + if (id == null) { + throw exception(EXPRESS_TEMPLATE_NAME_DUPLICATE); + } + if (!template.getId().equals(id)) { + throw exception(EXPRESS_TEMPLATE_NAME_DUPLICATE); + } + } + + private void validateDeliveryExpressTemplateExists(Long id) { + if (expressTemplateMapper.selectById(id) == null) { + throw exception(EXPRESS_TEMPLATE_NOT_EXISTS); + } + } + + @Override + public DeliveryExpressTemplateDetailRespVO getDeliveryExpressTemplate(Long id) { + List chargeList = expressTemplateChargeMapper.selectListByTemplateId(id); + List freeList = expressTemplateFreeMapper.selectListByTemplateId(id); + DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(id); + return INSTANCE.convert(template, chargeList, freeList); + } + + @Override + public List getDeliveryExpressTemplateList(Collection ids) { + return expressTemplateMapper.selectBatchIds(ids); + } + + @Override + public List getDeliveryExpressTemplateList() { + return expressTemplateMapper.selectList(); + } + + @Override + public PageResult getDeliveryExpressTemplatePage(DeliveryExpressTemplatePageReqVO pageReqVO) { + return expressTemplateMapper.selectPage(pageReqVO); + } + + @Override + public DeliveryExpressTemplateDO validateDeliveryExpressTemplate(Long templateId) { + DeliveryExpressTemplateDO template = expressTemplateMapper.selectById(templateId); + if (template == null) { + throw exception(EXPRESS_TEMPLATE_NOT_EXISTS); + } + return template; + } + + @Override + public Map getExpressTemplateMapByIdsAndArea(Collection ids, Integer areaId) { + Assert.notNull(areaId, "区域编号 {} 不能为空", areaId); + // 查询 template 数组 + if (CollUtil.isEmpty(ids)) { + return Collections.emptyMap(); + } + List templateList = expressTemplateMapper.selectBatchIds(ids); + // 查询 templateCharge 数组 + List chargeList = expressTemplateChargeMapper.selectByTemplateIds(ids); + // 查询 templateFree 数组 + List freeList = expressTemplateFreeMapper.selectListByTemplateIds(ids); + + // 组合运费模板配置 RespBO + return INSTANCE.convertMap(areaId, templateList, chargeList, freeList); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryPickUpStoreService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryPickUpStoreService.java new file mode 100644 index 0000000..ee5875f --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryPickUpStoreService.java @@ -0,0 +1,82 @@ +package com.tashow.cloud.trade.service.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpBindReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreCreateReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStorePageReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreUpdateReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import jakarta.validation.Valid; + +import java.util.Collection; +import java.util.List; + +/** + * 自提门店 Service 接口 + * + * @author jason + */ +public interface DeliveryPickUpStoreService { + + /** + * 创建自提门店 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createDeliveryPickUpStore(@Valid DeliveryPickUpStoreCreateReqVO createReqVO); + + /** + * 更新自提门店 + * + * @param updateReqVO 更新信息 + */ + void updateDeliveryPickUpStore(@Valid DeliveryPickUpStoreUpdateReqVO updateReqVO); + + /** + * 删除自提门店 + * + * @param id 编号 + */ + void deleteDeliveryPickUpStore(Long id); + + /** + * 获得自提门店 + * + * @param id 编号 + * @return 自提门店 + */ + DeliveryPickUpStoreDO getDeliveryPickUpStore(Long id); + + /** + * 获得自提门店列表 + * + * @param ids 编号 + * @return 自提门店列表 + */ + List getDeliveryPickUpStoreList(Collection ids); + + /** + * 获得指定状态的自提门店列表 + * + * @param status 状态 + * @return 自提门店列表 + */ + List getDeliveryPickUpStoreListByStatus(Integer status); + + /** + * 获得自提门店分页 + * + * @param pageReqVO 分页查询 + * @return 自提门店分页 + */ + PageResult getDeliveryPickUpStorePage(DeliveryPickUpStorePageReqVO pageReqVO); + + /** + * 绑定自提店员 + * + * @param bindReqVO 绑定数据 + */ + void bindDeliveryPickUpStore(DeliveryPickUpBindReqVO bindReqVO); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java new file mode 100644 index 0000000..242ccf8 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java @@ -0,0 +1,104 @@ +package com.tashow.cloud.trade.service.delivery; + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.common.util.object.BeanUtils; +import com.tashow.cloud.systemapi.api.user.AdminUserApi; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpBindReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreCreateReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStorePageReqVO; +import com.tashow.cloud.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreUpdateReqVO; +import com.tashow.cloud.trade.convert.delivery.DeliveryPickUpStoreConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import com.tashow.cloud.trade.dal.mysql.delivery.DeliveryPickUpStoreMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.PICK_UP_STORE_NOT_EXISTS; + + +/** + * 自提门店 Service 实现类 + * + * @author jason + */ +@Service +@Validated +public class DeliveryPickUpStoreServiceImpl implements DeliveryPickUpStoreService { + + @Resource + private DeliveryPickUpStoreMapper deliveryPickUpStoreMapper; + + @Resource + private AdminUserApi adminUserApi; + + @Override + public Long createDeliveryPickUpStore(DeliveryPickUpStoreCreateReqVO createReqVO) { + // 插入 + DeliveryPickUpStoreDO deliveryPickUpStore = DeliveryPickUpStoreConvert.INSTANCE.convert(createReqVO); + deliveryPickUpStoreMapper.insert(deliveryPickUpStore); + // 返回 + return deliveryPickUpStore.getId(); + } + + @Override + public void updateDeliveryPickUpStore(DeliveryPickUpStoreUpdateReqVO updateReqVO) { + // 校验存在 + validateDeliveryPickUpStoreExists(updateReqVO.getId()); + // 更新 + DeliveryPickUpStoreDO updateObj = DeliveryPickUpStoreConvert.INSTANCE.convert(updateReqVO); + deliveryPickUpStoreMapper.updateById(updateObj); + } + + @Override + public void deleteDeliveryPickUpStore(Long id) { + // 校验存在 + validateDeliveryPickUpStoreExists(id); + // 删除 + deliveryPickUpStoreMapper.deleteById(id); + } + + private void validateDeliveryPickUpStoreExists(Long id) { + DeliveryPickUpStoreDO deliveryPickUpStore = deliveryPickUpStoreMapper.selectById(id); + if (deliveryPickUpStore == null) { + throw exception(PICK_UP_STORE_NOT_EXISTS); + } + } + + @Override + public DeliveryPickUpStoreDO getDeliveryPickUpStore(Long id) { + return deliveryPickUpStoreMapper.selectById(id); + } + + @Override + public List getDeliveryPickUpStoreList(Collection ids) { + return deliveryPickUpStoreMapper.selectBatchIds(ids); + } + + @Override + public List getDeliveryPickUpStoreListByStatus(Integer status) { + return deliveryPickUpStoreMapper.selectListByStatus(status); + } + + @Override + public PageResult getDeliveryPickUpStorePage(DeliveryPickUpStorePageReqVO pageReqVO) { + return deliveryPickUpStoreMapper.selectPage(pageReqVO); + } + + @Override + public void bindDeliveryPickUpStore(DeliveryPickUpBindReqVO bindReqVO) { + // 1.1 校验门店存在 + validateDeliveryPickUpStoreExists(bindReqVO.getId()); + // 1.2 校验用户存在 + adminUserApi.validateUserList(bindReqVO.getVerifyUserIds()).checkError(); + + // 2. 更新 + DeliveryPickUpStoreDO updateObj = BeanUtils.toBean(bindReqVO, DeliveryPickUpStoreDO.class); + deliveryPickUpStoreMapper.updateById(updateObj); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java new file mode 100644 index 0000000..5628be9 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/delivery/bo/DeliveryExpressTemplateRespBO.java @@ -0,0 +1,80 @@ +package com.tashow.cloud.trade.service.delivery.bo; + +import com.tashow.cloud.tradeapi.enums.delivery.DeliveryExpressChargeModeEnum; +import lombok.Data; + +/** + * 运费模板配置 Resp BO + * + * @author jason + */ +@Data +public class DeliveryExpressTemplateRespBO { + + /** + * 配送计费方式 + * + * 枚举 {@link DeliveryExpressChargeModeEnum} + */ + private Integer chargeMode; + + /** + * 运费模板快递运费设置 + */ + private Charge charge; + + /** + * 运费模板包邮设置 + */ + private Free free; + + /** + * 快递运费模板费用配置 BO + * + * @author jason + */ + @Data + public static class Charge { + + /** + * 首件数量(件数,重量,或体积) + */ + private Double startCount; + /** + * 起步价,单位:分 + */ + private Integer startPrice; + /** + * 续件数量(件, 重量,或体积) + */ + private Double extraCount; + /** + * 额外价,单位:分 + */ + private Integer extraPrice; + } + + /** + * 快递运费模板包邮配置 BO + * + * @author jason + */ + @Data + public static class Free { + + /** + * 包邮金额,单位:分 + * + * 订单总金额 > 包邮金额时,才免运费 + */ + private Integer freePrice; + + /** + * 包邮件数 + * + * 订单总件数 > 包邮件数时,才免运费 + */ + private Integer freeCount; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderLogService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderLogService.java new file mode 100644 index 0000000..2d4bfdc --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderLogService.java @@ -0,0 +1,35 @@ +package com.tashow.cloud.trade.service.order; + +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.service.order.bo.TradeOrderLogCreateReqBO; +import org.springframework.scheduling.annotation.Async; + +import java.util.List; + +/** + * 交易下单日志 Service 接口 + * + * @author 陈賝 + * @since 2023/7/6 15:44 + */ +public interface TradeOrderLogService { + + /** + * 创建交易下单日志 + * + * @param logDTO 日志记录 + * @author 陈賝 + * @since 2023/7/6 15:45 + */ + @Async + void createOrderLog(TradeOrderLogCreateReqBO logDTO); + + /** + * 获得交易订单日志列表 + * + * @param orderId 订单编号 + * @return 交易订单日志列表 + */ + List getOrderLogListByOrderId(Long orderId); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderLogServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderLogServiceImpl.java new file mode 100644 index 0000000..bfd6dc5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderLogServiceImpl.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.trade.service.order; + +import com.tashow.cloud.trade.convert.order.TradeOrderLogConvert; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderLogDO; +import com.tashow.cloud.trade.dal.mysql.order.TradeOrderLogMapper; +import com.tashow.cloud.trade.service.order.bo.TradeOrderLogCreateReqBO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 交易下单日志 Service 实现类 + * + * @author 陈賝 + * @since 2023/7/6 15:44 + */ +@Service +public class TradeOrderLogServiceImpl implements TradeOrderLogService { + + @Resource + private TradeOrderLogMapper tradeOrderLogMapper; + + @Override + public void createOrderLog(TradeOrderLogCreateReqBO createReqBO) { + tradeOrderLogMapper.insert(TradeOrderLogConvert.INSTANCE.convert(createReqBO)); + } + + @Override + public List getOrderLogListByOrderId(Long orderId) { + return tradeOrderLogMapper.selectListByOrderId(orderId); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderQueryService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderQueryService.java new file mode 100644 index 0000000..55ef7bf --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderQueryService.java @@ -0,0 +1,161 @@ +package com.tashow.cloud.trade.service.order; + + +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderSummaryRespVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderPageReqVO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderTypeEnum; + +import java.util.Collection; +import java.util.List; + +import static java.util.Collections.singleton; + +/** + * 交易订单【读】 Service 接口 + * + * @author 芋道源码 + */ +public interface TradeOrderQueryService { + + // =================== Order =================== + + /** + * 获得指定编号的交易订单 + * + * @param id 交易订单编号 + * @return 交易订单 + */ + TradeOrderDO getOrder(Long id); + + /** + * 获得指定用户,指定的交易订单 + * + * @param userId 用户编号 + * @param id 交易订单编号 + * @return 交易订单 + */ + TradeOrderDO getOrder(Long userId, Long id); + + /** + * 获得指定用户,指定活动,指定状态的交易订单 + * + * @param userId 用户编号 + * @param combinationActivityId 活动编号 + * @param status 订单状态 + * @return 交易订单 + */ + TradeOrderDO getOrderByUserIdAndStatusAndCombination(Long userId, Long combinationActivityId, Integer status); + + /** + * 获得订单列表 + * + * @param ids 订单编号数组 + * @return 订单列表 + */ + List getOrderList(Collection ids); + + /** + * 【管理员】获得交易订单分页 + * + * @param reqVO 分页请求 + * @return 交易订单 + */ + PageResult getOrderPage(TradeOrderPageReqVO reqVO); + + /** + * 获得订单统计 + * + * @param reqVO 请求参数 + * @return 订单统计 + */ + TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO); + + /** + * 【会员】获得交易订单分页 + * + * @param userId 用户编号 + * @param reqVO 分页请求 + * @return 交易订单 + */ + PageResult getOrderPage(Long userId, AppTradeOrderPageReqVO reqVO); + + /** + * 【会员】获得交易订单数量 + * + * @param userId 用户编号 + * @param status 订单状态。如果为空,则不进行筛选 + * @param commonStatus 评价状态。如果为空,则不进行筛选 + * @return 订单数量 + */ + Long getOrderCount(Long userId, Integer status, Boolean commonStatus); + + /** + * 【前台】获得订单的物流轨迹 + * + * @param id 订单编号 + * @param userId 用户编号 + * @return 物流轨迹数组 + */ + List getExpressTrackList(Long id, Long userId); + + /** + * 【后台】获得订单的物流轨迹 + * + * @param id 订单编号 + * @return 物流轨迹数组 + */ + List getExpressTrackList(Long id); + + /** + * 【会员】在指定活动下,用户购买的商品数量 + * + * @param userId 用户编号 + * @param activityId 活动编号 + * @param type 订单类型 + * @return 活动商品数量 + */ + int getActivityProductCount(Long userId, Long activityId, TradeOrderTypeEnum type); + + // =================== Order Item =================== + + /** + * 获得指定用户,指定的交易订单项 + * + * @param userId 用户编号 + * @param itemId 交易订单项编号 + * @return 交易订单项 + */ + TradeOrderItemDO getOrderItem(Long userId, Long itemId); + + /** + * 获得交易订单项 + * + * @param id 交易订单项编号 itemId + * @return 交易订单项 + */ + TradeOrderItemDO getOrderItem(Long id); + + /** + * 根据交易订单编号,查询交易订单项 + * + * @param orderId 交易订单编号 + * @return 交易订单项数组 + */ + default List getOrderItemListByOrderId(Long orderId) { + return getOrderItemListByOrderId(singleton(orderId)); + } + + /** + * 根据交易订单编号数组,查询交易订单项 + * + * @param orderIds 交易订单编号数组 + * @return 交易订单项数组 + */ + List getOrderItemListByOrderId(Collection orderIds); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderQueryServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderQueryServiceImpl.java new file mode 100644 index 0000000..d045a6c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderQueryServiceImpl.java @@ -0,0 +1,258 @@ +package com.tashow.cloud.trade.service.order; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.pojo.PageResult; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderSummaryRespVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderPageReqVO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.dal.mysql.order.TradeOrderItemMapper; +import com.tashow.cloud.trade.dal.mysql.order.TradeOrderMapper; +import com.tashow.cloud.trade.dal.redis.RedisKeyConstants; +import com.tashow.cloud.trade.framework.delivery.core.client.ExpressClientFactory; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackQueryReqDTO; +import com.tashow.cloud.trade.framework.delivery.core.client.dto.ExpressTrackRespDTO; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressService; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderRefundStatusEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderStatusEnum; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderTypeEnum; +import jakarta.annotation.Resource; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.*; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.EXPRESS_NOT_EXISTS; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.ORDER_NOT_FOUND; + + +/** + * 交易订单【读】 Service 实现类 + * + * @author 芋道源码 + */ +@Service +public class TradeOrderQueryServiceImpl implements TradeOrderQueryService { + + @Resource + private ExpressClientFactory expressClientFactory; + + @Resource + private TradeOrderMapper tradeOrderMapper; + @Resource + private TradeOrderItemMapper tradeOrderItemMapper; + + @Resource + private DeliveryExpressService deliveryExpressService; + +// @Resource +// private MemberUserApi memberUserApi; + + // =================== Order =================== + + @Override + public TradeOrderDO getOrder(Long id) { + return tradeOrderMapper.selectById(id); + } + + @Override + public TradeOrderDO getOrder(Long userId, Long id) { + TradeOrderDO order = tradeOrderMapper.selectById(id); + if (order != null + && ObjectUtil.notEqual(order.getUserId(), userId)) { + return null; + } + return order; + } + + @Override + public TradeOrderDO getOrderByUserIdAndStatusAndCombination(Long userId, Long combinationActivityId, Integer status) { + return tradeOrderMapper.selectByUserIdAndCombinationActivityIdAndStatus(userId, combinationActivityId, status); + } + + @Override + public List getOrderList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return tradeOrderMapper.selectBatchIds(ids); + } + + @Override + public PageResult getOrderPage(TradeOrderPageReqVO reqVO) { + // 根据用户查询条件构建用户编号列表 + Set userIds = buildQueryConditionUserIds(reqVO); + if (userIds == null) { // 没查询到用户,说明肯定也没他的订单 + return PageResult.empty(); + } + + // 分页查询 + return tradeOrderMapper.selectPage(reqVO, userIds); + } + + private Set buildQueryConditionUserIds(TradeOrderPageReqVO reqVO) { + // 获得 userId 相关的查询 + Set userIds = new HashSet<>(); +// if (StrUtil.isNotEmpty(reqVO.getUserMobile())) { +// MemberUserRespDTO user = memberUserApi.getUserByMobile(reqVO.getUserMobile()).getCheckedData(); +// if (user == null) { // 没查询到用户,说明肯定也没他的订单 +// return null; +// } +// userIds.add(user.getId()); +// } +// if (StrUtil.isNotEmpty(reqVO.getUserNickname())) { +// List users = memberUserApi.getUserListByNickname(reqVO.getUserNickname()).getCheckedData(); +// if (CollUtil.isEmpty(users)) { // 没查询到用户,说明肯定也没他的订单 +// return null; +// } +// userIds.addAll(convertSet(users, MemberUserRespDTO::getId)); +// } + return userIds; + } + + @Override + public TradeOrderSummaryRespVO getOrderSummary(TradeOrderPageReqVO reqVO) { + // 根据用户查询条件构建用户编号列表 + Set userIds = buildQueryConditionUserIds(reqVO); + if (userIds == null) { // 没查询到用户,说明肯定也没他的订单 + return new TradeOrderSummaryRespVO(); + } + // 查询每个售后状态对应的数量、金额 + List> list = tradeOrderMapper.selectOrderSummaryGroupByRefundStatus(reqVO, userIds); + + TradeOrderSummaryRespVO vo = new TradeOrderSummaryRespVO().setAfterSaleCount(0L).setAfterSalePrice(0L); + for (Map map : list) { + Long count = MapUtil.getLong(map, "count", 0L); + Long price = MapUtil.getLong(map, "price", 0L); + // 未退款的计入订单,部分退款、全部退款计入售后 + if (TradeOrderRefundStatusEnum.NONE.getStatus().equals(MapUtil.getInt(map, "refundStatus"))) { + vo.setOrderCount(count).setOrderPayPrice(price); + } else { + vo.setAfterSaleCount(vo.getAfterSaleCount() + count).setAfterSalePrice(vo.getAfterSalePrice() + price); + } + } + return vo; + } + + @Override + public PageResult getOrderPage(Long userId, AppTradeOrderPageReqVO reqVO) { + return tradeOrderMapper.selectPage(reqVO, userId); + } + + @Override + public Long getOrderCount(Long userId, Integer status, Boolean commentStatus) { + return tradeOrderMapper.selectCountByUserIdAndStatus(userId, status, commentStatus); + } + + @Override + public List getExpressTrackList(Long id, Long userId) { + // 查询订单 + TradeOrderDO order = tradeOrderMapper.selectByIdAndUserId(id, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 查询物流 + return getExpressTrackList(order); + } + + @Override + public List getExpressTrackList(Long id) { + // 查询订单 + TradeOrderDO order = tradeOrderMapper.selectById(id); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 查询物流 + return getExpressTrackList(order); + } + + @Override + public int getActivityProductCount(Long userId, Long activityId, TradeOrderTypeEnum type) { + // 获得订单列表 + List orders = tradeOrderMapper.selectListByUserIdAndActivityId(userId, activityId, type); + orders.removeIf(order -> TradeOrderStatusEnum.isCanceled(order.getOrderStatus())); // 过滤掉【已取消】的订单 + if (CollUtil.isEmpty(orders)) { + return 0; + } + // 获得订单项列表 + return tradeOrderItemMapper.selectProductSumByOrderId(convertSet(orders, TradeOrderDO::getId)); + } + + /** + * 获得订单的物流轨迹 + * + * @param order 订单 + * @return 物流轨迹 + */ + private List getExpressTrackList(TradeOrderDO order) { + if (order.getLogisticsId() == null) { + return Collections.emptyList(); + } + // 查询物流公司 + DeliveryExpressDO express = deliveryExpressService.getDeliveryExpress(order.getLogisticsId()); + if (express == null) { + throw exception(EXPRESS_NOT_EXISTS); + } + // 查询物流轨迹 + return getSelf().getExpressTrackList(express.getCode(), order.getLogisticsNo(), order.getReceiverMobile()); + } + + /** + * 查询物流轨迹 + *

+ * 缓存的目的:考虑及时性要求不高,但是每次调用需要钱 + * + * @param code 快递公司编码 + * @param logisticsNo 发货快递单号 + * @param receiverMobile 收、寄件人的电话号码 + * @return 物流轨迹 + */ + @Cacheable(cacheNames = RedisKeyConstants.EXPRESS_TRACK, key = "#code + '-' + #logisticsNo + '-' + #receiverMobile", + unless = "#result == null") + public List getExpressTrackList(String code, String logisticsNo, String receiverMobile) { + return expressClientFactory.getDefaultExpressClient().getExpressTrackList(new ExpressTrackQueryReqDTO() + .setExpressCode(code).setLogisticsNo(logisticsNo).setPhone(receiverMobile)); + } + + // =================== Order Item =================== + + @Override + public TradeOrderItemDO getOrderItem(Long userId, Long itemId) { + TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(itemId); + if (orderItem != null + && ObjectUtil.notEqual(orderItem.getUserId(), userId)) { + return null; + } + return orderItem; + } + + @Override + public TradeOrderItemDO getOrderItem(Long id) { + return tradeOrderItemMapper.selectById(id); + } + + @Override + public List getOrderItemListByOrderId(Collection orderIds) { + if (CollUtil.isEmpty(orderIds)) { + return Collections.emptyList(); + } + return tradeOrderItemMapper.selectListByOrderId(orderIds); + } + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private TradeOrderQueryServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderUpdateService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderUpdateService.java new file mode 100644 index 0000000..98c0118 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderUpdateService.java @@ -0,0 +1,221 @@ +package com.tashow.cloud.trade.service.order; + +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +/** + * 交易订单【写】Service 接口 + * + * @author LeeYan9 + * @since 2022-08-26 + */ +public interface TradeOrderUpdateService { + + // =================== Order =================== + + /** + * 获得订单结算信息 + * + * @param userId 登录用户 + * @param settlementReqVO 订单结算请求 + * @return 订单结算结果 + */ + AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO); + + /** + * 【会员】创建交易订单 + * + * @param userId 登录用户 + * @param createReqVO 创建交易订单请求模型 + * @return 交易订单的 + */ + TradeOrderDO createOrder(Long userId, AppTradeOrderCreateReqVO createReqVO); + + /** + * 更新交易订单已支付 + * + * @param id 交易订单编号 + * @param payOrderId 支付订单编号 + */ + void updateOrderPaid(Long id, Long payOrderId); + + /** + * 同步订单的支付状态 + * + * 1. Quietly 表示,即使同步失败,也不会抛出异常 + * 2. 什么时候回出现异常?因为是主动同步,可能和支付模块的回调通知 {@link #updateOrderPaid(Long, Long)} 存在并发冲突,导致抛出异常 + * + * @param id 订单编号 + * @param payOrderId 支付订单编号 + */ + void syncOrderPayStatusQuietly(Long id, Long payOrderId); + + /** + * 【管理员】发货交易订单 + * + * @param deliveryReqVO 发货请求 + */ + void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO); + + /** + * 【会员】收货交易订单 + * + * @param userId 用户编号 + * @param id 订单编号 + */ + void receiveOrderByMember(Long userId, Long id); + + /** + * 【系统】自动收货交易订单 + * + * @return 收货数量 + */ + int receiveOrderBySystem(); + + /** + * 【会员】取消交易订单 + * + * @param userId 用户编号 + * @param id 订单编号 + */ + void cancelOrderByMember(Long userId, Long id); + + /** + * 【系统】自动取消订单 + * + * @return 取消数量 + */ + int cancelOrderBySystem(); + + /** + * 【会员】删除订单 + * + * @param userId 用户编号 + * @param id 订单编号 + */ + void deleteOrder(Long userId, Long id); + + /** + * 【管理员】交易订单备注 + * + * @param reqVO 请求 + */ + void updateOrderRemark(TradeOrderRemarkReqVO reqVO); + + /** + * 【管理员】调整价格 + * + * @param reqVO 请求 + */ + void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO); + + /** + * 【管理员】调整地址 + * + * @param reqVO 请求 + */ + void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO); + + /** + * 【管理员】核销订单 + * + * @param userId 管理员编号 + * @param id 订单编号 + */ + void pickUpOrderByAdmin(Long userId, Long id); + + /** + * 【管理员】核销订单 + * + * @param userId 管理员编号 + * @param pickUpVerifyCode 自提核销码 + */ + void pickUpOrderByAdmin(Long userId, String pickUpVerifyCode); + + /** + * 【管理员】根据自提核销码,查询订单 + * + * @param pickUpVerifyCode 自提核销码 + */ + TradeOrderDO getByPickUpVerifyCode(String pickUpVerifyCode); + + // =================== Order Item =================== + + /** + * 当售后申请后,更新交易订单项的售后状态 + * + * @param id 交易订单项编号 + * @param afterSaleId 售后单编号 + */ + void updateOrderItemWhenAfterSaleCreate(@NotNull Long id, @NotNull Long afterSaleId); + + /** + * 当售后完成后,更新交易订单项的售后状态 + * + * @param id 交易订单项编号 + * @param refundPrice 退款金额 + */ + void updateOrderItemWhenAfterSaleSuccess(@NotNull Long id, @NotNull Integer refundPrice); + + /** + * 当售后取消(用户取消、管理员驳回、管理员拒绝收货)后,更新交易订单项的售后状态 + * + * @param id 交易订单项编号 + */ + void updateOrderItemWhenAfterSaleCancel(@NotNull Long id); + + /** + * 【会员】创建订单项的评论 + * + * @param userId 用户编号 + * @param createReqVO 创建请求 + * @return 得到评价 id + */ + Long createOrderItemCommentByMember(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO); + + /** + * 【系统】创建订单项的评论 + * + * @return 被评论的订单数 + */ + int createOrderItemCommentBySystem(); + + /** + * 更新拼团相关信息到订单 + * + * @param orderId 订单编号 + * @param activityId 拼团活动编号 + * @param combinationRecordId 拼团记录编号 + * @param headId 团长编号 + */ + void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId); + + /** + * 取消支付订单 + * + * @param userId 用户编号 + * @param orderId 订单编号 + * @param cancelType 取消类型 + */ + void cancelPaidOrder(Long userId, Long orderId, Integer cancelType); + + /** + * 更新下单赠送的优惠券编号到订单 + * + * @param userId 用户编号 + * @param orderId 订单编号 + * @param giveCouponIds 赠送的优惠券编号列表 + */ + void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderUpdateServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderUpdateServiceImpl.java new file mode 100644 index 0000000..78d54be --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -0,0 +1,962 @@ +package com.tashow.cloud.trade.service.order; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.tashow.cloud.common.enums.UserTypeEnum; +import com.tashow.cloud.common.util.json.JsonUtils; +import com.tashow.cloud.common.util.number.MoneyUtils; +import com.tashow.cloud.memberapi.api.address.MemberAddressApi; +import com.tashow.cloud.memberapi.api.address.dto.MemberAddressRespDTO; +import com.tashow.cloud.payapi.api.order.PayOrderApi; +import com.tashow.cloud.payapi.api.order.dto.PayOrderCreateReqDTO; +import com.tashow.cloud.payapi.api.refund.PayRefundApi; +import com.tashow.cloud.payapi.api.refund.dto.PayRefundCreateReqDTO; +import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum; +import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; +import com.tashow.cloud.systemapi.api.social.SocialClientApi; +import com.tashow.cloud.systemapi.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; +import com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderCreateReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO; +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO; +import com.tashow.cloud.trade.controller.app.order.vo.item.AppTradeOrderItemCommentCreateReqVO; +import com.tashow.cloud.trade.convert.order.TradeOrderConvert; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryExpressDO; +import com.tashow.cloud.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.trade.dal.mysql.order.TradeOrderItemMapper; +import com.tashow.cloud.trade.dal.mysql.order.TradeOrderMapper; +import com.tashow.cloud.trade.dal.redis.no.TradeNoRedisDAO; +import com.tashow.cloud.trade.framework.order.config.TradeOrderProperties; +import com.tashow.cloud.trade.framework.order.core.annotations.TradeOrderLog; +import com.tashow.cloud.trade.framework.order.core.utils.TradeOrderLogUtils; +import com.tashow.cloud.trade.service.delivery.DeliveryExpressService; +import com.tashow.cloud.trade.service.delivery.DeliveryPickUpStoreService; +import com.tashow.cloud.trade.service.order.handler.TradeOrderHandler; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateRespBO; +import com.tashow.cloud.tradeapi.enums.delivery.DeliveryTypeEnum; +import com.tashow.cloud.tradeapi.enums.order.*; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static com.tashow.cloud.common.exception.util.ServiceExceptionUtil.exception; +import static com.tashow.cloud.common.util.collection.CollectionUtils.convertSet; +import static com.tashow.cloud.common.util.date.LocalDateTimeUtils.minusTime; +import static com.tashow.cloud.tradeapi.enums.ErrorCodeConstants.*; +import static com.tashow.cloud.tradeapi.enums.MessageTemplateConstants.WXA_ORDER_DELIVERY; + + +/** + * 交易订单【写】Service 实现类 + * + * @author LeeYan9 + * @since 2022-08-26 + */ +@Service +@Slf4j +public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { + + @Resource + private TradeOrderMapper tradeOrderMapper; + @Resource + private TradeOrderItemMapper tradeOrderItemMapper; + @Resource + private TradeNoRedisDAO tradeNoRedisDAO; + @Resource + private List tradeOrderHandlers; + @Resource + private DeliveryExpressService deliveryExpressService; + @Resource + private DeliveryPickUpStoreService pickUpStoreService; + @Resource + private PayOrderApi payOrderApi; + @Resource + private MemberAddressApi addressApi; + @Resource + public SocialClientApi socialClientApi; + @Resource + public PayRefundApi payRefundApi; + @Resource + private TradeOrderProperties tradeOrderProperties; + + // =================== Order =================== + + @Override + public AppTradeOrderSettlementRespVO settlementOrder(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) { + // 1. 获得收货地址 + MemberAddressRespDTO address = getAddress(userId, settlementReqVO.getAddressId()); + if (address != null) { + settlementReqVO.setAddressId(address.getId()); + } + + // 2. 计算价格 + TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, settlementReqVO); + + // 3. 拼接返回 + return TradeOrderConvert.INSTANCE.convert(calculateRespBO, address); + } + + /** + * 获得用户地址 + * + * @param userId 用户编号 + * @param addressId 地址编号 + * @return 地址 + */ + private MemberAddressRespDTO getAddress(Long userId, Long addressId) { + if (addressId != null) { + return addressApi.getAddress(addressId, userId).getCheckedData(); + } + return addressApi.getDefaultAddress(userId).getCheckedData(); + } + + /** + * 计算订单价格 + * + * @param userId 用户编号 + * @param settlementReqVO 结算信息 + * @return 订单价格 + */ + private TradePriceCalculateRespBO calculatePrice(Long userId, AppTradeOrderSettlementReqVO settlementReqVO) { + // 1. 如果来自购物车,则获得购物车的商品 +// List cartList = cartService.getCartList(userId, +// convertSet(settlementReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId)); + + // 2. 计算价格 +// TradePriceCalculateReqBO calculateReqBO = TradeOrderConvert.INSTANCE.convert(userId, settlementReqVO, cartList); +// calculateReqBO.getItems().forEach(item -> Assert.isTrue(item.getSelected(), // 防御性编程,保证都是选中的 +// "商品({}) 未设置为选中", item.getSkuId())); + return null;// tradePriceService.calculateOrderPrice(calculateReqBO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE) + public TradeOrderDO createOrder(Long userId, AppTradeOrderCreateReqVO createReqVO) { + // 1.1 价格计算 + TradePriceCalculateRespBO calculateRespBO = calculatePrice(userId, createReqVO); + // 1.2 构建订单 + TradeOrderDO order = null;// buildTradeOrder(userId, createReqVO, calculateRespBO); + List orderItems = null;// buildTradeOrderItems(order, calculateRespBO); + + // 2. 订单创建前的逻辑 + tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems)); + + // 3. 保存订单 + tradeOrderMapper.insert(order); + orderItems.forEach(orderItem -> orderItem.setOrderId(order.getId())); + tradeOrderItemMapper.insertBatch(orderItems); + + // 4. 订单创建后的逻辑 + afterCreateTradeOrder(order, orderItems, createReqVO); + return order; + } +// +// private TradeOrderDO buildTradeOrder(Long userId, AppTradeOrderCreateReqVO createReqVO, +// TradePriceCalculateRespBO calculateRespBO) { +// TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(userId, createReqVO, calculateRespBO); +// order.setType(calculateRespBO.getType()); +// order.setOrderNum(tradeNoRedisDAO.generate(TradeNoRedisDAO.TRADE_ORDER_NO_PREFIX)); +// order.setStatus(TradeOrderStatusEnum.UNPAID.getOrderStatus()); +// order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getOrderStatus()); +// order.setProductCount(getSumValue(calculateRespBO.getItems(), TradePriceCalculateRespBO.OrderItem::getCount, Integer::sum)); +// order.setUserIp(getClientIP()).setTerminal(getTerminal()); +// // 使用 + 赠送优惠券 +// order.setGiveCouponTemplateCounts(calculateRespBO.getGiveCouponTemplateCounts()); +// // 支付 + 退款信息 +// order.setAdjustPrice(0).setPayStatus(false); +// order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getOrderStatus()).setRefundPrice(0); +// // 物流信息 +// order.setDeliveryType(createReqVO.getDeliveryType()); +// if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) { +// MemberAddressRespDTO address = addressApi.getAddress(createReqVO.getAddressId(), userId).getCheckedData(); +// Assert.notNull(address, "地址({}) 不能为空", createReqVO.getAddressId()); // 价格计算时,已经计算 +// order.setReceiverName(address.getName()).setReceiverMobile(address.getMobile()) +// .setReceiverAreaId(address.getAreaId()).setReceiverDetailAddress(address.getDetailAddress()); +// } else if (Objects.equals(createReqVO.getDeliveryType(), DeliveryTypeEnum.PICK_UP.getType())) { +// order.setReceiverName(createReqVO.getReceiverName()).setReceiverMobile(createReqVO.getReceiverMobile()); +// order.setPickUpVerifyCode(RandomUtil.randomNumbers(8)); // 随机一个核销码,长度为 8 位 +// } +// return order; +// } +// +// private List buildTradeOrderItems(TradeOrderDO tradeOrderDO, +// TradePriceCalculateRespBO calculateRespBO) { +// return TradeOrderConvert.INSTANCE.convertList(tradeOrderDO, calculateRespBO); +// } + + /** + * 订单创建后,执行后置逻辑 + *

+ * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等 + * + * @param order 订单 + * @param orderItems 订单项 + * @param createReqVO 创建订单请求 + */ + private void afterCreateTradeOrder(TradeOrderDO order, List orderItems, + AppTradeOrderCreateReqVO createReqVO) { + // 1. 执行订单创建后置处理器 + tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems)); + + // 2. 删除购物车商品 + Set cartIds = convertSet(createReqVO.getItems(), AppTradeOrderSettlementReqVO.Item::getCartId); + if (CollUtil.isNotEmpty(cartIds)) { +// cartService.deleteCart(order.getUserId(), cartIds); + } + + // 3. 生成预支付 + // 特殊情况:积分兑换时,可能支付金额为零 + createPayOrder(order, orderItems); + + // 4. 插入订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getOrderStatus()); + + // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来! + } + + private void createPayOrder(TradeOrderDO order, List orderItems) { + // 创建支付单,用于后续的支付 + PayOrderCreateReqDTO payOrderCreateReqDTO = TradeOrderConvert.INSTANCE.convert( + order, orderItems, tradeOrderProperties); + Long payOrderId = payOrderApi.createOrder(payOrderCreateReqDTO).getCheckedData(); + + // 更新到交易单上 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setPayOrderId(payOrderId)); + order.setPayOrderId(payOrderId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_PAY) + public void updateOrderPaid(Long id, Long payOrderId) { + // 1.1 校验订单是否存在 + TradeOrderDO order = validateOrderExists(id); + // 1.2 校验订单已支付 + if (!TradeOrderStatusEnum.isUnpaid(order.getOrderStatus()) || order.getPayStatus()) { + // 特殊:如果订单已支付,且支付单号相同,直接返回,说明重复回调 + if (ObjectUtil.equals(order.getPayOrderId(), payOrderId)) { + log.warn("[updateOrderPaid][order({}) 已支付,且支付单号相同({}),直接返回]", order, payOrderId); + return; + } + log.error("[updateOrderPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]", + id, payOrderId, JsonUtils.toJsonString(order)); + throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); + } + + // 2. 校验支付订单的合法性 + PayOrderRespDTO payOrder = validatePayOrderPaid(order, payOrderId); + + // 3. 更新 TradeOrderDO 状态为已支付,等待发货 + int updateCount = tradeOrderMapper.updateByIdAndStatus(id, order.getOrderStatus(), + new TradeOrderDO().setOrderStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()).setPayStatus(true) + .setPayTime(LocalDateTime.now()).setPayChannelCode("")); + if (updateCount == 0) { + throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID); + } + + // 4. 执行 TradeOrderHandler 的后置处理 + List orderItems = tradeOrderItemMapper.selectListByOrderId(id); + tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems)); + + // 5. 记录订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus()); + TradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue()); + } + + @Override + public void syncOrderPayStatusQuietly(Long id, Long payOrderId) { + PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId).getCheckedData(); + if (payOrder == null) { + return; + } + if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { + return; + } + try { + getSelf().updateOrderPaid(id, payOrderId); + } catch (Throwable e) { + log.warn("[syncOrderPayStatusQuietly][id({}) payOrderId({}) 同步支付状态失败]", id, payOrderId, e); + } + } + + /** + * 校验支付订单的合法性 + * + * @param order 交易订单 + * @param payOrderId 支付订单编号 + * @return 支付订单 + */ + private PayOrderRespDTO validatePayOrderPaid(TradeOrderDO order, Long payOrderId) { + // 1. 校验支付单是否存在 + PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId).getCheckedData(); + if (payOrder == null) { + log.error("[validatePayOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", order.getId(), payOrderId); + throw exception(ORDER_NOT_FOUND); + } + + // 2.1 校验支付单已支付 + if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { + log.error("[validatePayOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", + order.getId(), payOrderId, JsonUtils.toJsonString(payOrder)); + throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS); + } + // 2.2 校验支付金额一致 + if (ObjectUtil.notEqual(null, order.getPayPrice())) { + log.error("[validatePayOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]", + order.getId(), payOrderId, JsonUtils.toJsonString(order), JsonUtils.toJsonString(payOrder)); + throw exception(ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH); + } + // 2.2 校验支付订单匹配(二次) + if (ObjectUtil.notEqual(null, order.getId().toString())) { + log.error("[validatePayOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", + order.getId(), payOrderId, JsonUtils.toJsonString(payOrder)); + throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR); + } + return payOrder; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_DELIVERY) + public void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO) { + // 1.1 校验并获得交易订单(可发货) + TradeOrderDO order = validateOrderDeliverable(deliveryReqVO.getId()); + // 1.2 校验 deliveryType 是否为快递,是快递才可以发货 + if (ObjectUtil.notEqual(order.getDeliveryType(), DeliveryTypeEnum.EXPRESS.getType())) { + throw exception(ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS); + } + + // 2. 更新订单为已发货 + TradeOrderDO updateOrderObj = new TradeOrderDO(); + // 2.1 快递发货 + DeliveryExpressDO express = null; + if (ObjectUtil.notEqual(deliveryReqVO.getLogisticsId(), TradeOrderDO.LOGISTICS_ID_NULL)) { + express = deliveryExpressService.validateDeliveryExpress(deliveryReqVO.getLogisticsId()); + updateOrderObj.setLogisticsId(deliveryReqVO.getLogisticsId()).setLogisticsNo(deliveryReqVO.getLogisticsNo()); + } else { + // 2.2 无需发货 + updateOrderObj.setLogisticsId(0L).setLogisticsNo(""); + } + // 执行更新 + updateOrderObj.setOrderStatus(TradeOrderStatusEnum.DELIVERED.getStatus()).setDeliveryTime(LocalDateTime.now()); + int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getOrderStatus(), updateOrderObj); + if (updateCount == 0) { + throw exception(ORDER_DELIVERY_FAIL_STATUS_NOT_UNDELIVERED); + } + + // 3. 记录订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), TradeOrderStatusEnum.DELIVERED.getStatus(), + MapUtil.builder().put("deliveryName", express != null ? express.getName() : "") + .put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "").build()); + + // 4.1 发送站内信 +// tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO() +// .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null)); + // 4.2 发送订阅消息 + getSelf().sendDeliveryOrderMessage(order, deliveryReqVO); + } + + @Async + public void sendDeliveryOrderMessage(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) { + // 构建并发送模版消息 + Long orderId = order.getId(); + socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO() + .setUserId(order.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) + .setTemplateTitle(WXA_ORDER_DELIVERY) + .setPage("pages/order/detail?id=" + orderId) // 订单详情页 + .addMessage("character_string3", String.valueOf(orderId)) // 订单编号 + .addMessage("phrase6", TradeOrderStatusEnum.DELIVERED.getName()) // 订单状态 + .addMessage("date4", LocalDateTimeUtil.formatNormal(LocalDateTime.now()))// 发货时间 + .addMessage("character_string5", StrUtil.blankToDefault(deliveryReqVO.getLogisticsNo(), "-")) // 快递单号 + .addMessage("thing9", order.getReceiverDetailAddress())).checkError(); // 收货地址 + } + + /** + * 校验交易订单满足被发货的条件 + *

+ * 1. 交易订单未发货 + * + * @param id 交易订单编号 + * @return 交易订单 + */ + private TradeOrderDO validateOrderDeliverable(Long id) { + TradeOrderDO order = validateOrderExists(id); + // 1. 校验订单是否未发货 + if (ObjectUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { + throw exception(ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE); + } + + // 2. 执行 TradeOrderHandler 前置处理 + tradeOrderHandlers.forEach(handler -> handler.beforeDeliveryOrder(order)); + return order; + } + + @NotNull + private TradeOrderDO validateOrderExists(Long id) { + // 校验订单是否存在 + TradeOrderDO order = tradeOrderMapper.selectById(id); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + return order; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_RECEIVE) + public void receiveOrderByMember(Long userId, Long id) { + // 校验并获得交易订单(可收货) + TradeOrderDO order = validateOrderReceivable(userId, id); + + // 收货订单 + receiveOrder0(order); + } + + @Override + public int receiveOrderBySystem() { + // 1. 查询过期的待支付订单 + LocalDateTime expireTime = minusTime(tradeOrderProperties.getReceiveExpireTime()); + List orders = tradeOrderMapper.selectListByStatusAndDeliveryTimeLt( + TradeOrderStatusEnum.DELIVERED.getStatus(), expireTime); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行,逐个取消 + int count = 0; + for (TradeOrderDO order : orders) { + try { + getSelf().receiveOrderBySystem(order); + count++; + } catch (Throwable e) { + log.error("[receiveOrderBySystem][order({}) 自动收货订单异常]", order.getId(), e); + } + } + return count; + } + + /** + * 自动收货单个订单 + * + * @param order 订单 + */ + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_RECEIVE) + public void receiveOrderBySystem(TradeOrderDO order) { + receiveOrder0(order); + } + + /** + * 收货订单的核心实现 + * + * @param order 订单 + */ + private void receiveOrder0(TradeOrderDO order) { + // 更新 TradeOrderDO 状态为已完成 + int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getOrderStatus(), + new TradeOrderDO().setOrderStatus(TradeOrderStatusEnum.COMPLETED.getStatus()).setReceiveTime(LocalDateTime.now())); + if (updateCount == 0) { + throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED); + } + + // 插入订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), TradeOrderStatusEnum.COMPLETED.getStatus()); + } + + /** + * 校验交易订单满足可售货的条件 + *

+ * 1. 交易订单待收货 + * + * @param userId 用户编号 + * @param id 交易订单编号 + * @return 交易订单 + */ + private TradeOrderDO validateOrderReceivable(Long userId, Long id) { + // 校验订单是否存在 + TradeOrderDO order = tradeOrderMapper.selectByIdAndUserId(id, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 校验订单是否是待收货状态 + if (!TradeOrderStatusEnum.isDelivered(order.getOrderStatus())) { + throw exception(ORDER_RECEIVE_FAIL_STATUS_NOT_DELIVERED); + } + return order; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL) + public void cancelOrderByMember(Long userId, Long id) { + // 1.1 校验存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 1.2 校验状态 + if (ObjectUtil.notEqual(order.getOrderStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) { + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); + } + + // 2. 取消订单 + cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); + } + + @Override + public int cancelOrderBySystem() { + // 1. 查询过期的待支付订单 + LocalDateTime expireTime = minusTime(tradeOrderProperties.getPayExpireTime()); + List orders = tradeOrderMapper.selectListByStatusAndCreateTimeLt( + TradeOrderStatusEnum.UNPAID.getStatus(), expireTime); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行,逐个取消 + int count = 0; + for (TradeOrderDO order : orders) { + try { + getSelf().cancelOrderBySystem(order); + count++; + } catch (Throwable e) { + log.error("[cancelOrderBySystem][order({}) 过期订单异常]", order.getId(), e); + } + } + return count; + } + + /** + * 自动取消单个订单 + * + * @param order 订单 + */ + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL) + public void cancelOrderBySystem(TradeOrderDO order) { + cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT); + } + + /** + * 取消订单的核心实现 + * + * @param order 订单 + * @param cancelType 取消类型 + */ + private void cancelOrder0(TradeOrderDO order, TradeOrderCancelTypeEnum cancelType) { + // 1. 更新 TradeOrderDO 状态为已取消 + int updateCount = tradeOrderMapper.updateByIdAndStatus(order.getId(), order.getOrderStatus(), + new TradeOrderDO().setOrderStatus(TradeOrderStatusEnum.CANCELED.getStatus()) + .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now())); + if (updateCount == 0) { + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); + } + + // 2. 执行 TradeOrderHandler 的后置处理 + List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); + tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems)); + + // 3. 增加订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), TradeOrderStatusEnum.CANCELED.getStatus()); + } + + /** + * 如果金额全部被退款,则取消订单 + * 如果还有未被退款的金额,则无需取消订单 + * + * @param order 订单 + * @param refundPrice 退款金额 + */ + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_CANCEL_AFTER_SALE) + public void cancelOrderByAfterSale(TradeOrderDO order, Integer refundPrice) { + // 1. 更新订单 + if (refundPrice < order.getPayPrice()) { + return; + } + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()) + .setOrderStatus(TradeOrderStatusEnum.CANCELED.getStatus()) + .setCancelType(TradeOrderCancelTypeEnum.AFTER_SALE_CLOSE.getType()).setCancelTime(LocalDateTime.now())); + + // 2. 执行 TradeOrderHandler 的后置处理 + List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); + tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_DELETE) + public void deleteOrder(Long userId, Long id) { + // 1.1 校验存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(id, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + // 1.2 校验状态 + if (ObjectUtil.notEqual(order.getOrderStatus(), TradeOrderStatusEnum.CANCELED.getStatus())) { + throw exception(ORDER_DELETE_FAIL_STATUS_NOT_CANCEL); + } + // 2. 删除订单 + tradeOrderMapper.deleteById(id); + + // 3. 记录日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), order.getOrderStatus()); + } + + @Override + public void updateOrderRemark(TradeOrderRemarkReqVO reqVO) { + // 校验并获得交易订单 + validateOrderExists(reqVO.getId()); + + // 更新 + TradeOrderDO order = TradeOrderConvert.INSTANCE.convert(reqVO); + tradeOrderMapper.updateById(order); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_UPDATE_PRICE) + public void updateOrderPrice(TradeOrderUpdatePriceReqVO reqVO) { + // 1.1 校验交易订单 + TradeOrderDO order = validateOrderExists(reqVO.getId()); + if (order.getPayStatus()) { + throw exception(ORDER_UPDATE_PRICE_FAIL_PAID); + } + // 1.2 校验调价金额是否变化 + if (order.getAdjustPrice() > 0) { + throw exception(ORDER_UPDATE_PRICE_FAIL_ALREADY); + } + // 1.3 支付价格不能为 0 + int newPayPrice = order.getPayPrice() + reqVO.getAdjustPrice(); + if (newPayPrice <= 0) { + throw exception(ORDER_UPDATE_PRICE_FAIL_PRICE_ERROR); + } + + // 2. 更新订单 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()) + .setAdjustPrice(reqVO.getAdjustPrice() + order.getAdjustPrice()).setPayPrice(newPayPrice)); + + // 3. 更新 TradeOrderItem,需要做 adjustPrice 的分摊 + List orderOrderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); + List dividePrices = null;// TradePriceCalculatorHelper.dividePrice2(orderOrderItems, reqVO.getAdjustPrice()); + List updateItems = new ArrayList<>(); + for (int i = 0; i < orderOrderItems.size(); i++) { + TradeOrderItemDO item = orderOrderItems.get(i); + updateItems.add(new TradeOrderItemDO().setId(item.getId()) + .setAdjustPrice(item.getAdjustPrice() + dividePrices.get(i)) + .setPayPrice(item.getPayPrice() + dividePrices.get(i))); + } + tradeOrderItemMapper.updateBatch(updateItems); + + // 4. 更新支付订单 + payOrderApi.updatePayOrderPrice(order.getPayOrderId(), newPayPrice).checkError(); + + // 5. 记录订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), order.getOrderStatus(), + MapUtil.builder().put("oldPayPrice", MoneyUtils.fenToYuanStr(order.getPayPrice())) + .put("adjustPrice", MoneyUtils.fenToYuanStr(reqVO.getAdjustPrice())) + .put("newPayPrice", MoneyUtils.fenToYuanStr(newPayPrice)).build()); + } + + @Override + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_UPDATE_ADDRESS) + public void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO) { + // 校验交易订单 + TradeOrderDO order = validateOrderExists(reqVO.getId()); + // 只有待发货状态,才可以修改订单收货地址; + if (!TradeOrderStatusEnum.isUndelivered(order.getOrderStatus())) { + throw exception(ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED); + } + + // 更新 + tradeOrderMapper.updateById(TradeOrderConvert.INSTANCE.convert(reqVO)); + + // 记录订单日志 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), order.getOrderStatus()); + } + + @Override + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_PICK_UP_RECEIVE) + public void pickUpOrderByAdmin(Long userId, Long id) { + getSelf().pickUpOrder(userId, tradeOrderMapper.selectById(id)); + } + + @Override + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_PICK_UP_RECEIVE) + public void pickUpOrderByAdmin(Long userId, String pickUpVerifyCode) { + getSelf().pickUpOrder(userId, tradeOrderMapper.selectOneByPickUpVerifyCode(pickUpVerifyCode)); + } + + @Override + public TradeOrderDO getByPickUpVerifyCode(String pickUpVerifyCode) { + return tradeOrderMapper.selectOneByPickUpVerifyCode(pickUpVerifyCode); + } + + @Transactional(rollbackFor = Exception.class) + public void pickUpOrder(Long userId, TradeOrderDO order) { + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + if (ObjUtil.notEqual(DeliveryTypeEnum.PICK_UP.getType(), order.getDeliveryType())) { + throw exception(ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP); + } + DeliveryPickUpStoreDO deliveryPickUpStore = pickUpStoreService.getDeliveryPickUpStore(order.getPickUpStoreId()); + if (deliveryPickUpStore == null + || !CollUtil.contains(deliveryPickUpStore.getVerifyUserIds(), userId)) { + throw exception(ORDER_PICK_UP_FAIL_NOT_VERIFY_USER); + } + + receiveOrder0(order); + } + + // =================== Order Item =================== + + @Override + public void updateOrderItemWhenAfterSaleCreate(Long id, Long afterSaleId) { + // 更新订单项 + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), + TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), afterSaleId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) { + // 1.1 更新订单项 + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), + TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null); + // 1.2 执行 TradeOrderHandler 的后置处理 + TradeOrderItemDO orderItem = tradeOrderItemMapper.selectById(id); + TradeOrderDO order = tradeOrderMapper.selectById(orderItem.getOrderId()); + tradeOrderHandlers.forEach(handler -> handler.afterCancelOrderItem(order, orderItem)); + + // 2.1 更新订单的退款金额、积分 + Integer orderRefundPrice = order.getRefundPrice() + refundPrice; + Integer refundStatus = isAllOrderItemAfterSaleSuccess(order.getId()) ? + TradeOrderRefundStatusEnum.ALL.getStatus() // 如果都售后成功,则需要取消订单 + : TradeOrderRefundStatusEnum.PART.getStatus(); + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()) + .setRefundStatus(refundStatus)); + // 2.2 如果全部退款,则进行取消订单 + getSelf().cancelOrderByAfterSale(order, orderRefundPrice); + } + + @Override + public void updateOrderItemWhenAfterSaleCancel(Long id) { + // 更新订单项 + updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), + TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null); + } + + private void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus, + Long afterSaleId) { + // 更新订单项 + int updateCount = tradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId); + if (updateCount <= 0) { + throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL); + } + + } + + /** + * 判断指定订单的所有订单项,是不是都售后成功 + * + * @param id 订单编号 + * @return 是否都售后成功 + */ + private boolean isAllOrderItemAfterSaleSuccess(Long id) { + List orderItems = tradeOrderItemMapper.selectListByOrderId(id); + return orderItems.stream().allMatch(orderItem -> Objects.equals(1, + TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus())); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_COMMENT) + public Long createOrderItemCommentByMember(Long userId, AppTradeOrderItemCommentCreateReqVO createReqVO) { + // 1.1 先通过订单项 ID,查询订单项是否存在 + TradeOrderItemDO orderItem = tradeOrderItemMapper.selectByIdAndUserId(createReqVO.getOrderItemId(), userId); + if (orderItem == null) { + throw exception(ORDER_ITEM_NOT_FOUND); + } + // 1.2 校验订单相关状态 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderItem.getOrderId(), userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + if (ObjectUtil.notEqual(order.getOrderStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) { + throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED); + } + if (ObjectUtil.notEqual(order.getCommentStatus(), Boolean.FALSE)) { + throw exception(ORDER_COMMENT_STATUS_NOT_FALSE); + } + + // 2. 创建评价 + Long commentId = createOrderItemComment0(orderItem, createReqVO); + + // 3. 如果订单项都评论了,则更新订单评价状态 + List orderItems = tradeOrderItemMapper.selectListByOrderId(order.getId()); +// if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) { +// tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE) +// .setFinishTime(LocalDateTime.now())); +// // 增加订单日志。注意:只有在所有订单项都评价后,才会增加 +// TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), order.getOrderStatus()); +// } + return commentId; + } + + @Override + public int createOrderItemCommentBySystem() { + // 1. 查询过期的待支付订单 + LocalDateTime expireTime = minusTime(tradeOrderProperties.getCommentExpireTime()); + List orders = tradeOrderMapper.selectListByStatusAndReceiveTimeLt( + TradeOrderStatusEnum.COMPLETED.getStatus(), expireTime, false); + if (CollUtil.isEmpty(orders)) { + return 0; + } + + // 2. 遍历执行,逐个取消 + int count = 0; + for (TradeOrderDO order : orders) { + try { + getSelf().createOrderItemCommentBySystemBySystem(order); + count++; + } catch (Throwable e) { + log.error("[createOrderItemCommentBySystem][order({}) 过期订单异常]", order.getId(), e); + } + } + return count; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateOrderCombinationInfo(Long orderId, Long activityId, Long combinationRecordId, Long headId) { + tradeOrderMapper.updateById( + new TradeOrderDO().setId(orderId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelPaidOrder(Long userId, Long orderId, Integer cancelType) { + // 1.1 这里校验下 cancelType 只允许拼团关闭; + if (ObjUtil.notEqual(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getType(), cancelType)) { + return; + } + // 1.2 检验订单存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + + // 1.3 校验订单是否支付 + if (!order.getPayStatus()) { + throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); + } + // 1.3 校验订单是否未退款 + if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { + throw exception(ORDER_CANCEL_PAID_FAIL, "未退款"); + } + + // 2.1 取消订单 + cancelOrder0(order, TradeOrderCancelTypeEnum.COMBINATION_CLOSE); + // 2.2 创建退款单 + payRefundApi.createRefund(new PayRefundCreateReqDTO() + .setAppKey(tradeOrderProperties.getPayAppKey()) // 支付应用 + .setUserIp(NetUtil.getLocalhostStr()) // 使用本机 IP,因为是服务器发起退款的 + .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 + .setMerchantRefundId(String.valueOf(order.getId())) + .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())).checkError(); // 价格信息 + } + + @Override + public void updateOrderGiveCouponIds(Long userId, Long orderId, List giveCouponIds) { + // 1. 检验订单存在 + TradeOrderDO order = tradeOrderMapper.selectOrderByIdAndUserId(orderId, userId); + if (order == null) { + throw exception(ORDER_NOT_FOUND); + } + + } + + /** + * 创建单个订单的评论 + * + * @param order 订单 + */ + @Transactional(rollbackFor = Exception.class) + @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT) + public void createOrderItemCommentBySystemBySystem(TradeOrderDO order) { + // 1. 查询未评论的订单项 + List orderItems = tradeOrderItemMapper.selectListByOrderIdAndCommentStatus( + order.getId(), Boolean.FALSE); + if (CollUtil.isEmpty(orderItems)) { + return; + } + + // 2. 逐个评论 + for (TradeOrderItemDO orderItem : orderItems) { + // 2.1 创建评价 + AppTradeOrderItemCommentCreateReqVO commentCreateReqVO = new AppTradeOrderItemCommentCreateReqVO() + .setOrderItemId(orderItem.getId()).setAnonymous(false).setContent("") + .setBenefitScores(5).setDescriptionScores(5); + createOrderItemComment0(orderItem, commentCreateReqVO); + + // 2.2 更新订单项评价状态 + tradeOrderItemMapper.updateById(new TradeOrderItemDO().setId(orderItem.getId())); + } + + // 3. 所有订单项都评论了,则更新订单评价状态 + tradeOrderMapper.updateById(new TradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE) + .setFinishTime(LocalDateTime.now())); + // 增加订单日志。注意:只有在所有订单项都评价后,才会增加 + TradeOrderLogUtils.setOrderInfo(order.getId(), order.getOrderStatus(), order.getOrderStatus()); + } + + /** + * 创建订单项的评论的核心实现 + * + * @param orderItem 订单项 + * @param createReqVO 评论内容 + * @return 评论编号 + */ + private Long createOrderItemComment0(TradeOrderItemDO orderItem, AppTradeOrderItemCommentCreateReqVO createReqVO) { + // 1. 创建评价 +// ProductCommentCreateReqDTO productCommentCreateReqDTO = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItem); +// Long commentId = productCommentApi.createComment(productCommentCreateReqDTO).getCheckedData(); + + // 2. 更新订单项评价状态 + tradeOrderItemMapper.updateById(new TradeOrderItemDO().setId(orderItem.getId())); + return 0l;//commentId; + } + + // =================== 营销相关的操作 =================== + + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private TradeOrderUpdateServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/bo/TradeOrderLogCreateReqBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/bo/TradeOrderLogCreateReqBO.java new file mode 100644 index 0000000..327ef5a --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/bo/TradeOrderLogCreateReqBO.java @@ -0,0 +1,53 @@ +package com.tashow.cloud.trade.service.order.bo; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * 订单日志的创建 Request BO + * + * @author 陈賝 + * @since 2023/7/6 15:27 + */ +@Data +public class TradeOrderLogCreateReqBO { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 订单编号 + */ + @NotNull(message = "订单编号不能为空") + private Long orderId; + /** + * 操作前状态 + */ + private Integer beforeStatus; + /** + * 操作后状态 + */ + @NotNull(message = "操作后的状态不能为空") + private Integer afterStatus; + + /** + * 操作类型 + */ + @NotNull(message = "操作类型不能为空") + private Integer operateType; + /** + * 操作明细 + */ + @NotEmpty(message = "操作明细不能为空") + private String content; + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/handler/TradeOrderHandler.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/handler/TradeOrderHandler.java new file mode 100644 index 0000000..89e3798 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/handler/TradeOrderHandler.java @@ -0,0 +1,79 @@ +package com.tashow.cloud.trade.service.order.handler; + + +import com.tashow.cloud.common.util.collection.CollectionUtils; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import com.tashow.cloud.tradeapi.enums.order.TradeOrderItemAfterSaleStatusEnum; + +import java.util.List; + +/** + * 订单活动特殊逻辑处理器 handler 接口 + * 提供订单生命周期钩子接口;订单创建前、订单创建后、订单支付后、订单取消 + * + * @author HUIHUI + */ +public interface TradeOrderHandler { + + /** + * 订单创建前 + * + * @param order 订单 + * @param orderItems 订单项 + */ + default void beforeOrderCreate(TradeOrderDO order, List orderItems) {} + + /** + * 订单创建后 + * + * @param order 订单 + * @param orderItems 订单项 + */ + default void afterOrderCreate(TradeOrderDO order, List orderItems) {} + + /** + * 支付订单后 + * + * @param order 订单 + * @param orderItems 订单项 + */ + default void afterPayOrder(TradeOrderDO order, List orderItems) {} + + /** + * 订单取消后 + * + * @param order 订单 + * @param orderItems 订单项 + */ + default void afterCancelOrder(TradeOrderDO order, List orderItems) {} + + /** + * 订单项取消后 + * + * @param order 订单 + * @param orderItem 订单项 + */ + default void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) {} + + /** + * 订单发货前 + * + * @param order 订单 + */ + default void beforeDeliveryOrder(TradeOrderDO order) {} + + // ========== 公用方法 ========== + + /** + * 过滤“未售后”的订单项列表 + * + * @param orderItems 订单项列表 + * @return 过滤后的订单项列表 + */ + default List filterOrderItemListByNoneAfterSale(List orderItems) { + return CollectionUtils.filterList(orderItems, + item -> TradeOrderItemAfterSaleStatusEnum.isNone(1)); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/handler/TradeProductSkuOrderHandler.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/handler/TradeProductSkuOrderHandler.java new file mode 100644 index 0000000..12829d5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/order/handler/TradeProductSkuOrderHandler.java @@ -0,0 +1,41 @@ +package com.tashow.cloud.trade.service.order.handler; + +import cn.hutool.core.collection.CollUtil; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderDO; +import com.tashow.cloud.trade.dal.dataobject.order.TradeOrderItemDO; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 商品 SKU 库存的 {@link TradeOrderHandler} 实现类 + * + * @author 芋道源码 + */ +@Component +public class TradeProductSkuOrderHandler implements TradeOrderHandler { + +// @Resource +// private ProductSkuApi productSkuApi; + + @Override + public void beforeOrderCreate(TradeOrderDO order, List orderItems) { +// productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convertNegative(orderItems)).checkError(); + } + + @Override + public void afterCancelOrder(TradeOrderDO order, List orderItems) { + // 售后的订单项,已经在 afterCancelOrderItem 回滚库存,所以这里不需要重复回滚 + orderItems = filterOrderItemListByNoneAfterSale(orderItems); + if (CollUtil.isEmpty(orderItems)) { + return; + } +// productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(orderItems)).checkError(); + } + + @Override + public void afterCancelOrderItem(TradeOrderDO order, TradeOrderItemDO orderItem) { +// productSkuApi.updateSkuStock(TradeOrderConvert.INSTANCE.convert(singletonList(orderItem))).checkError(); + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/TradePriceService.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/TradePriceService.java new file mode 100644 index 0000000..232e479 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/TradePriceService.java @@ -0,0 +1,34 @@ +package com.tashow.cloud.trade.service.price; + +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeProductSettlementRespVO; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateReqBO; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateRespBO; +import jakarta.validation.Valid; + +import java.util.List; + +/** + * 价格计算 Service 接口 + * + * @author 芋道源码 + */ +public interface TradePriceService { + + /** + * 【订单】价格计算 + * + * @param calculateReqDTO 计算信息 + * @return 计算结果 + */ + TradePriceCalculateRespBO calculateOrderPrice(@Valid TradePriceCalculateReqBO calculateReqDTO); + + /** + * 【商品】价格计算,用于商品列表、商品详情 + * + * @param userId 用户编号,允许为空 + * @param spuIds 商品 SPU 编号数组 + * @return 计算结果 + */ + List calculateProductPrice(Long userId, List spuIds); + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/TradePriceServiceImpl.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/TradePriceServiceImpl.java new file mode 100644 index 0000000..c981cdb --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/TradePriceServiceImpl.java @@ -0,0 +1,65 @@ +package com.tashow.cloud.trade.service.price; + +import com.tashow.cloud.trade.controller.app.order.vo.AppTradeProductSettlementRespVO; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateReqBO; +import com.tashow.cloud.trade.service.price.bo.TradePriceCalculateRespBO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +/** + * 价格计算 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class TradePriceServiceImpl implements TradePriceService { + +// @Resource +// private ProductSkuApi productSkuApi; +// @Resource +// private ProductSpuApi productSpuApi; +// @Resource +// private DiscountActivityApi discountActivityApi; +// @Resource +// private RewardActivityApi rewardActivityApi; +// +// @Resource +// private List priceCalculators; +// +// @Resource +// private TradeDiscountActivityPriceCalculator discountActivityPriceCalculator; + + @Override + public TradePriceCalculateRespBO calculateOrderPrice(TradePriceCalculateReqBO calculateReqBO) { +// // 1.1 获得商品 SKU 数组 +// List skuList = checkSkuList(calculateReqBO); +// // 1.2 获得商品 SPU 数组 +// List spuList = checkSpuList(skuList); +// +// // 2.1 计算价格 +// TradePriceCalculateRespBO calculateRespBO = TradePriceCalculatorHelper +// .buildCalculateResp(calculateReqBO, spuList, skuList); +// priceCalculators.forEach(calculator -> calculator.calculate(calculateReqBO, calculateRespBO)); +// // 2.2 如果最终支付金额小于等于 0,则抛出业务异常 +// if (calculateReqBO.getPointActivityId() == null // 积分订单,允许支付金额为 0 +// && calculateRespBO.getPrice().getPayPrice() <= 0) { +// log.error("[calculatePrice][价格计算不正确,请求 calculateReqDTO({}),结果 priceCalculate({})]", +// calculateReqBO, calculateRespBO); +// throw exception(PRICE_CALCULATE_PAY_PRICE_ILLEGAL); +// } + return null; + } + + + + @Override + public List calculateProductPrice(Long userId, List spuIds) { + return null; + } + +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/bo/TradePriceCalculateReqBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/bo/TradePriceCalculateReqBO.java new file mode 100644 index 0000000..a201343 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/bo/TradePriceCalculateReqBO.java @@ -0,0 +1,125 @@ +package com.tashow.cloud.trade.service.price.bo; + +import com.tashow.cloud.tradeapi.enums.delivery.DeliveryTypeEnum; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 价格计算 Request BO + * + * @author yudao源码 + */ +@Data +public class TradePriceCalculateReqBO { + + /** + * 用户编号 + * + * 对应 MemberUserDO 的 id 编号 + */ + private Long userId; + + /** + * 优惠劵编号 + * + * 对应 CouponDO 的 id 编号 + */ + private Long couponId; + + /** + * 是否使用积分 + */ + @NotNull(message = "是否使用积分不能为空") + private Boolean pointStatus; + + /** + * 配送方式 + * + * 枚举 {@link DeliveryTypeEnum} + */ + private Integer deliveryType; + /** + * 收货地址编号 + * + * 对应 MemberAddressDO 的 id 编号 + */ + private Long addressId; + /** + * 自提门店编号 + * + * 对应 PickUpStoreDO 的 id 编号 + */ + private Long pickUpStoreId; + + /** + * 商品 SKU 数组 + */ + @NotNull(message = "商品数组不能为空") + private List items; + + // ========== 秒杀活动相关字段 ========== + /** + * 秒杀活动编号 + */ + private Long seckillActivityId; + + // ========== 拼团活动相关字段 ========== + /** + * 拼团活动编号 + */ + private Long combinationActivityId; + + /** + * 拼团团长编号 + */ + private Long combinationHeadId; + + // ========== 砍价活动相关字段 ========== + /** + * 砍价记录编号 + */ + private Long bargainRecordId; + + // ========== 积分商城活动相关字段 ========== + /** + * 积分商城活动编号 + */ + private Long pointActivityId; + + /** + * 商品 SKU + */ + @Data + @Valid + public static class Item { + + /** + * SKU 编号 + */ + @NotNull(message = "商品 SKU 编号不能为空") + private Long skuId; + + /** + * SKU 数量 + */ + @NotNull(message = "商品 SKU 数量不能为空") + @Min(value = 0L, message = "商品 SKU 数量必须大于等于 0") + private Integer count; + + /** + * 购物车项的编号 + */ + private Long cartId; + + /** + * 是否选中 + */ + @NotNull(message = "是否选中不能为空") + private Boolean selected; + + } +} diff --git a/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/bo/TradePriceCalculateRespBO.java b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/bo/TradePriceCalculateRespBO.java new file mode 100644 index 0000000..b4d836c --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/java/com/tashow/cloud/trade/service/price/bo/TradePriceCalculateRespBO.java @@ -0,0 +1,403 @@ +package com.tashow.cloud.trade.service.price.bo; + +import com.tashow.cloud.tradeapi.enums.order.TradeOrderTypeEnum; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 价格计算 Response BO + * + * 整体设计,参考 taobao 的技术文档: + * 1. 订单管理 + * 2. 常用订单金额说明 + * + * @author 芋道源码 + */ +@Data +public class TradePriceCalculateRespBO { + + /** + * 订单类型 + * + * 枚举 {@link TradeOrderTypeEnum} + */ + private Integer type; + + /** + * 订单价格 + */ + private Price price; + + /** + * 订单项数组 + */ + private List items; + + /** + * 营销活动数组 + * + * 只对应 {@link Price#items} 商品匹配的活动 + */ + private List promotions; + + /** + * 使用的优惠劵编号 + */ + private Long couponId; + /** + * 用户的优惠劵列表(可用 + 不可用) + */ + private List coupons; + + /** + * 会员剩余积分 + */ + private Integer totalPoint; + /** + * 使用的积分 + */ + private Integer usePoint; + + /** + * 赠送的积分 + */ + private Integer givePoint; + + /** + * 砍价活动编号 + */ + private Long bargainActivityId; + + /** + * 是否包邮 + */ + private Boolean freeDelivery; + + /** + * 赠送的优惠劵 + * + * key: 优惠劵模版编号 + * value:对应的优惠券数量 + * + * 目的:用于订单支付后赠送优惠券 + */ + private Map giveCouponTemplateCounts; + + /** + * 订单价格 + */ + @Data + public static class Price { + + /** + * 商品原价(总),单位:分 + * + * 基于 {@link OrderItem#getPrice()} * {@link OrderItem#getCount()} 求和 + * + * 对应 taobao 的 trade.total_fee 字段 + */ + private Integer totalPrice; + /** + * 订单优惠(总),单位:分 + * + * 对应 taobao 的 order.discount_fee 字段 + */ + private Integer discountPrice; + /** + * 运费金额,单位:分 + */ + private Integer deliveryPrice; + /** + * 优惠劵减免金额(总),单位:分 + * + * 对应 taobao 的 trade.coupon_fee 字段 + */ + private Integer couponPrice; + /** + * 积分抵扣的金额,单位:分 + * + * 对应 taobao 的 trade.point_fee 字段 + */ + private Integer pointPrice; + /** + * VIP 减免金额,单位:分 + */ + private Integer vipPrice; + /** + * 最终购买金额(总),单位:分 + * + * = {@link #totalPrice} + * - {@link #couponPrice} + * - {@link #pointPrice} + * - {@link #discountPrice} + * + {@link #deliveryPrice} + * - {@link #vipPrice} + */ + private Integer payPrice; + + } + + /** + * 订单商品 SKU + */ + @Data + public static class OrderItem { + + /** + * SPU 编号 + */ + private Long spuId; + /** + * SKU 编号 + */ + private Long skuId; + /** + * 购买数量 + */ + private Integer count; + /** + * 购物车项的编号 + */ + private Long cartId; + /** + * 是否选中 + */ + private Boolean selected; + + /** + * 商品原价(单),单位:分 + * + * 对应 ProductSkuDO 的 price 字段 + * 对应 taobao 的 order.price 字段 + */ + private Integer price; + /** + * 优惠金额(总),单位:分 + * + * 对应 taobao 的 order.discount_fee 字段 + */ + private Integer discountPrice; + /** + * 运费金额(总),单位:分 + */ + private Integer deliveryPrice; + /** + * 优惠劵减免金额,单位:分 + * + * 对应 taobao 的 trade.coupon_fee 字段 + */ + private Integer couponPrice; + /** + * 积分抵扣的金额,单位:分 + * + * 对应 taobao 的 trade.point_fee 字段 + */ + private Integer pointPrice; + /** + * 使用的积分 + */ + private Integer usePoint; + /** + * VIP 减免金额,单位:分 + */ + private Integer vipPrice; + /** + * 应付金额(总),单位:分 + * + * = {@link #price} * {@link #count} + * - {@link #couponPrice} + * - {@link #pointPrice} + * - {@link #discountPrice} + * + {@link #deliveryPrice} + * - {@link #vipPrice} + */ + private Integer payPrice; + + // ========== 商品 SPU 信息 ========== + /** + * 商品名 + */ + private String spuName; + /** + * 商品图片 + * + * 优先级:SKU.picUrl > SPU.picUrl + */ + private String picUrl; + /** + * 分类编号 + */ + private Long categoryId; + + // ========== 物流相关字段 ========= + + /** + * 配送方式数组 + * + * 对应 DeliveryTypeEnum 枚举 + */ + private List deliveryTypes; + + /** + * 物流配置模板编号 + * + * 对应 TradeDeliveryExpressTemplateDO 的 id 编号 + */ + private Long deliveryTemplateId; + + // ========== 商品 SKU 信息 ========== + /** + * 商品重量,单位:kg 千克 + */ + private Double weight; + /** + * 商品体积,单位:m^3 平米 + */ + private Double volume; + +// /** +// * 商品属性数组 +// */ +// private List properties; + + /** + * 赠送的积分 + */ + private Integer givePoint; + + } + + /** + * 营销明细 + */ + @Data + public static class Promotion { + + /** + * 营销编号 + * + * 例如说:营销活动的编号、优惠劵的编号 + */ + private Long id; + /** + * 营销名字 + */ + private String name; + /** + * 营销类型 + * + * 枚举 {@link PromotionTypeEnum} + */ + private Integer type; + /** + * 计算时的原价(总),单位:分 + */ + private Integer totalPrice; + /** + * 计算时的优惠(总),单位:分 + */ + private Integer discountPrice; + /** + * 匹配的商品 SKU 数组 + */ + private List items; + + // ========== 匹配情况 ========== + + /** + * 是否满足优惠条件 + */ + private Boolean match; + /** + * 满足条件的提示 + * + * 如果 {@link #match} = true 满足,则提示“圣诞价:省 150.00 元” + * 如果 {@link #match} = false 不满足,则提示“购满 85 元,可减 40 元” + */ + private String description; + + } + + /** + * 营销匹配的商品 SKU + */ + @Data + public static class PromotionItem { + + /** + * 商品 SKU 编号 + */ + private Long skuId; + /** + * 计算时的原价(总),单位:分 + */ + private Integer totalPrice; + /** + * 计算时的优惠(总),单位:分 + */ + private Integer discountPrice; + + } + + /** + * 优惠劵信息 + */ + @Data + public static class Coupon { + + /** + * 优惠劵编号 + */ + private Long id; + /** + * 优惠劵名 + */ + private String name; + + /** + * 是否设置满多少金额可用,单位:分 + */ + private Integer usePrice; + + /** + * 生效开始时间 + */ + private LocalDateTime validStartTime; + /** + * 生效结束时间 + */ + private LocalDateTime validEndTime; + + /** + * 优惠类型 + */ + private Integer discountType; + /** + * 折扣百分比 + */ + private Integer discountPercent; + /** + * 优惠金额,单位:分 + */ + private Integer discountPrice; + /** + * 折扣上限,单位:分 + */ + private Integer discountLimitPrice; + + /** + * 是否匹配 + */ + private Boolean match; + /** + * 不匹配的原因 + */ + private String mismatchReason; + + } + + +} diff --git a/tashow-module/tashow-module-trade/src/main/resources/application-local.yaml b/tashow-module/tashow-module-trade/src/main/resources/application-local.yaml new file mode 100644 index 0000000..e3f26db --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/resources/application-local.yaml @@ -0,0 +1,14 @@ +spring: + cloud: + nacos: + server-addr: 43.139.42.137:8848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: nacos # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 + config: # 【注册中心】配置项 + namespace: dev # 命名空间。这里使用 dev 开发环境 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP diff --git a/tashow-module/tashow-module-trade/src/main/resources/application.yaml b/tashow-module/tashow-module-trade/src/main/resources/application.yaml new file mode 100644 index 0000000..0491d71 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/resources/application.yaml @@ -0,0 +1,12 @@ +server: + port: 48085 +spring: + application: + name: trade-server + profiles: + active: local + config: + import: + - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 + - optional:nacos:application.yaml # 加载【本地】配置 + - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/main/resources/logback-spring.xml b/tashow-module/tashow-module-trade/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..8c6b0f7 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/main/resources/logback-spring.xml @@ -0,0 +1,76 @@ + + + + + + + + + +       + + + ${PATTERN_DEFAULT} + + + + + + + + + + ${PATTERN_DEFAULT} + + + + ${LOG_FILE} + + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} + + + + + + 0 + + 256 + + + + + + + + ${PATTERN_DEFAULT} + + + + + + + + + + + + + + + + + + + + + + diff --git a/tashow-module/tashow-module-trade/src/test/resources/application-unit-test.yaml b/tashow-module/tashow-module-trade/src/test/resources/application-unit-test.yaml new file mode 100644 index 0000000..ab50eb5 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/test/resources/application-unit-test.yaml @@ -0,0 +1,60 @@ +spring: + main: + lazy-initialization: true # 开启懒加载,加快速度 + banner-mode: off # 单元测试,禁用 Banner + +--- #################### 数据库相关配置 #################### + +spring: + # 数据源配置项 + datasource: + name: ruoyi-vue-pro + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 + driver-class-name: org.h2.Driver + username: sa + password: + druid: + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 + initial-size: 1 # 单元测试,配置为 1,提升启动速度 + sql: + init: + schema-locations: classpath:/sql/create_tables.sql + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 16379 # 端口(单元测试,使用 16379 端口) + database: 0 # 数据库索引 + +mybatis: + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 + +--- #################### 定时任务相关配置 #################### + +--- #################### 配置中心相关配置 #################### + +--- #################### 服务保障相关配置 #################### + +# Lock4j 配置项(单元测试,禁用 Lock4j) + +--- #################### 监控相关配置 #################### + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + info: + base-package: cn.iocoder.yudao.module + trade: + order: + app-id: 1 + merchant-order-id: 1 + express: + kd-niao: + api-key: xxxx + business-id: xxxxx + kd100: + customer: xxxxx + key: xxxxx + client: not_provide \ No newline at end of file diff --git a/tashow-module/tashow-module-trade/src/test/resources/logback.xml b/tashow-module/tashow-module-trade/src/test/resources/logback.xml new file mode 100644 index 0000000..daf756b --- /dev/null +++ b/tashow-module/tashow-module-trade/src/test/resources/logback.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tashow-module/tashow-module-trade/src/test/resources/sql/clean.sql b/tashow-module/tashow-module-trade/src/test/resources/sql/clean.sql new file mode 100644 index 0000000..f7f3477 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/test/resources/sql/clean.sql @@ -0,0 +1,7 @@ +DELETE FROM trade_order; +DELETE FROM trade_order_item; +DELETE FROM trade_after_sale; +DELETE FROM trade_after_sale_log; +DELETE FROM trade_brokerage_user; +DELETE FROM trade_brokerage_record; +DELETE FROM "trade_brokerage_withdraw"; diff --git a/tashow-module/tashow-module-trade/src/test/resources/sql/create_tables.sql b/tashow-module/tashow-module-trade/src/test/resources/sql/create_tables.sql new file mode 100644 index 0000000..1d7ed24 --- /dev/null +++ b/tashow-module/tashow-module-trade/src/test/resources/sql/create_tables.sql @@ -0,0 +1,235 @@ +CREATE TABLE IF NOT EXISTS "trade_order" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar NOT NULL, + "type" int NOT NULL, + "terminal" int NOT NULL, + "user_id" bigint NOT NULL, + "user_ip" varchar NOT NULL, + "user_remark" varchar, + "status" int NOT NULL, + "product_count" int NOT NULL, + "cancel_type" int, + "remark" varchar, + "comment_status" boolean, + "brokerage_user_id" bigint, + "pay_status" bit NOT NULL, + "pay_time" datetime, + "finish_time" datetime, + "cancel_time" datetime, + "total_price" int NULL, + "order_price" int NULL, + "discount_price" int NOT NULL, + "delivery_price" int NOT NULL, + "adjust_price" int NOT NULL, + "pay_price" int NOT NULL, + "delivery_type" int NOT NULL, + "pay_order_id" bigint, + "pay_channel_code" varchar, + "delivery_template_id" bigint, + "logistics_id" bigint, + "logistics_no" varchar, + "delivery_time" datetime, + "receive_time" datetime, + "receiver_name" varchar NOT NULL, + "receiver_mobile" varchar NOT NULL, + "receiver_area_id" int NOT NULL, + "receiver_post_code" int, + "receiver_detail_address" varchar NOT NULL, + "pick_up_store_id" long NULL, + "pick_up_verify_code" varchar NULL, + "refund_status" int NULL, + "refund_price" int NULL, + "after_sale_status" int NULL, + "coupon_id" bigint NOT NULL, + "coupon_price" int NOT NULL, + "use_point" int NULL, + "point_price" int NOT NULL, + "give_point" int NULL, + "refund_point" int NULL, + "vip_price" int NULL, + "give_coupons_map" varchar NULL, + "seckill_activity_id" long NULL, + "bargain_activity_id" long NULL, + "bargain_record_id" long NULL, + "combination_activity_id" long NULL, + "combination_head_id" long NULL, + "combination_record_id" long NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '交易订单表'; + +CREATE TABLE IF NOT EXISTS "trade_order_item" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "order_id" bigint NOT NULL, + "cart_id" int NULL, + "spu_id" bigint NOT NULL, + "spu_name" varchar NOT NULL, + "sku_id" bigint NOT NULL, + "properties" varchar, + "pic_url" varchar, + "count" int NOT NULL, + "comment_status" boolean NULL, + "price" int NOT NULL, + "discount_price" int NOT NULL, + "delivery_price" int NULL, + "adjust_price" int NULL, + "pay_price" int NOT NULL, + "coupon_price" int NULL, + "point_price" int NULL, + "use_point" int NULL, + "give_point" int NULL, + "vip_price" int NULL, + "after_sale_id" long NULL, + "after_sale_status" int NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '交易订单明细表'; + +CREATE TABLE IF NOT EXISTS "trade_after_sale" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar NOT NULL, + "status" int NOT NULL, + "type" int NOT NULL, + "way" int NOT NULL, + "user_id" bigint NOT NULL, + "apply_reason" varchar NOT NULL, + "apply_description" varchar, + "apply_pic_urls" varchar, + "order_id" bigint NOT NULL, + "order_no" varchar NOT NULL, + "order_item_id" bigint NOT NULL, + "spu_id" bigint NOT NULL, + "spu_name" varchar NOT NULL, + "sku_id" bigint NOT NULL, + "properties" varchar, + "pic_url" varchar, + "count" int NOT NULL, + "audit_time" varchar, + "audit_user_id" bigint, + "audit_reason" varchar, + "refund_price" int NOT NULL, + "pay_refund_id" bigint, + "refund_time" varchar, + "logistics_id" bigint, + "logistics_no" varchar, + "delivery_time" varchar, + "receive_time" varchar, + "receive_reason" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '交易售后表'; + +CREATE TABLE IF NOT EXISTS "trade_after_sale_log" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "user_type" int NOT NULL, + "after_sale_id" bigint NOT NULL, + "order_id" bigint NOT NULL, + "order_item_id" bigint NOT NULL, + "before_status" int, + "after_status" int NOT NULL, + "content" varchar NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '交易售后日志'; + +CREATE TABLE IF NOT EXISTS "trade_brokerage_user" +( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "bind_user_id" bigint NOT NULL, + "bind_user_time" varchar, + "brokerage_enabled" bit NOT NULL, + "brokerage_time" varchar, + "price" int NOT NULL, + "frozen_price" int NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT '0', + PRIMARY KEY ("id") +) COMMENT '分销用户'; +CREATE TABLE IF NOT EXISTS "trade_brokerage_record" +( + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "biz_id" varchar NOT NULL, + "biz_type" varchar NOT NULL, + "title" varchar NOT NULL, + "price" int NOT NULL, + "total_price" int NOT NULL, + "description" varchar NOT NULL, + "status" varchar NOT NULL, + "frozen_days" int NOT NULL, + "unfreeze_time" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT '佣金记录'; +CREATE TABLE IF NOT EXISTS "trade_brokerage_withdraw" +( + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "user_id" bigint NOT NULL, + "price" int NOT NULL, + "fee_price" int NOT NULL, + "total_price" int NOT NULL, + "type" varchar NOT NULL, + "name" varchar, + "account_no" varchar, + "bank_name" varchar, + "bank_address" varchar, + "account_qr_code_url" varchar, + "status" varchar NOT NULL, + "audit_reason" varchar, + "audit_time" varchar, + "remark" varchar, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', + PRIMARY KEY ("id") +) COMMENT '佣金提现'; + +CREATE TABLE IF NOT EXISTS "trade_delivery_express" +( + "id" int NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar NULL, + "name" varchar, + "logo" varchar NULL, + "sort" int NOT NULL, + "status" int NOT NULL, + "creator" varchar DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '佣金提现'; \ No newline at end of file diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java index 57a1093..96888da 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/PayClientFactory.java @@ -1,7 +1,7 @@ package com.tashow.cloud.sdk.payment.client; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; /** * 支付客户端的工厂接口 @@ -33,6 +33,6 @@ public interface PayClientFactory { * @param channel 支付渠道的编码的枚举 * @param payClientClass 支付客户端 class */ - void registerPayClientClass(PayChannelEnum channel, Class payClientClass); + void registerPayClientClass(PayTypeEnum channel, Class payClientClass); } diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java index cf29aed..4f4e69e 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/PayClientFactoryImpl.java @@ -7,14 +7,14 @@ import com.tashow.cloud.sdk.payment.client.PayClientConfig; import com.tashow.cloud.sdk.payment.client.PayClientFactory; import com.tashow.cloud.sdk.payment.client.impl.alipay.*; import com.tashow.cloud.sdk.payment.client.impl.weixin.*; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import lombok.extern.slf4j.Slf4j; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import static com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum.*; +import static com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum.*; /** @@ -35,7 +35,7 @@ public class PayClientFactoryImpl implements PayClientFactory { /** * 支付客户端 Class Map */ - private final Map> clientClass = new ConcurrentHashMap<>(); + private final Map> clientClass = new ConcurrentHashMap<>(); public PayClientFactoryImpl() { // 微信支付客户端 @@ -56,7 +56,7 @@ public class PayClientFactoryImpl implements PayClientFactory { } @Override - public void registerPayClientClass(PayChannelEnum channel, Class payClientClass) { + public void registerPayClientClass(PayTypeEnum channel, Class payClientClass) { clientClass.put(channel, payClientClass); } @@ -87,7 +87,7 @@ public class PayClientFactoryImpl implements PayClientFactory { @SuppressWarnings("unchecked") private AbstractPayClient createPayClient(Long channelId, String channelCode, Config config) { - PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode); + PayTypeEnum channelEnum = PayTypeEnum.getByCode(channelCode); Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode)); Class payClientClass = clientClass.get(channelEnum); Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode)); diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java index 1067ab8..f8738b2 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayAppPayClient.java @@ -6,7 +6,7 @@ import com.alipay.api.request.AlipayTradeAppPayRequest; import com.alipay.api.response.AlipayTradeAppPayResponse; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,7 @@ import lombok.extern.slf4j.Slf4j; public class AlipayAppPayClient extends AbstractAlipayPayClient { public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config); + super(channelId, PayTypeEnum.ALIPAY_APP.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java index 3466dd2..a4fb081 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayBarPayClient.java @@ -9,7 +9,7 @@ import com.alipay.api.request.AlipayTradePayRequest; import com.alipay.api.response.AlipayTradePayResponse; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -32,7 +32,7 @@ import static com.tashow.cloud.sdk.payment.client.impl.alipay.AlipayPayClientCon public class AlipayBarPayClient extends AbstractAlipayPayClient { public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config); + super(channelId, PayTypeEnum.ALIPAY_BAR.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java index 0e45d45..1cdefed 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayPcPayClient.java @@ -8,7 +8,7 @@ import com.alipay.api.request.AlipayTradePagePayRequest; import com.alipay.api.response.AlipayTradePagePayResponse; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -25,7 +25,7 @@ import java.util.Objects; public class AlipayPcPayClient extends AbstractAlipayPayClient { public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config); + super(channelId, PayTypeEnum.ALIPAY_PC.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java index 9995ef4..449993c 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayQrPayClient.java @@ -6,7 +6,7 @@ import com.alipay.api.request.AlipayTradePrecreateRequest; import com.alipay.api.response.AlipayTradePrecreateResponse; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -26,7 +26,7 @@ import static com.tashow.cloud.sdk.payment.client.impl.alipay.AlipayPayClientCon public class AlipayQrPayClient extends AbstractAlipayPayClient { public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config); + super(channelId, PayTypeEnum.ALIPAY_QR.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java index 560fdd8..3832d7a 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/alipay/AlipayWapPayClient.java @@ -7,7 +7,7 @@ import com.alipay.api.request.AlipayTradeWapPayRequest; import com.alipay.api.response.AlipayTradeWapPayResponse; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j; public class AlipayWapPayClient extends AbstractAlipayPayClient { public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { - super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config); + super(channelId, PayTypeEnum.ALIPAY_WAP.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java index aa0eb68..c3c7d2b 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxAppPayClient.java @@ -9,7 +9,7 @@ import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -29,7 +29,7 @@ import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; public class WxAppPayClient extends AbstractWxPayClient { public WxAppPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_APP.getCode(), config); + super(channelId, PayTypeEnum.WX_APP.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java index 21c17e8..8200fc2 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxBarPayClient.java @@ -10,7 +10,7 @@ import com.github.binarywang.wxpay.exception.WxPayException; import com.tashow.cloud.common.util.date.LocalDateTimeUtils; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -38,7 +38,7 @@ public class WxBarPayClient extends AbstractWxPayClient { private static final Duration AUTH_CODE_EXPIRE = Duration.ofMinutes(3); public WxBarPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_BAR.getCode(), config); + super(channelId, PayTypeEnum.WX_BAR.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java index 0f764fb..3e38f0f 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxLitePayClient.java @@ -1,6 +1,6 @@ package com.tashow.cloud.sdk.payment.client.impl.weixin; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import lombok.extern.slf4j.Slf4j; /** @@ -16,7 +16,7 @@ import lombok.extern.slf4j.Slf4j; public class WxLitePayClient extends WxPubPayClient { public WxLitePayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_LITE.getCode(), config); + super(channelId, PayTypeEnum.WX_LITE.getCode(), config); } } diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java index 55423fc..80a8b70 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxNativePayClient.java @@ -8,7 +8,7 @@ import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,7 @@ import lombok.extern.slf4j.Slf4j; public class WxNativePayClient extends AbstractWxPayClient { public WxNativePayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config); + super(channelId, PayTypeEnum.WX_NATIVE.getCode(), config); } @Override diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java index d694052..5193825 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxPubPayClient.java @@ -11,7 +11,7 @@ import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -30,7 +30,7 @@ import static com.tashow.cloud.common.util.json.JsonUtils.toJsonString; public class WxPubPayClient extends AbstractWxPayClient { public WxPubPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_PUB.getCode(), config); + super(channelId, PayTypeEnum.WX_PUB.getCode(), config); } protected WxPubPayClient(Long channelId, String channelCode, WxPayClientConfig config) { diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java index 731f377..3e08248 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/client/impl/weixin/WxWapPayClient.java @@ -8,7 +8,7 @@ import com.github.binarywang.wxpay.constant.WxPayConstants; import com.github.binarywang.wxpay.exception.WxPayException; import com.tashow.cloud.sdk.payment.dto.order.PayOrderRespDTO; import com.tashow.cloud.sdk.payment.dto.order.PayOrderUnifiedReqDTO; -import com.tashow.cloud.sdk.payment.enums.channel.PayChannelEnum; +import com.tashow.cloud.sdk.payment.enums.channel.PayTypeEnum; import com.tashow.cloud.sdk.payment.enums.order.PayOrderDisplayModeEnum; import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,7 @@ import lombok.extern.slf4j.Slf4j; public class WxWapPayClient extends AbstractWxPayClient { public WxWapPayClient(Long channelId, WxPayClientConfig config) { - super(channelId, PayChannelEnum.WX_WAP.getCode(), config); + super(channelId, PayTypeEnum.WX_WAP.getCode(), config); } protected WxWapPayClient(Long channelId, String channelCode, WxPayClientConfig config) { diff --git a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayChannelEnum.java b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayTypeEnum.java similarity index 92% rename from tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayChannelEnum.java rename to tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayTypeEnum.java index efb0eff..b11e7a0 100644 --- a/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayChannelEnum.java +++ b/tashow-sdk/tashow-sdk-payment/src/main/java/com/tashow/cloud/sdk/payment/enums/channel/PayTypeEnum.java @@ -14,7 +14,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum PayChannelEnum { +public enum PayTypeEnum { WX_PUB("wx_pub", "微信 JSAPI 支付", WxPayClientConfig.class), // 公众号网页 WX_LITE("wx_lite", "微信小程序支付", WxPayClientConfig.class), @@ -55,8 +55,12 @@ public enum PayChannelEnum { * 支付宝支付 */ public static final String ALIPAY = "ALIPAY"; + /** + * 云闪付支付 + */ + public static final String CLOUDPAY = "CLOUDPAY"; - public static PayChannelEnum getByCode(String code) { + public static PayTypeEnum getByCode(String code) { return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values()); }