初始化order和pay模块

This commit is contained in:
2025-08-27 10:02:53 +08:00
parent ea780302e3
commit 1555739fcd
536 changed files with 6640 additions and 17174 deletions

View File

@@ -1,48 +0,0 @@
---
description:
globs:
alwaysApply: false
---
---
description:
globs:
alwaysApply: false
---
# Your rule content
#角色
你是一名精通开发的高级工程师拥有10年以上的应用开发经验熟悉*等开发工具和技术栈。
你的任务是帮助用户设计和开发易用且易于推护的 *** 应用。始终遵循最佳实践,并坚持干净代码和健壮架构的原则。
#目标
你的目标是以用户容易理解的方式帮助他们完成“应用的设计和开发工作,确保应用功能完善、性能优异、用户体验良好。
#要求
在理解用户需求、设计UI、编写代码、解决问题和项目选代优化时你应该始终遵循以下原则:
##需求理解
-充分理解用户需求,站在用户角度思考,分析需求是否存在缺漏,并与用户讨论完善需求;
-选择最简单的解决方案来满足用户需求,避免过度设计。
##UI和样式设计
-使用现代UI框架进行样式设计(例如***这里可以根据不同开发项目仔纽展开比如使用哪些视觉规范或者UI框架没有的话也可以不用过多展开);
-在不同平台上实现一致的设计和响应式模式
##代码编写
技术选型:根据项目需求选择合适的技术栈(例如***,这里需要仔细展开,比如介招某个技术栈用在什么地方,以及要遵循什么最佳实践)
代码结构:强调代码的清晰性、模块化、可维护性,遵循最佳实践(如DRY原则、最小权限原则、响应式设计等)
-代码安全性:在编写代码时,始终考虑安全性,避免引入漏洞,确保用户输入的安全处理
-性能优化:优化代码的性能,减少资源占用,提升加载速度,确保项目的高效运行
-测试与文档:编写单元测试,确保代码的健壮性,并提供清晰的中文注释和文档。方便后续阅读和维护
##问题解决
-全面阅读相关代码,理解***应用的工作原理
-根据用户的反馈分析问题的原因,提出解决问题的思路
-确保每次代码变更不会破坏现有功能,且尽可能保持最小的改动
##迭代优化
与用户保持密切沟通,根据反读调整功能和设计,确保应用符合用户需求
在不确定需求时,主动询问用户以澄清需求或技术细节
##方法论
-系统2思维:以分析严谨的方式解决问题。将需求分解为更小、可管理的部分,并在实施前仔细考虑每一步
思维树:评估多种可能的解决方案及其后果。使用结构化的方法探索不同的路径。并选择最优的解决方案
-选代改进:在最终确定代码之前,考虑改进、边缘情况和优化。通过潜在增强的迭代,确保最终解决方案是健壮的

View File

@@ -1,2 +0,0 @@
**添加规则文件可帮助模型精准理解你的编码偏好,如框架、代码风格等**
**规则文件只对当前工程生效单文件限制10000字符。如果无需将该文件提交到远程 Git 仓库,请将其添加到 .gitignore**

Binary file not shown.

266
sql/mysql/order.sql Normal file
View File

@@ -0,0 +1,266 @@
-- 基础实体表结构 (BaseDO) 不单独建表,字段会被继承到其他表中
-- 交易订单表 (trade_order)
CREATE TABLE `tz_trade_order`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单编号,主键自增',
`order_num` varchar(64) NOT NULL COMMENT '订单流水号',
`order_type` tinyint(4) NOT NULL COMMENT '订单类型 (枚举 TradeOrderTypeEnum)',
`order_terminal` tinyint(4) NOT NULL COMMENT '订单来源 (枚举 TerminalEnum)',
`order_status` tinyint(4) NOT NULL COMMENT '订单状态 (枚举 TradeOrderStatusEnum)',
`user_id` bigint(20) NOT NULL COMMENT '用户编号',
`user_ip` varchar(64) DEFAULT NULL COMMENT '用户IP',
`user_remark` varchar(512) DEFAULT NULL COMMENT '用户备注',
`finish_time` datetime DEFAULT NULL COMMENT '订单完成时间',
`cancel_time` datetime DEFAULT NULL COMMENT '订单取消时间',
`cancel_type` tinyint(4) DEFAULT NULL COMMENT '取消类型 (枚举 TradeOrderCancelTypeEnum)',
`merchant_remark` varchar(512) DEFAULT NULL COMMENT '商家备注',
`comment_status` tinyint(1) DEFAULT NULL COMMENT '是否评价 (true-已评价, false-未评价)',
`total_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 '应付金额(总),单位:分',
`pay_order_id` bigint(20) DEFAULT NULL COMMENT '支付订单编号',
`pay_status` tinyint(1) NOT NULL COMMENT '是否已支付 (true-已支付, false-未支付)',
`pay_time` datetime DEFAULT NULL COMMENT '付款时间',
`pay_channel_code` varchar(64) DEFAULT NULL COMMENT '支付渠道',
`delivery_type` tinyint(4) NOT NULL COMMENT '配送方式 (枚举 DeliveryTypeEnum)',
`logistics_id` bigint(20) NOT NULL 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) NOT NULL COMMENT '收件人名称',
`receiver_mobile` varchar(20) NOT NULL COMMENT '收件人手机',
`receiver_area_id` int(11) NOT NULL COMMENT '收件人地区编号',
`receiver_detail_address` varchar(512) NOT NULL COMMENT '收件人详细地址',
`pick_up_store_id` bigint(20) DEFAULT NULL COMMENT '自提门店编号',
`pick_up_verify_code` varchar(64) DEFAULT NULL COMMENT '自提核销码',
`refund_status` tinyint(4) NOT NULL COMMENT '退款状态 (枚举 TradeOrderRefundStatusEnum)',
`refund_price` int(11) NOT NULL COMMENT '退款金额,单位:分',
`after_sale_id` bigint(20) DEFAULT NULL COMMENT '售后单编号',
`after_sale_status` tinyint(4) NOT NULL COMMENT '售后状态 (枚举 TradeOrderItemAfterSaleStatusEnum)',
`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_order_num` (`order_num`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`order_status`),
KEY `idx_pay_status` (`pay_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 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 '购物车项编号',
`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) NOT NULL COMMENT '商品图片',
`count` 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 '应付金额(总),单位:分',
`serve_ext_info` json DEFAULT NULL COMMENT '扩展服务信息,存储额外的服务相关数据',
`price_ext_info` json 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_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='自提门店表';

View File

@@ -166,6 +166,16 @@
<artifactId>tashow-pay-api</artifactId> <artifactId>tashow-pay-api</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-sdk-payment</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-member-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency> <dependency>
<groupId>com.tashow.cloud</groupId> <groupId>com.tashow.cloud</groupId>
<artifactId>tashow-module-pay</artifactId> <artifactId>tashow-module-pay</artifactId>

View File

@@ -16,6 +16,7 @@
<module>tashow-product-api</module> <module>tashow-product-api</module>
<module>tashow-trade-api</module> <module>tashow-trade-api</module>
<module>tashow-pay-api</module> <module>tashow-pay-api</module>
<module>tashow-member-api</module>
</modules> </modules>
</project> </project>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-feign</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>tashow-member-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
member 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-common</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -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<MemberAddressRespDTO> getAddress(@RequestParam("id") Long id,
@RequestParam("userId") Long userId);
/**
* 获得用户默认收件地址
* @param userId 用户编号
* @return
*/
@GetMapping(PREFIX + "/get-default")
CommonResult<MemberAddressRespDTO> getDefaultAddress(@RequestParam("userId") Long userId);
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
/**
* member API 包,定义暴露给其它模块的 API
*/
package com.tashow.cloud.memberapi.api;

View File

@@ -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<Long, MemberUserRespDTO> getUserMap(Collection<Long> ids) {
List<MemberUserRespDTO> list = getUserList(ids).getCheckedData();
return convertMap(list, MemberUserRespDTO::getId);
}
/**
* 获得会员用户信息
* @param id 编号
* @return
*/
@GetMapping(PREFIX + "/get")
CommonResult<MemberUserRespDTO> getUser(@RequestParam("id") Long id);
/**
* 获得会员用户信息们
* @param ids 用户编号的数组
* @return
*/
@GetMapping(PREFIX + "/list")
CommonResult<List<MemberUserRespDTO>> getUserList(@RequestParam("ids") Collection<Long> ids);
/**
* 基于用户昵称,模糊匹配用户列表
* @param nickname 用户昵称,模糊匹配
* @return
*/
@GetMapping(PREFIX + "/list-by-nickname")
CommonResult<List<MemberUserRespDTO>> getUserListByNickname(@RequestParam("nickname") String nickname);
/**
* 基于手机号,精准匹配用户
* @param mobile 基于手机号,精准匹配用户
* @return
*/
@GetMapping(PREFIX + "/get-by-mobile")
CommonResult<MemberUserRespDTO> getUserByMobile(@RequestParam("mobile") String mobile);
/**
* 校验用户是否存在
* @param id 用户编号
* @return
*/
@GetMapping(PREFIX + "/valid")
CommonResult<Boolean> validateUser(@RequestParam("id") Long id);
}

View File

@@ -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;
}

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -0,0 +1,59 @@
package com.tashow.cloud.memberapi.enums;
import com.tashow.cloud.common.exception.ErrorCode;
/**
* Member 错误码枚举类
* <p>
* 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, "用户分组下存在用户,无法删除");
}

View File

@@ -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()));
}
}

View File

@@ -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<Integer> {
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()));
}
}

View File

@@ -0,0 +1,4 @@
/**
* 消息队列的消息
*/
package com.tashow.cloud.memberapi.message;

View File

@@ -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;
}

View File

@@ -1,6 +1,6 @@
package com.tashow.cloud.payapi.api.order.dto; package com.tashow.cloud.payapi.api.order.dto;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import com.tashow.cloud.payapi.enums.order.PayOrderStatusEnum;
import lombok.Data; import lombok.Data;
/** /**

View File

@@ -1,6 +1,8 @@
package com.tashow.cloud.payapi.api.refund; package com.tashow.cloud.payapi.api.refund;
import com.tashow.cloud.common.pojo.CommonResult; 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 com.tashow.cloud.payapi.enums.ApiConstants;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;

View File

@@ -1,6 +1,6 @@
package com.tashow.cloud.payapi.api.refund.dto; package com.tashow.cloud.payapi.api.refund.dto;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; import com.tashow.cloud.payapi.enums.refund.PayRefundStatusEnum;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;

View File

@@ -1,7 +1,9 @@
package com.tashow.cloud.payapi.api.transfer; package com.tashow.cloud.payapi.api.transfer;
import com.tashow.cloud.common.pojo.CommonResult; 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.payapi.enums.ApiConstants;
import com.tashow.cloud.sdk.payment.dto.transfer.PayTransferRespDTO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;

View File

@@ -1,7 +1,7 @@
package com.tashow.cloud.payapi.api.transfer.dto; package com.tashow.cloud.payapi.api.transfer.dto;
import cn.iocoder.yudao.framework.common.validation.InEnum; import com.tashow.cloud.common.validation.InEnum;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum; import com.tashow.cloud.sdk.payment.enums.transfer.PayTransferTypeEnum;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;

View File

@@ -1,6 +1,6 @@
package com.tashow.cloud.payapi.api.transfer.dto; package com.tashow.cloud.payapi.api.transfer.dto;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; import com.tashow.cloud.payapi.enums.transfer.PayTransferStatusEnum;
import lombok.Data; import lombok.Data;
@Data @Data

View File

@@ -1,6 +1,7 @@
package com.tashow.cloud.payapi.api.wallet; package com.tashow.cloud.payapi.api.wallet;
import com.tashow.cloud.common.pojo.CommonResult; import com.tashow.cloud.common.pojo.CommonResult;
import com.tashow.cloud.payapi.api.wallet.dto.PayWalletAddBalanceReqDTO;
import com.tashow.cloud.payapi.enums.ApiConstants; import com.tashow.cloud.payapi.enums.ApiConstants;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;

View File

@@ -324,7 +324,7 @@ public class GlobalExceptionHandler {
* 处理 Table 不存在的异常情况 * 处理 Table 不存在的异常情况
* *
* @param ex 异常 * @param ex 异常
* @return 如果是 Table 不存在的异常,则返回对应的 CommonResult * @return 如果是 Table 不存在的异常 ,则返回对应的 CommonResult
*/ */
private CommonResult<?> handleTableNotExists(Throwable ex) { private CommonResult<?> handleTableNotExists(Throwable ex) {
String message = ExceptionUtil.getRootCauseMessage(ex); String message = ExceptionUtil.getRootCauseMessage(ex);
@@ -333,57 +333,57 @@ public class GlobalExceptionHandler {
} }
// 1. 数据报表 // 1. 数据报表
if (message.contains("report_")) { 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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]"); "[报表模块 yudao-module-report - 表结构未导入]");
} }
// 2. 工作流 // 2. 工作流
if (message.contains("bpm_")) { 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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]"); "[工作流模块 yudao-module-bpm - 表结构未导入]");
} }
// 3. 微信公众号 // 3. 微信公众号
if (message.contains("mp_")) { 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(), 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_")) { if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); log.error("[商城系统 tashow-module-trade - 表结构未导入]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]"); "[商城系统 tashow-module-trade - 表结构未导入]");
} }
// 5. ERP 系统 // 5. ERP 系统
if (message.contains("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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]"); "[ERP 系统 yudao-module-erp - 表结构未导入]");
} }
// 6. CRM 系统 // 6. CRM 系统
if (message.contains("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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]"); "[CRM 系统 yudao-module-crm - 表结构未导入]");
} }
// 7. 支付平台 // 7. 支付平台
if (message.contains("pay_")) { 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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]"); "[支付模块 yudao-module-pay - 表结构未导入]");
} }
// 8. AI 大模型 // 8. AI 大模型
if (message.contains("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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]"); "[AI 大模型 yudao-module-ai - 表结构未导入]");
} }
// 9. IOT 物联网 // 9. IOT 物联网
if (message.contains("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(), return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[IOT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]"); "[IOT 物联网 yudao-module-iot - 表结构未导入]");
} }
return null; return null;
} }

View File

@@ -3,14 +3,14 @@
spring: spring:
cloud: cloud:
nacos: nacos:
server-addr: 43.139.42.137:8848 # Nacos 服务器地址 server-addr: localhost:8848 # Nacos 服务器地址
username: nacos # Nacos 账号 username: # Nacos 账号
password: nacos # Nacos 密码 password: # Nacos 密码
discovery: # 【配置中心】配置项 discovery: # 【配置中心】配置项
namespace: 76667956-2ac2-4e05-906b-4bca4ebcc5f0 # 命名空间。这里使用 dev 开发环境 namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
config: # 【注册中心】配置项 config: # 【注册中心】配置项
namespace: 76667956-2ac2-4e05-906b-4bca4ebcc5f0 # 命名空间。这里使用 dev 开发环境 namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
# 日志文件配置 # 日志文件配置

View File

@@ -17,6 +17,7 @@
<module>tashow-module-product</module> <module>tashow-module-product</module>
<module>tashow-module-pay</module> <module>tashow-module-pay</module>
<module>tashow-module-trade</module> <module>tashow-module-trade</module>
<module>tashow-module-member</module>
</modules> </modules>
</project> </project>

View File

@@ -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

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-module</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>tashow-module-member</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
member 模块,我们放会员业务。
例如说:会员中心等等
</description>
<dependencies>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-framework-env</artifactId>
</dependency>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-framework-monitor</artifactId>
</dependency>
<!-- 依赖服务 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-member-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-infra-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-framework-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-framework-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-data-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-data-redis</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-framework-rpc</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Config 配置中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-framework-mq</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.tashow.cloud</groupId>
<artifactId>tashow-data-excel</artifactId>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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);
}
}

View File

@@ -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<MemberAddressRespDTO> getAddress(Long id, Long userId) {
return success(AddressConvert.INSTANCE.convert02(addressService.getAddress(userId, id)));
}
@Override
public CommonResult<MemberAddressRespDTO> getDefaultAddress(Long userId) {
return success(AddressConvert.INSTANCE.convert02(addressService.getDefaultUserAddress(userId)));
}
}

View File

@@ -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<List<AddressRespVO>> getAddressList(@RequestParam("userId") Long userId) {
List<MemberAddressDO> list = addressService.getAddressList(userId);
return success(AddressConvert.INSTANCE.convertList2(list));
}
}

View File

@@ -0,0 +1 @@
package com.tashow.cloud.member.controller.admin.address;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<Boolean> updateUser(@Valid @RequestBody MemberUserUpdateReqVO updateReqVO) {
memberUserService.updateUser(updateReqVO);
return success(true);
}
@GetMapping("/get")
// 获得会员用户
// id: 编号必填示例1024
@PreAuthorize("@ss.hasPermission('member:user:query')")
public CommonResult<MemberUserRespVO> 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<PageResult<MemberUserRespVO>> getUserPage(@Valid MemberUserPageReqVO pageVO) {
PageResult<MemberUserDO> pageResult = memberUserService.getUserPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty());
}
// 处理用户标签返显
Set<Long> tagIds = pageResult.getList().stream()
.map(MemberUserDO::getTagIds)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
return success(MemberUserConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -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<Long> tagIds;
// 会员等级编号示例1
private Long levelId;
// 用户分组编号示例1
private Long groupId;
}

View File

@@ -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<Long> tagIds;
// 会员等级编号示例1
private Long levelId;
// 用户分组编号示例1
private Long groupId;
// TODO 芋艿:注册用户类型;
// TODO 芋艿:登录用户类型;
}

View File

@@ -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<String> tagNames;
// 会员等级,示例:黄金会员
private String levelName;
// 用户分组,示例:购物达人
private String groupName;
// 用户经验值必填示例200
private Integer experience;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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}}

View File

@@ -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<Long> createAddress(@Valid @RequestBody AppAddressCreateReqVO createReqVO) {
return success(addressService.createAddress(getLoginUserId(), createReqVO));
}
/**
* 更新用户收件地址
* @param updateReqVO
* @return
*/
@PutMapping("/update")
public CommonResult<Boolean> updateAddress(@Valid @RequestBody AppAddressUpdateReqVO updateReqVO) {
addressService.updateAddress(getLoginUserId(), updateReqVO);
return success(true);
}
/**
* 删除用户收件地址
* @param id 编号
* @return
*/
@DeleteMapping("/delete")
public CommonResult<Boolean> deleteAddress(@RequestParam("id") Long id) {
addressService.deleteAddress(getLoginUserId(), id);
return success(true);
}
/**
* 获得用户收件地址
* @param id 编号
* @return
*/
@GetMapping("/get")
public CommonResult<AppAddressRespVO> getAddress(@RequestParam("id") Long id) {
MemberAddressDO address = addressService.getAddress(getLoginUserId(), id);
return success(AddressConvert.INSTANCE.convert(address));
}
/**
* 获得默认的用户收件地址
* @return
*/
@GetMapping("/get-default")
public CommonResult<AppAddressRespVO> getDefaultUserAddress() {
MemberAddressDO address = addressService.getDefaultUserAddress(getLoginUserId());
return success(AddressConvert.INSTANCE.convert(address));
}
/**
* 获得用户收件地址列表
* @return
*/
@GetMapping("/list")
public CommonResult<List<AppAddressRespVO>> getAddressList() {
List<MemberAddressDO> list = addressService.getAddressList(getLoginUserId());
return success(AddressConvert.INSTANCE.convertList(list));
}
}

View File

@@ -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;
}

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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}}

View File

@@ -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<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
return success(authService.login(reqVO));
}
/**
* 登出系统
* @param request
* @return
*/
@PostMapping("/logout")
@PermitAll
public CommonResult<Boolean> 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<AppAuthLoginRespVO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
return success(authService.refreshToken(refreshToken));
}
// ========== 短信登录相关 ==========
/**
* 使用手机 + 验证码登录
* @param reqVO
* @return
*/
@PostMapping("/sms-login")
@PermitAll
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
return success(authService.smsLogin(reqVO));
}
/**
* 发送手机验证码
* @param reqVO
* @return
*/
@PostMapping("/send-sms-code")
@PermitAll
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSmsSendReqVO reqVO) {
authService.sendSmsCode(getLoginUserId(), reqVO);
return success(true);
}
/**
* 校验手机验证码
* @param reqVO
* @return
*/
@PostMapping("/validate-sms-code")
@PermitAll
public CommonResult<Boolean> validateSmsCode(@RequestBody @Valid AppAuthSmsValidateReqVO reqVO) {
authService.validateSmsCode(getLoginUserId(), reqVO);
return success(true);
}
// ========== 社交登录相关 ==========
/**
* 社交授权的跳转
* @param type 社交类型
* @param redirectUri 回调路径
* @return
*/
@GetMapping("/social-auth-redirect")
@PermitAll
public CommonResult<String> 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<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {
return success(authService.socialLogin(reqVO));
}
/**
* 微信小程序的一键登录
* @param reqVO
* @return
*/
@PostMapping("/weixin-mini-app-login")
@PermitAll
public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) {
return success(authService.weixinMiniAppLogin(reqVO));
}
/**
* 创建微信 JS SDK 初始化所需的签名
* @param url
* @return
*/
@PostMapping("/create-weixin-jsapi-signature")
@PermitAll
public CommonResult<SocialWxJsapiSignatureRespDTO> createWeixinMpJsapiSignature(@RequestParam("url") String url) {
SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature(
UserTypeEnum.MEMBER.getValue(), url).getCheckedData();
return success(AuthConvert.INSTANCE.convert(signature));
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -0,0 +1,4 @@
### 请求 /member/user/profile/get 接口 => 没有权限
GET {{appApi}}/member/user/get
Authorization: Bearer test245
tenant-id: {{appTenantId}}

View File

@@ -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<AppMemberUserInfoRespVO> getUserInfo() {
MemberUserDO user = userService.getUser(getLoginUserId());
return success(MemberUserConvert.INSTANCE.convert(user));
}
@PutMapping("/update")
// 修改基本信息
public CommonResult<Boolean> updateUser(@RequestBody @Valid AppMemberUserUpdateReqVO reqVO) {
userService.updateUser(getLoginUserId(), reqVO);
return success(true);
}
@PutMapping("/update-mobile")
// 修改用户手机
public CommonResult<Boolean> updateUserMobile(@RequestBody @Valid AppMemberUserUpdateMobileReqVO reqVO) {
userService.updateUserMobile(getLoginUserId(), reqVO);
return success(true);
}
@PutMapping("/update-mobile-by-weixin")
// 基于微信小程序的授权码,修改用户手机
public CommonResult<Boolean> updateUserMobileByWeixin(@RequestBody @Valid AppMemberUserUpdateMobileByWeixinReqVO reqVO) {
userService.updateUserMobileByWeixin(getLoginUserId(), reqVO);
return success(true);
}
@PutMapping("/update-password")
// 修改用户密码
// 用户修改密码时使用
public CommonResult<Boolean> updateUserPassword(@RequestBody @Valid AppMemberUserUpdatePasswordReqVO reqVO) {
userService.updateUserPassword(getLoginUserId(), reqVO);
return success(true);
}
@PutMapping("/reset-password")
// 重置密码
// 用户忘记密码时使用
@PermitAll
public CommonResult<Boolean> resetUserPassword(@RequestBody @Valid AppMemberUserResetPasswordReqVO reqVO) {
userService.resetUserPassword(reqVO);
return success(true);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<AppAddressRespVO> convertList(List<MemberAddressDO> list);
MemberAddressRespDTO convert02(MemberAddressDO bean);
@Named("convertAreaIdToAreaName")
default String convertAreaIdToAreaName(Integer areaId) {
return AreaUtils.format(areaId);
}
List<AddressRespVO> convertList2(List<MemberAddressDO> list);
}

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
/**
* 提供 POJO 类的实体转换
*
* 目前使用 MapStruct 框架
*/
package com.tashow.cloud.member.convert;

View File

@@ -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<MemberUserRespDTO> convertList2(List<MemberUserDO> list);
MemberUserDO convert(MemberUserUpdateReqVO bean);
PageResult<MemberUserRespVO> convertPage(PageResult<MemberUserDO> page);
@Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName")
MemberUserRespVO convert03(MemberUserDO bean);
}

View File

@@ -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;
}

View File

@@ -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<Long> tagIds;
/**
* 会员级别编号
*
* 关联 {@link MemberLevelDO#getId()} 字段
*/
private Long levelId;
/**
* 会员经验
*/
private Integer experience;
/**
* 用户分组编号
*
* 关联 {@link MemberGroupDO#getId()} 字段
*/
private Long groupId;
}

View File

@@ -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<MemberAddressDO> {
default MemberAddressDO selectByIdAndUserId(Long id, Long userId) {
return selectOne(MemberAddressDO::getId, id, MemberAddressDO::getUserId, userId);
}
default List<MemberAddressDO> selectListByUserIdAndDefaulted(Long userId, Boolean defaulted) {
return selectList(new LambdaQueryWrapperX<MemberAddressDO>().eq(MemberAddressDO::getUserId, userId)
.eqIfPresent(MemberAddressDO::getDefaultStatus, defaulted));
}
}

View File

@@ -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<MemberUserDO> {
default MemberUserDO selectByMobile(String mobile) {
return selectOne(MemberUserDO::getMobile, mobile);
}
default List<MemberUserDO> selectListByNicknameLike(String nickname) {
return selectList(new LambdaQueryWrapperX<MemberUserDO>()
.likeIfPresent(MemberUserDO::getNickname, nickname));
}
default PageResult<MemberUserDO> 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<MemberUserDO>()
.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<MemberUserDO>()
.apply("FIND_IN_SET({0}, tag_ids)", tagId));
}
/**
* 更新用户积分(增加)
*
* @param id 用户编号
* @param incrCount 增加积分(正数)
*/
default void updatePointIncr(Long id, Integer incrCount) {
Assert.isTrue(incrCount > 0);
LambdaUpdateWrapper<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
.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<MemberUserDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<MemberUserDO>()
.setSql(" point = point + " + incrCount) // 负数,所以使用 + 号
.eq(MemberUserDO::getId, id);
return update(null, lambdaUpdateWrapper);
}
}

View File

@@ -0,0 +1,9 @@
/**
* DAL = Data Access Layer 数据访问层
* 1. data object数据对象
* 2. redisRedis 的 CRUD 操作
* 3. mysqlMySQL 的 CRUD 操作
*
* 其中MySQL 的表以 member_ 作为前缀
*/
package com.tashow.cloud.member.dal;

View File

@@ -0,0 +1,4 @@
/**
* 占位,后续有类后,可以删除,避免 package 无法提交到 Git 上
*/
package com.tashow.cloud.member.dal.redis;

View File

@@ -0,0 +1,6 @@
/**
* 属于 member 模块的 framework 封装
*
* @author 芋道源码
*/
package com.tashow.cloud.member.framework;

View File

@@ -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 {
}

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package com.tashow.cloud.member.framework.rpc;

View File

@@ -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<HttpSecurity>.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();
}
};
}
}

View File

@@ -0,0 +1,4 @@
/**
* 占位
*/
package com.tashow.cloud.member.framework.security.core;

View File

@@ -0,0 +1,4 @@
/**
* 消息队列的消费者
*/
package com.tashow.cloud.member.mq.consumer;

View File

@@ -0,0 +1,4 @@
/**
* 消息队列的消息
*/
package com.tashow.cloud.member.mq.message;

View File

@@ -0,0 +1,4 @@
/**
* 消息队列的生产者
*/
package com.tashow.cloud.member.mq.producer;

View File

@@ -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));
}
}

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