diff --git a/pom.xml b/pom.xml
index 17601bfea..65000e402 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,10 +12,10 @@
yudao-gateway
yudao-framework
-
- yudao-module-bpm
yudao-module-system
yudao-module-infra
+ yudao-module-member
+ yudao-module-bpm
yudao-module-pay
yudao-module-report
yudao-module-mp
diff --git a/yudao-module-member/pom.xml b/yudao-module-member/pom.xml
new file mode 100644
index 000000000..f481f6b26
--- /dev/null
+++ b/yudao-module-member/pom.xml
@@ -0,0 +1,24 @@
+
+
+
+ cn.iocoder.cloud
+ yudao
+ ${revision}
+
+ 4.0.0
+
+ yudao-module-member-api
+ yudao-module-member-biz
+
+ yudao-module-member
+ pom
+
+ ${project.artifactId}
+
+ member 模块,我们放会员业务。
+ 例如说:会员中心等等
+
+
+
diff --git a/yudao-module-member/yudao-module-member-api/pom.xml b/yudao-module-member/yudao-module-member-api/pom.xml
new file mode 100644
index 000000000..242df88f5
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/pom.xml
@@ -0,0 +1,33 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-member
+ ${revision}
+
+ 4.0.0
+ yudao-module-member-api
+ jar
+
+ ${project.artifactId}
+
+ member 模块 API,暴露给其它模块调用
+
+
+
+
+ cn.iocoder.cloud
+ yudao-common
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+ true
+
+
+
+
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApi.java
new file mode 100644
index 000000000..658748819
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApi.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.api.address;
+
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+
+/**
+ * 用户收件地址 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface AddressApi {
+
+ /**
+ * 获得用户收件地址
+ *
+ * @param id 收件地址编号
+ * @param userId 用户编号
+ * @return 用户收件地址
+ */
+ AddressRespDTO getAddress(Long id, Long userId);
+
+ /**
+ * 获得用户默认收件地址
+ *
+ * @param userId 用户编号
+ * @return 用户收件地址
+ */
+ AddressRespDTO getDefaultAddress(Long userId);
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/dto/AddressRespDTO.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/dto/AddressRespDTO.java
new file mode 100644
index 000000000..5a5e44ff1
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/address/dto/AddressRespDTO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.member.api.address.dto;
+
+import lombok.Data;
+
+/**
+ * 用户收件地址 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class AddressRespDTO {
+
+ /**
+ * 编号
+ */
+ private Long id;
+ /**
+ * 用户编号
+ */
+ private Long userId;
+ /**
+ * 收件人名称
+ */
+ private String name;
+ /**
+ * 手机号
+ */
+ private String mobile;
+ /**
+ * 地区编号
+ */
+ private Integer areaId;
+ /**
+ * 收件详细地址
+ */
+ private String detailAddress;
+ /**
+ * 是否默认
+ */
+ private Boolean defaultStatus;
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApi.java
new file mode 100644
index 000000000..dab7f6877
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApi.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.member.api.config;
+
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+
+/**
+ * 用户配置 API 接口
+ *
+ * @author owen
+ */
+public interface MemberConfigApi {
+
+ /**
+ * 获得积分配置
+ *
+ * @return 积分配置
+ */
+ MemberConfigRespDTO getConfig();
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/dto/MemberConfigRespDTO.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/dto/MemberConfigRespDTO.java
new file mode 100644
index 000000000..59aab53f9
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/config/dto/MemberConfigRespDTO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.member.api.config.dto;
+
+import lombok.Data;
+
+/**
+ * 用户信息 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MemberConfigRespDTO {
+
+ /**
+ * 积分抵扣开关
+ */
+ private Boolean pointTradeDeductEnable;
+ /**
+ * 积分抵扣,单位:分
+ *
+ * 1 积分抵扣多少分
+ */
+ private Integer pointTradeDeductUnitPrice;
+ /**
+ * 积分抵扣最大值
+ */
+ private Integer pointTradeDeductMaxPrice;
+ /**
+ * 1 元赠送多少分
+ */
+ private Integer pointTradeGivePoint;
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java
new file mode 100644
index 000000000..587683797
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.api.level;
+
+import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+
+/**
+ * 会员等级 API 接口
+ *
+ * @author owen
+ */
+public interface MemberLevelApi {
+
+ /**
+ * 获得会员等级
+ *
+ * @param id 会员等级编号
+ * @return 会员等级
+ */
+ MemberLevelRespDTO getMemberLevel(Long id);
+
+ /**
+ * 增加会员经验
+ *
+ * @param userId 会员ID
+ * @param experience 经验
+ * @param bizType 业务类型 {@link MemberExperienceBizTypeEnum}
+ * @param bizId 业务编号
+ */
+ void addExperience(Long userId, Integer experience, Integer bizType, String bizId);
+
+ /**
+ * 扣减会员经验
+ *
+ * @param userId 会员ID
+ * @param experience 经验
+ * @param bizType 业务类型 {@link MemberExperienceBizTypeEnum}
+ * @param bizId 业务编号
+ */
+ void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId);
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/dto/MemberLevelRespDTO.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/dto/MemberLevelRespDTO.java
new file mode 100644
index 000000000..a72d65f23
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/dto/MemberLevelRespDTO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.api.level.dto;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import lombok.Data;
+
+/**
+ * 会员等级 Resp DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MemberLevelRespDTO {
+
+ /**
+ * 编号
+ */
+ private Long id;
+ /**
+ * 等级名称
+ */
+ private String name;
+ /**
+ * 等级
+ */
+ private Integer level;
+ /**
+ * 升级经验
+ */
+ private Integer experience;
+ /**
+ * 享受折扣
+ */
+ private Integer discountPercent;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/package-info.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/package-info.java
new file mode 100644
index 000000000..56cd9857f
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * member API 包,定义暴露给其它模块的 API
+ */
+package cn.iocoder.yudao.module.member.api;
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
new file mode 100644
index 000000000..3eb749fb6
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApi.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.member.api.point;
+
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+
+import javax.validation.constraints.Min;
+
+/**
+ * 用户积分的 API 接口
+ *
+ * @author owen
+ */
+public interface MemberPointApi {
+
+ /**
+ * 增加用户积分
+ *
+ * @param userId 用户编号
+ * @param point 积分
+ * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
+ * @param bizId 业务编号
+ */
+ void addPoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
+ Integer bizType, String bizId);
+
+ /**
+ * 减少用户积分
+ *
+ * @param userId 用户编号
+ * @param point 积分
+ * @param bizType 业务类型 {@link MemberPointBizTypeEnum}
+ * @param bizId 业务编号
+ */
+ void reducePoint(Long userId, @Min(value = 1L, message = "积分必须是正数") Integer point,
+ Integer bizType, String bizId);
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java
new file mode 100644
index 000000000..3d2130e18
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.member.api.user;
+
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 会员用户的 API 接口
+ *
+ * @author 芋道源码
+ */
+public interface MemberUserApi {
+
+ /**
+ * 获得会员用户信息
+ *
+ * @param id 用户编号
+ * @return 用户信息
+ */
+ MemberUserRespDTO getUser(Long id);
+
+ /**
+ * 获得会员用户信息们
+ *
+ * @param ids 用户编号的数组
+ * @return 用户信息们
+ */
+ List getUserList(Collection ids);
+
+ /**
+ * 获得会员用户 Map
+ *
+ * @param ids 用户编号的数组
+ * @return 会员用户 Map
+ */
+ default Map getUserMap(Collection ids) {
+ return convertMap(getUserList(ids), MemberUserRespDTO::getId);
+ }
+
+ /**
+ * 基于用户昵称,模糊匹配用户列表
+ *
+ * @param nickname 用户昵称,模糊匹配
+ * @return 用户信息的列表
+ */
+ List getUserListByNickname(String nickname);
+
+ /**
+ * 基于手机号,精准匹配用户
+ *
+ * @param mobile 手机号
+ * @return 用户信息
+ */
+ MemberUserRespDTO getUserByMobile(String mobile);
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java
new file mode 100644
index 000000000..50548f935
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/dto/MemberUserRespDTO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.member.api.user.dto;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 用户信息 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class MemberUserRespDTO {
+
+ /**
+ * 用户ID
+ */
+ private Long id;
+ /**
+ * 用户昵称
+ */
+ private String nickname;
+ /**
+ * 帐号状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 用户头像
+ */
+ private String avatar;
+ /**
+ * 手机
+ */
+ private String mobile;
+ /**
+ * 创建时间(注册时间)
+ */
+ private LocalDateTime createTime;
+
+ // ========== 其它信息 ==========
+
+ /**
+ * 会员级别编号
+ */
+ private Long levelId;
+
+ /**
+ * 积分
+ */
+ private Integer point;
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/DictTypeConstants.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/DictTypeConstants.java
new file mode 100644
index 000000000..c87cbb901
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/DictTypeConstants.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.member.enums;
+
+/**
+ * Member 字典类型的枚举类
+ *
+ * @author owen
+ */
+public interface DictTypeConstants {
+
+ /**
+ * 会员经验记录 - 业务类型
+ */
+ String MEMBER_EXPERIENCE_BIZ_TYPE = "member_experience_biz_type";
+
+}
diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
new file mode 100644
index 000000000..c7dc8b749
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.member.enums;
+
+import cn.iocoder.yudao.framework.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_THIRD_LOGIN_NOT_BIND = 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/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java
new file mode 100644
index 000000000..3038dba31
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.member.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/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
new file mode 100644
index 000000000..ef491f42a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/point/MemberPointBizTypeEnum.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.member.enums.point;
+
+import cn.hutool.core.util.EnumUtil;
+import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Objects;
+
+/**
+ * 会员积分的业务类型枚举
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum MemberPointBizTypeEnum implements IntArrayValuable {
+
+ 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 int[] array() {
+ return new int[0];
+ }
+
+ public static MemberPointBizTypeEnum getByType(Integer type) {
+ return EnumUtil.getBy(MemberPointBizTypeEnum.class,
+ e -> Objects.equals(type, e.getType()));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/pom.xml b/yudao-module-member/yudao-module-member-biz/pom.xml
new file mode 100644
index 000000000..2d06e9c34
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/pom.xml
@@ -0,0 +1,95 @@
+
+
+
+ cn.iocoder.cloud
+ yudao-module-member
+ ${revision}
+
+ 4.0.0
+ yudao-module-member-biz
+ jar
+
+ ${project.artifactId}
+
+ member 模块,我们放会员业务。
+ 例如说:会员中心等等
+
+
+
+
+ cn.iocoder.cloud
+ yudao-module-member-api
+ ${revision}
+
+
+ cn.iocoder.cloud
+ yudao-module-system-api
+ ${revision}
+
+
+ cn.iocoder.cloud
+ yudao-module-infra-api
+ ${revision}
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-operatelog
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-tenant
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-security
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mybatis
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-redis
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-mq
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-test
+ test
+
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-excel
+
+
+
+ cn.iocoder.cloud
+ yudao-spring-boot-starter-biz-ip
+
+
+
+
+
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApiImpl.java
new file mode 100644
index 000000000..b8088a455
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/address/AddressApiImpl.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.member.api.address;
+
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.service.address.AddressService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 用户收件地址 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class AddressApiImpl implements AddressApi {
+
+ @Resource
+ private AddressService addressService;
+
+ @Override
+ public AddressRespDTO getAddress(Long id, Long userId) {
+ return AddressConvert.INSTANCE.convert02(addressService.getAddress(userId, id));
+ }
+
+ @Override
+ public AddressRespDTO getDefaultAddress(Long userId) {
+ return AddressConvert.INSTANCE.convert02(addressService.getDefaultUserAddress(userId));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApiImpl.java
new file mode 100644
index 000000000..510f4fff7
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/config/MemberConfigApiImpl.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.api.config;
+
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
+import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 用户配置 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberConfigApiImpl implements MemberConfigApi {
+
+ @Resource
+ private MemberConfigService memberConfigService;
+
+ @Override
+ public MemberConfigRespDTO getConfig() {
+ return MemberConfigConvert.INSTANCE.convert01(memberConfigService.getConfig());
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java
new file mode 100644
index 000000000..79fed98eb
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.member.api.level;
+
+import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.EXPERIENCE_BIZ_NOT_SUPPORT;
+
+/**
+ * 会员等级 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberLevelApiImpl implements MemberLevelApi {
+
+ @Resource
+ private MemberLevelService memberLevelService;
+
+ @Override
+ public MemberLevelRespDTO getMemberLevel(Long id) {
+ return MemberLevelConvert.INSTANCE.convert02(memberLevelService.getLevel(id));
+ }
+
+ @Override
+ public void addExperience(Long userId, Integer experience, Integer bizType, String bizId) {
+ MemberExperienceBizTypeEnum bizTypeEnum = MemberExperienceBizTypeEnum.getByType(bizType);
+ if (bizTypeEnum == null) {
+ throw exception(EXPERIENCE_BIZ_NOT_SUPPORT);
+ }
+ memberLevelService.addExperience(userId, experience, bizTypeEnum, bizId);
+ }
+
+ @Override
+ public void reduceExperience(Long userId, Integer experience, Integer bizType, String bizId) {
+ addExperience(userId, -experience, bizType, bizId);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/package-info.java
new file mode 100644
index 000000000..5f97979b8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.member.api;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
new file mode 100644
index 000000000..6e21e8546
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/point/MemberPointApiImpl.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.member.api.point;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.POINT_RECORD_BIZ_NOT_SUPPORT;
+
+/**
+ * 用户积分的 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberPointApiImpl implements MemberPointApi {
+
+ @Resource
+ private MemberPointRecordService memberPointRecordService;
+
+ @Override
+ public void addPoint(Long userId, Integer point, Integer bizType, String bizId) {
+ Assert.isTrue(point > 0);
+ MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType);
+ if (bizTypeEnum == null) {
+ throw exception(POINT_RECORD_BIZ_NOT_SUPPORT);
+ }
+ memberPointRecordService.createPointRecord(userId, point, bizTypeEnum, bizId);
+ }
+
+ @Override
+ public void reducePoint(Long userId, Integer point, Integer bizType, String bizId) {
+ Assert.isTrue(point > 0);
+ MemberPointBizTypeEnum bizTypeEnum = MemberPointBizTypeEnum.getByType(bizType);
+ if (bizTypeEnum == null) {
+ throw exception(POINT_RECORD_BIZ_NOT_SUPPORT);
+ }
+ memberPointRecordService.createPointRecord(userId, -point, bizTypeEnum, bizId);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java
new file mode 100644
index 000000000..8da857c6d
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.member.api.user;
+
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 会员用户的 API 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class MemberUserApiImpl implements MemberUserApi {
+
+ @Resource
+ private MemberUserService userService;
+
+ @Override
+ public MemberUserRespDTO getUser(Long id) {
+ MemberUserDO user = userService.getUser(id);
+ return MemberUserConvert.INSTANCE.convert2(user);
+ }
+
+ @Override
+ public List getUserList(Collection ids) {
+ return MemberUserConvert.INSTANCE.convertList2(userService.getUserList(ids));
+ }
+
+ @Override
+ public List getUserListByNickname(String nickname) {
+ return MemberUserConvert.INSTANCE.convertList2(userService.getUserListByNickname(nickname));
+ }
+
+ @Override
+ public MemberUserRespDTO getUserByMobile(String mobile) {
+ return MemberUserConvert.INSTANCE.convert2(userService.getUserByMobile(mobile));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/AddressController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/AddressController.java
new file mode 100644
index 000000000..0363634c5
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/AddressController.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.controller.admin.address;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.admin.address.vo.AddressRespVO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO;
+import cn.iocoder.yudao.module.member.service.address.AddressService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 用户收件地址")
+@RestController
+@RequestMapping("/member/address")
+@Validated
+public class AddressController {
+
+ @Resource
+ private AddressService addressService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得用户收件地址列表")
+ @Parameter(name = "userId", description = "用户编号", required = true)
+ @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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/package-info.java
new file mode 100644
index 000000000..652bbb6f1
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/package-info.java
@@ -0,0 +1 @@
+package cn.iocoder.yudao.module.member.controller.admin.address;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressBaseVO.java
new file mode 100644
index 000000000..5fa2d1ed9
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressBaseVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.controller.admin.address.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import java.util.*;
+import javax.validation.constraints.*;
+
+/**
+ * 用户收件地址 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class AddressBaseVO {
+
+ @Schema(description = "收件人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
+ @NotNull(message = "收件人名称不能为空")
+ private String name;
+
+ @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "手机号不能为空")
+ private String mobile;
+
+ @Schema(description = "地区编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "15716")
+ @NotNull(message = "地区编码不能为空")
+ private Long areaId;
+
+ @Schema(description = "收件详细地址", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "收件详细地址不能为空")
+ private String detailAddress;
+
+ @Schema(description = "是否默认", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "是否默认不能为空")
+ private Boolean defaultStatus;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressRespVO.java
new file mode 100644
index 000000000..26a4988af
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/address/vo/AddressRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.controller.admin.address.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 用户收件地址 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AddressRespVO extends AddressBaseVO {
+
+ @Schema(description = "收件地址编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7380")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/MemberConfigController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/MemberConfigController.java
new file mode 100644
index 000000000..730358f9b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/MemberConfigController.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.member.controller.admin.config;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import cn.iocoder.yudao.module.member.service.config.MemberConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 会员设置")
+@RestController
+@RequestMapping("/member/config")
+@Validated
+public class MemberConfigController {
+
+ @Resource
+ private MemberConfigService memberConfigService;
+
+ @PutMapping("/save")
+ @Operation(summary = "保存会员配置")
+ @PreAuthorize("@ss.hasPermission('member:config:save')")
+ public CommonResult saveConfig(@Valid @RequestBody MemberConfigSaveReqVO saveReqVO) {
+ memberConfigService.saveConfig(saveReqVO);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得会员配置")
+ @PreAuthorize("@ss.hasPermission('member:config:query')")
+ public CommonResult getConfig() {
+ MemberConfigDO config = memberConfigService.getConfig();
+ return success(MemberConfigConvert.INSTANCE.convert(config));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigBaseVO.java
new file mode 100644
index 000000000..a9a6b3195
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigBaseVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 会员配置 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberConfigBaseVO {
+
+ @Schema(description = "积分抵扣开关", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ @NotNull(message = "积分抵扣开发不能为空")
+ private Boolean pointTradeDeductEnable;
+
+ @Schema(description = "积分抵扣,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "13506")
+ @NotNull(message = "积分抵扣不能为空")
+ private Integer pointTradeDeductUnitPrice;
+
+ @Schema(description = "积分抵扣最大值", requiredMode = Schema.RequiredMode.REQUIRED, example = "32428")
+ @NotNull(message = "积分抵扣最大值不能为空")
+ private Integer pointTradeDeductMaxPrice;
+
+ @Schema(description = "1 元赠送多少分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "1 元赠送积分不能为空")
+ private Integer pointTradeGivePoint;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigRespVO.java
new file mode 100644
index 000000000..04f14f3d1
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigRespVO.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.member.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 会员配置 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberConfigRespVO extends MemberConfigBaseVO {
+
+ @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigSaveReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigSaveReqVO.java
new file mode 100644
index 000000000..8348f1f3c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/config/vo/MemberConfigSaveReqVO.java
@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.module.member.controller.admin.config.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 会员配置保存 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberConfigSaveReqVO extends MemberConfigBaseVO {
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/MemberGroupController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/MemberGroupController.java
new file mode 100644
index 000000000..566e516a1
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/MemberGroupController.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.member.controller.admin.group;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.*;
+import cn.iocoder.yudao.module.member.convert.group.MemberGroupConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+
+@Tag(name = "管理后台 - 用户分组")
+@RestController
+@RequestMapping("/member/group")
+@Validated
+public class MemberGroupController {
+
+ @Resource
+ private MemberGroupService groupService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建用户分组")
+ @PreAuthorize("@ss.hasPermission('member:group:create')")
+ public CommonResult createGroup(@Valid @RequestBody MemberGroupCreateReqVO createReqVO) {
+ return success(groupService.createGroup(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新用户分组")
+ @PreAuthorize("@ss.hasPermission('member:group:update')")
+ public CommonResult updateGroup(@Valid @RequestBody MemberGroupUpdateReqVO updateReqVO) {
+ groupService.updateGroup(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除用户分组")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('member:group:delete')")
+ public CommonResult deleteGroup(@RequestParam("id") Long id) {
+ groupService.deleteGroup(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得用户分组")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('member:group:query')")
+ public CommonResult getGroup(@RequestParam("id") Long id) {
+ MemberGroupDO group = groupService.getGroup(id);
+ return success(MemberGroupConvert.INSTANCE.convert(group));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "获取会员分组精简信息列表", description = "只包含被开启的会员分组,主要用于前端的下拉选项")
+ public CommonResult> getSimpleGroupList() {
+ // 获用户列表,只要开启状态的
+ List list = groupService.getEnableGroupList();
+ return success(MemberGroupConvert.INSTANCE.convertSimpleList(list));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得用户分组分页")
+ @PreAuthorize("@ss.hasPermission('member:group:query')")
+ public CommonResult> getGroupPage(@Valid MemberGroupPageReqVO pageVO) {
+ PageResult pageResult = groupService.getGroupPage(pageVO);
+ return success(MemberGroupConvert.INSTANCE.convertPage(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupBaseVO.java
new file mode 100644
index 000000000..0519bd968
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupBaseVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.vo;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 用户分组 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberGroupBaseVO {
+
+ @Schema(description = "名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "购物达人")
+ @NotNull(message = "名称不能为空")
+ private String name;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
+ private String remark;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupCreateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupCreateReqVO.java
new file mode 100644
index 000000000..ef3f83343
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupCreateReqVO.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 用户分组创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberGroupCreateReqVO extends MemberGroupBaseVO {
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupPageReqVO.java
new file mode 100644
index 000000000..ae67d5f6c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 用户分组分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberGroupPageReqVO extends PageParam {
+
+ @Schema(description = "名称", example = "购物达人")
+ private String name;
+
+ @Schema(description = "状态", example = "1")
+ private Integer status;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupRespVO.java
new file mode 100644
index 000000000..97365382a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 用户分组 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberGroupRespVO extends MemberGroupBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20357")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupSimpleRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupSimpleRespVO.java
new file mode 100644
index 000000000..ee7d905d0
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupSimpleRespVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 用户分组 Response VO")
+@Data
+@ToString(callSuper = true)
+public class MemberGroupSimpleRespVO {
+
+ @Schema(description = "编号", example = "6103")
+ private Long id;
+
+ @Schema(description = "等级名称", example = "芋艿")
+ private String name;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupUpdateReqVO.java
new file mode 100644
index 000000000..75910883b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupUpdateReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 用户分组更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberGroupUpdateReqVO extends MemberGroupBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20357")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberExperienceRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberExperienceRecordController.java
new file mode 100644
index 000000000..cdbd76046
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberExperienceRecordController.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.member.controller.admin.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.service.level.MemberExperienceRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 会员经验记录")
+@RestController
+@RequestMapping("/member/experience-record")
+@Validated
+public class MemberExperienceRecordController {
+
+ @Resource
+ private MemberExperienceRecordService experienceLogService;
+
+ @GetMapping("/get")
+ @Operation(summary = "获得会员经验记录")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('member:experience-record:query')")
+ public CommonResult getExperienceRecord(@RequestParam("id") Long id) {
+ MemberExperienceRecordDO experienceLog = experienceLogService.getExperienceRecord(id);
+ return success(MemberExperienceRecordConvert.INSTANCE.convert(experienceLog));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得会员经验记录分页")
+ @PreAuthorize("@ss.hasPermission('member:experience-record:query')")
+ public CommonResult> getExperienceRecordPage(
+ @Valid MemberExperienceRecordPageReqVO pageVO) {
+ PageResult pageResult = experienceLogService.getExperienceRecordPage(pageVO);
+ return success(MemberExperienceRecordConvert.INSTANCE.convertPage(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java
new file mode 100644
index 000000000..195800e98
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java
@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.member.controller.admin.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.*;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 会员等级")
+@RestController
+@RequestMapping("/member/level")
+@Validated
+public class MemberLevelController {
+
+ @Resource
+ private MemberLevelService levelService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建会员等级")
+ @PreAuthorize("@ss.hasPermission('member:level:create')")
+ public CommonResult createLevel(@Valid @RequestBody MemberLevelCreateReqVO createReqVO) {
+ return success(levelService.createLevel(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新会员等级")
+ @PreAuthorize("@ss.hasPermission('member:level:update')")
+ public CommonResult updateLevel(@Valid @RequestBody MemberLevelUpdateReqVO updateReqVO) {
+ levelService.updateLevel(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除会员等级")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('member:level:delete')")
+ public CommonResult deleteLevel(@RequestParam("id") Long id) {
+ levelService.deleteLevel(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得会员等级")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('member:level:query')")
+ public CommonResult getLevel(@RequestParam("id") Long id) {
+ MemberLevelDO level = levelService.getLevel(id);
+ return success(MemberLevelConvert.INSTANCE.convert(level));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "获取会员等级精简信息列表", description = "只包含被开启的会员等级,主要用于前端的下拉选项")
+ public CommonResult> getSimpleLevelList() {
+ // 获用户列表,只要开启状态的
+ List list = levelService.getEnableLevelList();
+ // 排序后,返回给前端
+ return success(MemberLevelConvert.INSTANCE.convertSimpleList(list));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得会员等级列表")
+ @PreAuthorize("@ss.hasPermission('member:level:query')")
+ public CommonResult> getLevelList(@Valid MemberLevelListReqVO listReqVO) {
+ List result = levelService.getLevelList(listReqVO);
+ return success(MemberLevelConvert.INSTANCE.convertList(result));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelRecordController.java
new file mode 100644
index 000000000..b54a54da0
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelRecordController.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.member.controller.admin.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 会员等级记录")
+@RestController
+@RequestMapping("/member/level-record")
+@Validated
+public class MemberLevelRecordController {
+
+ @Resource
+ private MemberLevelRecordService levelLogService;
+
+ @GetMapping("/get")
+ @Operation(summary = "获得会员等级记录")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('member:level-record:query')")
+ public CommonResult getLevelRecord(@RequestParam("id") Long id) {
+ MemberLevelRecordDO levelLog = levelLogService.getLevelRecord(id);
+ return success(MemberLevelRecordConvert.INSTANCE.convert(levelLog));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得会员等级记录分页")
+ @PreAuthorize("@ss.hasPermission('member:level-record:query')")
+ public CommonResult> getLevelRecordPage(
+ @Valid MemberLevelRecordPageReqVO pageVO) {
+ PageResult pageResult = levelLogService.getLevelRecordPage(pageVO);
+ return success(MemberLevelRecordConvert.INSTANCE.convertPage(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordBaseVO.java
new file mode 100644
index 000000000..7c71f8270
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordBaseVO.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.experience;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 会员经验记录 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberExperienceRecordBaseVO {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3638")
+ @NotNull(message = "用户编号不能为空")
+ private Long userId;
+
+ @Schema(description = "业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12164")
+ @NotNull(message = "业务编号不能为空")
+ private String bizId;
+
+ @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "业务类型不能为空")
+ private Integer bizType;
+
+ @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "增加经验")
+ @NotNull(message = "标题不能为空")
+ private String title;
+
+ @Schema(description = "经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "经验不能为空")
+ private Integer experience;
+
+ @Schema(description = "变更后的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
+ @NotNull(message = "变更后的经验不能为空")
+ private Integer totalExperience;
+
+ @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "下单增加 100 经验")
+ @NotNull(message = "描述不能为空")
+ private String description;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordPageReqVO.java
new file mode 100644
index 000000000..d18201d7c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordPageReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.experience;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 会员经验记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberExperienceRecordPageReqVO extends PageParam {
+
+ @Schema(description = "用户编号", example = "3638")
+ private Long userId;
+
+ @Schema(description = "业务编号", example = "12164")
+ private String bizId;
+
+ @Schema(description = "业务类型", example = "1")
+ private Integer bizType;
+
+ @Schema(description = "标题", example = "增加经验")
+ private String title;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordRespVO.java
new file mode 100644
index 000000000..5e652fcf0
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/experience/MemberExperienceRecordRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.experience;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 会员经验记录 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberExperienceRecordRespVO extends MemberExperienceRecordBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19610")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelBaseVO.java
new file mode 100644
index 000000000..9580647f8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelBaseVO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Positive;
+
+/**
+ * 会员等级 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberLevelBaseVO {
+
+ @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+ @NotBlank(message = "等级名称不能为空")
+ private String name;
+
+ @Schema(description = "升级经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "升级经验不能为空")
+ @Positive(message = "升级经验必须大于 0")
+ private Integer experience;
+
+ @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "等级不能为空")
+ @Positive(message = "等级必须大于 0")
+ private Integer level;
+
+ @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")
+ @NotNull(message = "享受折扣不能为空")
+ @Range(min = 0, max = 100, message = "享受折扣的范围为 0-100")
+ private Integer discountPercent;
+
+ @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
+ @URL(message = "等级图标必须是 URL 格式")
+ private String icon;
+
+ @Schema(description = "等级背景图", example = "https://www.iocoder.cn/yudao.jpg")
+ @URL(message = "等级背景图必须是 URL 格式")
+ private String backgroundUrl;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelCreateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelCreateReqVO.java
new file mode 100644
index 000000000..f51a7d967
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelCreateReqVO.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
+
+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 MemberLevelCreateReqVO extends MemberLevelBaseVO {
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelListReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelListReqVO.java
new file mode 100644
index 000000000..348e78e8e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelListReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 会员等级列表筛选 Request VO")
+@Data
+@ToString(callSuper = true)
+public class MemberLevelListReqVO {
+
+ @Schema(description = "等级名称", example = "芋艿")
+ private String name;
+
+ @Schema(description = "状态", example = "1")
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelRespVO.java
new file mode 100644
index 000000000..df91a814f
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 会员等级 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberLevelRespVO extends MemberLevelBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6103")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelSimpleRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelSimpleRespVO.java
new file mode 100644
index 000000000..96c515c8b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelSimpleRespVO.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 会员等级 Response VO")
+@Data
+@ToString(callSuper = true)
+public class MemberLevelSimpleRespVO {
+
+ @Schema(description = "编号", example = "6103")
+ private Long id;
+
+ @Schema(description = "等级名称", example = "芋艿")
+ private String name;
+
+ @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
+ private String icon;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelUpdateReqVO.java
new file mode 100644
index 000000000..83ad768de
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/level/MemberLevelUpdateReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.level;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 会员等级更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberLevelUpdateReqVO extends MemberLevelBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6103")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordBaseVO.java
new file mode 100644
index 000000000..99df53648
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordBaseVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 会员等级记录 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberLevelRecordBaseVO {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25923")
+ @NotNull(message = "用户编号不能为空")
+ private Long userId;
+
+ @Schema(description = "等级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25985")
+ @NotNull(message = "等级编号不能为空")
+ private Long levelId;
+
+ @Schema(description = "会员等级", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "会员等级不能为空")
+ private Integer level;
+
+ @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "13319")
+ @NotNull(message = "享受折扣不能为空")
+ private Integer discountPercent;
+
+ @Schema(description = "升级经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "13319")
+ @NotNull(message = "升级经验不能为空")
+ private Integer experience;
+
+ @Schema(description = "会员此时的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "13319")
+ @NotNull(message = "会员此时的经验不能为空")
+ private Integer userExperience;
+
+ @Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "推广需要")
+ @NotNull(message = "备注不能为空")
+ private String remark;
+
+ @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级为金牌会员")
+ @NotNull(message = "描述不能为空")
+ private String description;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordPageReqVO.java
new file mode 100644
index 000000000..2590cfba6
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordPageReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 会员等级记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberLevelRecordPageReqVO extends PageParam {
+
+ @Schema(description = "用户编号", example = "25923")
+ private Long userId;
+
+ @Schema(description = "等级编号", example = "25985")
+ private Long levelId;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordRespVO.java
new file mode 100644
index 000000000..caf98ea4e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 会员等级记录 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberLevelRecordRespVO extends MemberLevelRecordBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8741")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/MemberPointRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/MemberPointRecordController.java
new file mode 100644
index 000000000..4d6ec352b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/MemberPointRecordController.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.member.controller.admin.point;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod.MemberPointRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod.MemberPointRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.point.MemberPointRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.CollectionUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 签到记录")
+@RestController
+@RequestMapping("/member/point/record")
+@Validated
+public class MemberPointRecordController {
+
+ @Resource
+ private MemberPointRecordService pointRecordService;
+
+ @Resource
+ private MemberUserApi memberUserApi;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得用户积分记录分页")
+ @PreAuthorize("@ss.hasPermission('point:record:query')")
+ public CommonResult> getPointRecordPage(@Valid MemberPointRecordPageReqVO pageVO) {
+ // 执行分页查询
+ PageResult pageResult = pointRecordService.getPointRecordPage(pageVO);
+ if (CollectionUtils.isEmpty(pageResult.getList())) {
+ return success(PageResult.empty(pageResult.getTotal()));
+ }
+
+ // 拼接结果返回
+ List users = memberUserApi.getUserList(
+ convertSet(pageResult.getList(), MemberPointRecordDO::getUserId));
+ return success(MemberPointRecordConvert.INSTANCE.convertPage(pageResult, users));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/recrod/MemberPointRecordPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/recrod/MemberPointRecordPageReqVO.java
new file mode 100644
index 000000000..63cc80006
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/recrod/MemberPointRecordPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 用户积分记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberPointRecordPageReqVO extends PageParam {
+
+ @Schema(description = "用户昵称", example = "张三")
+ private String nickname;
+
+ @Schema(description = "用户编号", example = "123")
+ private Long userId;
+
+ @Schema(description = "业务类型", example = "1")
+ private Integer bizType;
+
+ @Schema(description = "积分标题", example = "呵呵")
+ private String title;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/recrod/MemberPointRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/recrod/MemberPointRecordRespVO.java
new file mode 100644
index 000000000..6714aa87f
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/point/vo/recrod/MemberPointRecordRespVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 用户积分记录 Response VO")
+@Data
+public class MemberPointRecordRespVO {
+
+ @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "31457")
+ private Long id;
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long userId;
+
+ @Schema(description = "昵称", example = "张三")
+ private String nickname;
+
+ @Schema(description = "业务编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "22706")
+ private String bizId;
+
+ @Schema(description = "业务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer bizType;
+
+ @Schema(description = "积分标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
+ private String title;
+
+ @Schema(description = "积分描述", example = "你猜")
+ private String description;
+
+ @Schema(description = "积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer point;
+
+ @Schema(description = "变动后的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
+ private Integer totalPoint;
+
+ @Schema(description = "发生时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/MemberSignInConfigController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/MemberSignInConfigController.java
new file mode 100644
index 000000000..65bfe44c2
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/MemberSignInConfigController.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.config.MemberSignInConfigCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.config.MemberSignInConfigRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.config.MemberSignInConfigUpdateReqVO;
+import cn.iocoder.yudao.module.member.convert.signin.MemberSignInConfigConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
+import cn.iocoder.yudao.module.member.service.signin.MemberSignInConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+// TODO 芋艿:url
+@Tag(name = "管理后台 - 签到规则")
+@RestController
+@RequestMapping("/member/sign-in/config")
+@Validated
+public class MemberSignInConfigController {
+
+ @Resource
+ private MemberSignInConfigService signInConfigService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建签到规则")
+ @PreAuthorize("@ss.hasPermission('point:sign-in-config:create')")
+ public CommonResult createSignInConfig(@Valid @RequestBody MemberSignInConfigCreateReqVO createReqVO) {
+ return success(signInConfigService.createSignInConfig(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新签到规则")
+ @PreAuthorize("@ss.hasPermission('point:sign-in-config:update')")
+ public CommonResult updateSignInConfig(@Valid @RequestBody MemberSignInConfigUpdateReqVO updateReqVO) {
+ signInConfigService.updateSignInConfig(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除签到规则")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('point:sign-in-config:delete')")
+ public CommonResult deleteSignInConfig(@RequestParam("id") Long id) {
+ signInConfigService.deleteSignInConfig(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得签到规则")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('point:sign-in-config:query')")
+ public CommonResult getSignInConfig(@RequestParam("id") Long id) {
+ MemberSignInConfigDO signInConfig = signInConfigService.getSignInConfig(id);
+ return success(MemberSignInConfigConvert.INSTANCE.convert(signInConfig));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得签到规则列表")
+ @PreAuthorize("@ss.hasPermission('point:sign-in-config:query')")
+ public CommonResult> getSignInConfigList() {
+ List list = signInConfigService.getSignInConfigList();
+ return success(MemberSignInConfigConvert.INSTANCE.convertList(list));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/MemberSignInRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/MemberSignInRecordController.java
new file mode 100644
index 000000000..83b8f9b3f
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/MemberSignInRecordController.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
+import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.util.CollectionUtils;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "管理后台 - 签到记录")
+@RestController
+@RequestMapping("/member/sign-in/record")
+@Validated
+public class MemberSignInRecordController {
+
+ @Resource
+ private MemberSignInRecordService signInRecordService;
+
+ @Resource
+ private MemberUserApi memberUserApi;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得签到记录分页")
+ @PreAuthorize("@ss.hasPermission('point:sign-in-record:query')")
+ public CommonResult> getSignInRecordPage(@Valid MemberSignInRecordPageReqVO pageVO) {
+ // 执行分页查询
+ PageResult pageResult = signInRecordService.getSignInRecordPage(pageVO);
+ if (CollectionUtils.isEmpty(pageResult.getList())) {
+ return success(PageResult.empty(pageResult.getTotal()));
+ }
+
+ // 拼接结果返回
+ List users = memberUserApi.getUserList(
+ convertSet(pageResult.getList(), MemberSignInRecordDO::getUserId));
+ return success(MemberSignInRecordConvert.INSTANCE.convertPage(pageResult, users));
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigBaseVO.java
new file mode 100644
index 000000000..2ddeeb9be
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigBaseVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin.vo.config;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.PositiveOrZero;
+
+/**
+ * 签到规则 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberSignInConfigBaseVO {
+
+ @Schema(description = "签到第 x 天", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
+ @NotNull(message = "签到天数不能为空")
+ private Integer day;
+
+ @Schema(description = "奖励积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "奖励积分不能为空")
+ @PositiveOrZero(message = "奖励积分不能小于 0")
+ private Integer point;
+
+ @Schema(description = "奖励经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @NotNull(message = "奖励经验不能为空")
+ @PositiveOrZero(message = "奖励经验不能小于 0")
+ private Integer experience;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "状态不能为空")
+ @InEnum(CommonStatusEnum.class)
+ private Integer status;
+
+ @AssertTrue(message = "签到奖励积分和经验不能同时为空")
+ @JsonIgnore
+ public boolean isConfigAward() {
+ return ObjUtil.notEqual(point, 0) || ObjUtil.notEqual(experience, 0);
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigCreateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigCreateReqVO.java
new file mode 100644
index 000000000..7ca03fa93
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigCreateReqVO.java
@@ -0,0 +1,12 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin.vo.config;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+@Schema(description = "管理后台 - 签到规则创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberSignInConfigCreateReqVO extends MemberSignInConfigBaseVO {
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigRespVO.java
new file mode 100644
index 000000000..8d423b25c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin.vo.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 签到规则 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberSignInConfigRespVO extends MemberSignInConfigBaseVO {
+
+ @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "20937")
+ private Integer id;
+
+ @Schema(description = "创建时间")
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigUpdateReqVO.java
new file mode 100644
index 000000000..89b6de15c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/config/MemberSignInConfigUpdateReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin.vo.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.*;
+
+@Schema(description = "管理后台 - 签到规则更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberSignInConfigUpdateReqVO extends MemberSignInConfigBaseVO {
+
+ @Schema(description = "规则自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "13653")
+ @NotNull(message = "规则自增主键不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordPageReqVO.java
new file mode 100644
index 000000000..b46712b6e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordPageReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin.vo.record;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 签到记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberSignInRecordPageReqVO extends PageParam {
+
+ @Schema(description = "签到用户", example = "土豆")
+ private String nickname;
+
+ @Schema(description = "第几天签到", example = "10")
+ private Integer day;
+
+ @Schema(description = "用户编号", example = "123")
+ private Long userId;
+
+ @Schema(description = "签到时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordRespVO.java
new file mode 100644
index 000000000..b5755ba53
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/signin/vo/record/MemberSignInRecordRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.member.controller.admin.signin.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 签到记录 Response VO")
+@Data
+public class MemberSignInRecordRespVO {
+
+ @Schema(description = "签到自增 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "11903")
+ private Long id;
+
+ @Schema(description = "签到用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "6507")
+ private Long userId;
+
+ @Schema(description = "昵称", example = "张三")
+ private String nickname;
+
+ @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer day;
+
+ @Schema(description = "签到的积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer point;
+
+ @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/MemberTagController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/MemberTagController.java
new file mode 100644
index 000000000..34f3c20cc
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/MemberTagController.java
@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.member.controller.admin.tag;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagUpdateReqVO;
+import cn.iocoder.yudao.module.member.convert.tag.MemberTagConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
+import cn.iocoder.yudao.module.member.service.tag.MemberTagService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 会员标签")
+@RestController
+@RequestMapping("/member/tag")
+@Validated
+public class MemberTagController {
+
+ @Resource
+ private MemberTagService tagService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建会员标签")
+ @PreAuthorize("@ss.hasPermission('member:tag:create')")
+ public CommonResult createTag(@Valid @RequestBody MemberTagCreateReqVO createReqVO) {
+ return success(tagService.createTag(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新会员标签")
+ @PreAuthorize("@ss.hasPermission('member:tag:update')")
+ public CommonResult updateTag(@Valid @RequestBody MemberTagUpdateReqVO updateReqVO) {
+ tagService.updateTag(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除会员标签")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('member:tag:delete')")
+ public CommonResult deleteTag(@RequestParam("id") Long id) {
+ tagService.deleteTag(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得会员标签")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('member:tag:query')")
+ public CommonResult getMemberTag(@RequestParam("id") Long id) {
+ MemberTagDO tag = tagService.getTag(id);
+ return success(MemberTagConvert.INSTANCE.convert(tag));
+ }
+
+ @GetMapping("/list-all-simple")
+ @Operation(summary = "获取会员标签精简信息列表", description = "只包含被开启的会员标签,主要用于前端的下拉选项")
+ public CommonResult> getSimpleTagList() {
+ // 获用户列表,只要开启状态的
+ List list = tagService.getTagList();
+ // 排序后,返回给前端
+ return success(MemberTagConvert.INSTANCE.convertList(list));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得会员标签列表")
+ @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
+ @PreAuthorize("@ss.hasPermission('member:tag:query')")
+ public CommonResult> getMemberTagList(@RequestParam("ids") Collection ids) {
+ List list = tagService.getTagList(ids);
+ return success(MemberTagConvert.INSTANCE.convertList(list));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得会员标签分页")
+ @PreAuthorize("@ss.hasPermission('member:tag:query')")
+ public CommonResult> getTagPage(@Valid MemberTagPageReqVO pageVO) {
+ PageResult pageResult = tagService.getTagPage(pageVO);
+ return success(MemberTagConvert.INSTANCE.convertPage(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagBaseVO.java
new file mode 100644
index 000000000..bc0efea68
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagBaseVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.controller.admin.tag.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 会员标签 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberTagBaseVO {
+
+ @Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+ @NotNull(message = "标签名称不能为空")
+ private String name;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagCreateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagCreateReqVO.java
new file mode 100644
index 000000000..b61f26bb2
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagCreateReqVO.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.member.controller.admin.tag.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 会员标签创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberTagCreateReqVO extends MemberTagBaseVO {
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagPageReqVO.java
new file mode 100644
index 000000000..99f59b068
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagPageReqVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.member.controller.admin.tag.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 会员标签分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberTagPageReqVO extends PageParam {
+
+ @Schema(description = "标签名称", example = "李四")
+ private String name;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagRespVO.java
new file mode 100644
index 000000000..2c21f53e3
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagRespVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.tag.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 会员标签 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberTagRespVO extends MemberTagBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "907")
+ private Long id;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagUpdateReqVO.java
new file mode 100644
index 000000000..2fe0e614d
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/tag/vo/MemberTagUpdateReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.member.controller.admin.tag.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 会员标签更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberTagUpdateReqVO extends MemberTagBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "907")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
new file mode 100644
index 000000000..b382c1caf
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.module.member.controller.admin.user;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.*;
+import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
+import cn.iocoder.yudao.module.member.service.tag.MemberTagService;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - 会员用户")
+@RestController
+@RequestMapping("/member/user")
+@Validated
+public class MemberUserController {
+
+ @Resource
+ private MemberUserService memberUserService;
+ @Resource
+ private MemberTagService memberTagService;
+ @Resource
+ private MemberLevelService memberLevelService;
+ @Resource
+ private MemberGroupService memberGroupService;
+ @Resource
+ private MemberPointRecordService memberPointRecordService;
+
+ @PutMapping("/update")
+ @Operation(summary = "更新会员用户")
+ @PreAuthorize("@ss.hasPermission('member:user:update')")
+ public CommonResult updateUser(@Valid @RequestBody MemberUserUpdateReqVO updateReqVO) {
+ memberUserService.updateUser(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-level")
+ @Operation(summary = "更新会员用户等级")
+ @PreAuthorize("@ss.hasPermission('member:user:update-level')")
+ public CommonResult updateUserLevel(@Valid @RequestBody MemberUserUpdateLevelReqVO updateReqVO) {
+ memberLevelService.updateUserLevel(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-point")
+ @Operation(summary = "更新会员用户积分")
+ @PreAuthorize("@ss.hasPermission('member:user:update-point')")
+ public CommonResult updateUserPoint(@Valid @RequestBody MemberUserUpdatePointReqVO updateReqVO) {
+ memberPointRecordService.createPointRecord(updateReqVO.getId(), updateReqVO.getPoint(),
+ MemberPointBizTypeEnum.ADMIN, String.valueOf(getLoginUserId()));
+ return success(true);
+ }
+
+ @PutMapping("/update-balance")
+ @Operation(summary = "更新会员用户余额")
+ @PreAuthorize("@ss.hasPermission('member:user:update-balance')")
+ public CommonResult updateUserBalance(@Valid @RequestBody Long id) {
+ // todo @jason:增加一个【修改余额】
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得会员用户")
+ @Parameter(name = "id", description = "编号", required = true, example = "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")
+ @Operation(summary = "获得会员用户分页")
+ @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());
+ List tags = memberTagService.getTagList(tagIds);
+ // 处理用户级别返显
+ List levels = memberLevelService.getLevelList(
+ convertSet(pageResult.getList(), MemberUserDO::getLevelId));
+ // 处理用户分组返显
+ List groups = memberGroupService.getGroupList(
+ convertSet(pageResult.getList(), MemberUserDO::getGroupId));
+ return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags, levels, groups));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java
new file mode 100644
index 000000000..e20963e5e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+
+/**
+ * 会员用户 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class MemberUserBaseVO {
+
+ @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+ @NotNull(message = "手机号不能为空")
+ private String mobile;
+
+ @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "状态不能为空")
+ private Byte status;
+
+ @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+ @NotNull(message = "用户昵称不能为空")
+ private String nickname;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/x.png")
+ @URL(message = "头像必须是 URL 格式")
+ private String avatar;
+
+ @Schema(description = "用户昵称", example = "李四")
+ private String name;
+
+ @Schema(description = "用户性别", example = "1")
+ private Byte sex;
+
+ @Schema(description = "所在地编号", example = "4371")
+ private Long areaId;
+
+ @Schema(description = "所在地全程", example = "上海上海市普陀区")
+ private String areaName;
+
+ @Schema(description = "出生日期", example = "2023-03-12")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+ private LocalDateTime birthday;
+
+ @Schema(description = "会员备注", example = "我是小备注")
+ private String mark;
+
+ @Schema(description = "会员标签", example = "[1, 2]")
+ private List tagIds;
+
+ @Schema(description = "会员等级编号", example = "1")
+ private Long levelId;
+
+ @Schema(description = "用户分组编号", example = "1")
+ private Long groupId;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java
new file mode 100644
index 000000000..abb94285e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 会员用户分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberUserPageReqVO extends PageParam {
+
+ @Schema(description = "手机号", example = "15601691300")
+ private String mobile;
+
+ @Schema(description = "用户昵称", example = "李四")
+ private String nickname;
+
+ @Schema(description = "最后登录时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] loginDate;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+ @Schema(description = "会员标签编号列表", example = "[1, 2]")
+ private List tagIds;
+
+ @Schema(description = "会员等级编号", example = "1")
+ private Long levelId;
+
+ @Schema(description = "用户分组编号", example = "1")
+ private Long groupId;
+
+ // TODO 芋艿:注册用户类型;
+
+ // TODO 芋艿:登录用户类型;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java
new file mode 100644
index 000000000..1cd228335
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 会员用户 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberUserRespVO extends MemberUserBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+ private Long id;
+
+ @Schema(description = "注册 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
+ private String registerIp;
+
+ @Schema(description = "最后登录IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
+ private String loginIp;
+
+ @Schema(description = "最后登录时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime loginDate;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+ // ========== 其它信息 ==========
+
+ @Schema(description = "积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer point;
+
+ @Schema(description = "总积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
+ private Integer totalPoint;
+
+ @Schema(description = "会员标签", example = "[红色, 快乐]")
+ private List tagNames;
+
+ @Schema(description = "会员等级", example = "黄金会员")
+ private String levelName;
+
+ @Schema(description = "用户分组", example = "购物达人")
+ private String groupName;
+
+ @Schema(description = "用户经验值", requiredMode = Schema.RequiredMode.REQUIRED, example = "200")
+ private Integer experience;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java
new file mode 100644
index 000000000..dba48f670
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateLevelReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 用户修改等级 Request VO")
+@Data
+@ToString(callSuper = true)
+public class MemberUserUpdateLevelReqVO {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+ @NotNull(message = "用户编号不能为空")
+ private Long id;
+
+ /**
+ * 取消用户等级时,值为空
+ */
+ @Schema(description = "用户等级编号", example = "1")
+ private Long levelId;
+
+ @Schema(description = "修改原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "推广需要")
+ @NotBlank(message = "修改原因不能为空")
+ private String reason;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java
new file mode 100644
index 000000000..a072c0726
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdatePointReqVO.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 用户修改积分 Request VO")
+@Data
+@ToString(callSuper = true)
+public class MemberUserUpdatePointReqVO {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+ @NotNull(message = "用户编号不能为空")
+ private Long id;
+
+ @Schema(description = "变动积分,正数为增加,负数为减少", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "变动积分不能为空")
+ private Integer point;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateReqVO.java
new file mode 100644
index 000000000..c6a92758d
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateReqVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.member.controller.admin.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 会员用户更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MemberUserUpdateReqVO extends MemberUserBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23788")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.http b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.http
new file mode 100644
index 000000000..6bae7c7eb
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.http
@@ -0,0 +1,54 @@
+### 请求 /create 接口 => 成功
+POST {{appApi}}//member/address/create
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+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: {{appTenentId}}
+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: {{appTenentId}}
+Authorization: Bearer {{appToken}}
+
+### 请求 /get 接口 => 成功
+GET {{appApi}}//member/address/get?id=1
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+Authorization: Bearer {{appToken}}
+
+### 请求 /get-default 接口 => 成功
+GET {{appApi}}//member/address/get-default
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+Authorization: Bearer {{appToken}}
+
+### 请求 /list 接口 => 成功
+GET {{appApi}}//member/address/list
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+Authorization: Bearer {{appToken}}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java
new file mode 100644
index 000000000..7ba55c3bd
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java
@@ -0,0 +1,82 @@
+package cn.iocoder.yudao.module.member.controller.app.address;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO;
+import cn.iocoder.yudao.module.member.service.address.AddressService;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 APP - 用户收件地址")
+@RestController
+@RequestMapping("/member/address")
+@Validated
+public class AppAddressController {
+
+ @Resource
+ private AddressService addressService;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建用户收件地址")
+ @PreAuthenticated
+ public CommonResult createAddress(@Valid @RequestBody AppAddressCreateReqVO createReqVO) {
+ return success(addressService.createAddress(getLoginUserId(), createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新用户收件地址")
+ @PreAuthenticated
+ public CommonResult updateAddress(@Valid @RequestBody AppAddressUpdateReqVO updateReqVO) {
+ addressService.updateAddress(getLoginUserId(), updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除用户收件地址")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthenticated
+ public CommonResult deleteAddress(@RequestParam("id") Long id) {
+ addressService.deleteAddress(getLoginUserId(), id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得用户收件地址")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthenticated
+ public CommonResult getAddress(@RequestParam("id") Long id) {
+ MemberAddressDO address = addressService.getAddress(getLoginUserId(), id);
+ return success(AddressConvert.INSTANCE.convert(address));
+ }
+
+ @GetMapping("/get-default")
+ @Operation(summary = "获得默认的用户收件地址")
+ @PreAuthenticated
+ public CommonResult getDefaultUserAddress() {
+ MemberAddressDO address = addressService.getDefaultUserAddress(getLoginUserId());
+ return success(AddressConvert.INSTANCE.convert(address));
+ }
+
+ @GetMapping("/list")
+ @Operation(summary = "获得用户收件地址列表")
+ @PreAuthenticated
+ public CommonResult> getAddressList() {
+ List list = addressService.getAddressList(getLoginUserId());
+ return success(AddressConvert.INSTANCE.convertList(list));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressBaseVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressBaseVO.java
new file mode 100644
index 000000000..076ce36ff
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressBaseVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.member.controller.app.address.vo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+// TODO 芋艿:example 缺失
+/**
+* 用户收件地址 Base VO,提供给添加、修改、详细的子 VO 使用
+* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+*/
+@Data
+public class AppAddressBaseVO {
+
+ @Schema(description = "收件人名称", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "收件人名称不能为空")
+ private String name;
+
+ @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "手机号不能为空")
+ private String mobile;
+
+ @Schema(description = "地区编号", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "地区编号不能为空")
+ private Long areaId;
+
+ @Schema(description = "收件详细地址", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "收件详细地址不能为空")
+ private String detailAddress;
+
+ @Schema(description = "是否默认地址", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "是否默认地址不能为空")
+ private Boolean defaultStatus;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressCreateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressCreateReqVO.java
new file mode 100644
index 000000000..c92687f27
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressCreateReqVO.java
@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.module.member.controller.app.address.vo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+@Schema(description = "用户 APP - 用户收件地址创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppAddressCreateReqVO extends AppAddressBaseVO {
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressRespVO.java
new file mode 100644
index 000000000..d3e2f9ffd
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressRespVO.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.member.controller.app.address.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "用户 APP - 用户收件地址 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppAddressRespVO extends AppAddressBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long id;
+
+ @Schema(description = "地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海上海市普陀区")
+ private String areaName;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressUpdateReqVO.java
new file mode 100644
index 000000000..19b58d807
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/vo/AppAddressUpdateReqVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.member.controller.app.address.vo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import javax.validation.constraints.*;
+
+@Schema(description = "用户 APP - 用户收件地址更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppAddressUpdateReqVO extends AppAddressBaseVO {
+
+ @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotNull(message = "编号不能为空")
+ private Long id;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
new file mode 100644
index 000000000..998c70c21
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
@@ -0,0 +1,66 @@
+### 请求 /login 接口 => 成功
+POST {{appApi}}/member/auth/login
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+{
+ "mobile": "15601691300",
+ "password": "admin123"
+}
+
+### 请求 /send-sms-code 接口 => 成功
+POST {{appApi}}/member/auth/send-sms-code
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+{
+ "mobile": "15601691399",
+ "scene": 1
+}
+
+### 请求 /sms-login 接口 => 成功
+POST {{appApi}}/member/auth/sms-login
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+{
+ "mobile": "15601691301",
+ "code": 9999
+}
+
+### 请求 /social-login 接口 => 成功
+POST {{appApi}}/member/auth/social-login
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+{
+ "type": 34,
+ "code": "0e1oc9000CTjFQ1oim200bhtb61oc90g",
+ "state": "default"
+}
+
+### 请求 /weixin-mini-app-login 接口 => 成功
+POST {{appApi}}/member/auth/weixin-mini-app-login
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+{
+ "phoneCode": "618e6412e0c728f5b8fc7164497463d0158a923c9e7fd86af8bba393b9decbc5",
+ "loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR"
+}
+
+### 请求 /logout 接口 => 成功
+POST {{appApi}}/member/auth/logout
+Content-Type: application/json
+Authorization: Bearer c1b76bdaf2c146c581caa4d7fd81ee66
+tenant-id: {{appTenentId}}
+
+### 请求 /auth/refresh-token 接口 => 成功
+POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
+Content-Type: application/json
+tenant-id: {{appTenentId}}
+
+### 请求 /auth/create-weixin-jsapi-signature 接口 => 成功
+POST {{appApi}}/member/auth/create-weixin-jsapi-signature?url=http://www.iocoder.cn
+Authorization: Bearer {{appToken}}
+tenant-id: {{appTenentId}}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
new file mode 100644
index 000000000..ed3963e90
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.java
@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.module.member.controller.app.auth;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.security.config.SecurityProperties;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
+import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
+import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
+import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 APP - 认证")
+@RestController
+@RequestMapping("/member/auth")
+@Validated
+@Slf4j
+public class AppAuthController {
+
+ @Resource
+ private MemberAuthService authService;
+
+ @Resource
+ private SocialClientApi socialClientApi;
+
+ @Resource
+ private SecurityProperties securityProperties;
+
+ @PostMapping("/login")
+ @Operation(summary = "使用手机 + 密码登录")
+ public CommonResult login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
+ return success(authService.login(reqVO));
+ }
+
+ @PostMapping("/logout")
+ @PermitAll
+ @Operation(summary = "登出系统")
+ public CommonResult logout(HttpServletRequest request) {
+ String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
+ if (StrUtil.isNotBlank(token)) {
+ authService.logout(token);
+ }
+ return success(true);
+ }
+
+ @PostMapping("/refresh-token")
+ @Operation(summary = "刷新令牌")
+ @Parameter(name = "refreshToken", description = "刷新令牌", required = true)
+ @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
+ public CommonResult refreshToken(@RequestParam("refreshToken") String refreshToken) {
+ return success(authService.refreshToken(refreshToken));
+ }
+
+ // ========== 短信登录相关 ==========
+
+ @PostMapping("/sms-login")
+ @Operation(summary = "使用手机 + 验证码登录")
+ public CommonResult smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO,
+ @RequestHeader Integer terminal) {
+ return success(authService.smsLogin(reqVO, terminal));
+ }
+
+ @PostMapping("/send-sms-code")
+ @Operation(summary = "发送手机验证码")
+ public CommonResult sendSmsCode(@RequestBody @Valid AppAuthSmsSendReqVO reqVO) {
+ authService.sendSmsCode(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ @PostMapping("/validate-sms-code")
+ @Operation(summary = "校验手机验证码")
+ public CommonResult validateSmsCode(@RequestBody @Valid AppAuthSmsValidateReqVO reqVO) {
+ authService.validateSmsCode(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ // ========== 社交登录相关 ==========
+
+ @GetMapping("/social-auth-redirect")
+ @Operation(summary = "社交授权的跳转")
+ @Parameters({
+ @Parameter(name = "type", description = "社交类型", required = true),
+ @Parameter(name = "redirectUri", description = "回调路径")
+ })
+ public CommonResult socialAuthRedirect(@RequestParam("type") Integer type,
+ @RequestParam("redirectUri") String redirectUri) {
+ return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));
+ }
+
+ @PostMapping("/social-login")
+ @Operation(summary = "社交快捷登录,使用 code 授权码", description = "适合未登录的用户,但是社交账号已绑定用户")
+ public CommonResult socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {
+ return success(authService.socialLogin(reqVO));
+ }
+
+ @PostMapping("/weixin-mini-app-login")
+ @Operation(summary = "微信小程序的一键登录")
+ public CommonResult weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) {
+ return success(authService.weixinMiniAppLogin(reqVO));
+ }
+
+ @PostMapping("/create-weixin-jsapi-signature")
+ @Operation(summary = "创建微信 JS SDK 初始化所需的签名",
+ description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
+ public CommonResult createWeixinMpJsapiSignature(@RequestParam("url") String url) {
+ SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature(
+ UserTypeEnum.MEMBER.getValue(), url);
+ return success(AuthConvert.INSTANCE.convert(signature));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java
new file mode 100644
index 000000000..eee7062cb
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+// TODO 芋艿:code review 相关逻辑
+@Schema(description = "用户 APP - 校验验证码 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppAuthCheckCodeReqVO {
+
+ @Schema(description = "手机号", example = "15601691234")
+ @NotBlank(message = "手机号不能为空")
+ @Mobile
+ private String mobile;
+
+ @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotBlank(message = "手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String code;
+
+ @Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
+ @NotNull(message = "发送场景不能为空")
+ @InEnum(SmsSceneEnum.class)
+ private Integer scene;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java
new file mode 100644
index 000000000..e64209de8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "用户 APP - 手机 + 密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppAuthLoginReqVO {
+
+ @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+ @NotEmpty(message = "手机号不能为空")
+ @Mobile
+ private String mobile;
+
+ @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
+ @NotEmpty(message = "密码不能为空")
+ @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+ private String password;
+
+ // ========== 绑定社交登录时,需要传递如下参数 ==========
+
+ @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @InEnum(SocialTypeEnum.class)
+ private Integer socialType;
+
+ @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private String socialCode;
+
+ @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+ 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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
new file mode 100644
index 000000000..072ec9e4b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 APP - 登录 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppAuthLoginRespVO {
+
+ @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long userId;
+
+ @Schema(description = "访问令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "happy")
+ private String accessToken;
+
+ @Schema(description = "刷新令牌", requiredMode = Schema.RequiredMode.REQUIRED, example = "nice")
+ private String refreshToken;
+
+ @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime expiresTime;
+
+ /**
+ * 仅社交登录、社交绑定时会返回
+ *
+ * 为什么需要返回?微信公众号、微信小程序支付需要传递 openid 给支付接口
+ */
+ @Schema(description = "社交用户 openid", example = "qq768")
+ private String openid;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java
new file mode 100644
index 000000000..8225269a3
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "用户 APP - 手机 + 验证码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppAuthSmsLoginReqVO {
+
+ @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+ @NotEmpty(message = "手机号不能为空")
+ @Mobile
+ private String mobile;
+
+ @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String code;
+
+ // ========== 绑定社交登录时,需要传递如下参数 ==========
+
+ @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @InEnum(SocialTypeEnum.class)
+ private Integer socialType;
+
+ @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private String socialCode;
+
+ @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+ 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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java
new file mode 100644
index 000000000..5f4b030f3
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "用户 APP - 发送手机验证码 Request VO")
+@Data
+@Accessors(chain = true)
+public class AppAuthSmsSendReqVO {
+
+ @Schema(description = "手机号", example = "15601691234")
+ @Mobile
+ private String mobile;
+
+ @Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
+ @NotNull(message = "发送场景不能为空")
+ @InEnum(SmsSceneEnum.class)
+ private Integer scene;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsValidateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsValidateReqVO.java
new file mode 100644
index 000000000..1a57be74b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSmsValidateReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "用户 APP - 校验手机验证码 Request VO")
+@Data
+@Accessors(chain = true)
+public class AppAuthSmsValidateReqVO {
+
+ @Schema(description = "手机号", example = "15601691234")
+ @Mobile
+ private String mobile;
+
+ @Schema(description = "发送场景,对应 SmsSceneEnum 枚举", example = "1")
+ @NotNull(message = "发送场景不能为空")
+ @InEnum(SmsSceneEnum.class)
+ private Integer scene;
+
+ @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String code;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java
new file mode 100644
index 000000000..d3bac4799
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "用户 APP - 社交快捷登录 Request VO,使用 code 授权码")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppAuthSocialLoginReqVO {
+
+ @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @InEnum(SocialTypeEnum.class)
+ @NotNull(message = "社交平台的类型不能为空")
+ private Integer type;
+
+ @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "授权码不能为空")
+ private String code;
+
+ @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+ @NotEmpty(message = "state 不能为空")
+ private String state;
+
+}
\ No newline at end of file
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java
new file mode 100644
index 000000000..11dcdf957
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "用户 APP - 微信小程序手机登录 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppAuthWeixinMiniAppLoginReqVO {
+
+ @Schema(description = "手机 code,小程序通过 wx.getPhoneNumber 方法获得", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
+ @NotEmpty(message = "手机 code 不能为空")
+ private String phoneCode;
+
+ @Schema(description = "登录 code,小程序通过 wx.login 方法获得", requiredMode = Schema.RequiredMode.REQUIRED, example = "word")
+ @NotEmpty(message = "登录 code 不能为空")
+ private String loginCode;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java
new file mode 100644
index 000000000..37e63652b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/vo/AuthWeixinJsapiSignatureRespVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.controller.app.auth.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Schema(description = "用户 APP - 微信公众号 JSAPI 签名 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AuthWeixinJsapiSignatureRespVO {
+
+ @Schema(description = "微信公众号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
+ private String appId;
+
+ @Schema(description = "匿名串", requiredMode = Schema.RequiredMode.REQUIRED, example = "world")
+ private String nonceStr;
+
+ @Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Long timestamp;
+
+ @Schema(description = "URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+ private String url;
+
+ @Schema(description = "签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "阿巴阿巴")
+ private String signature;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberExperienceRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberExperienceRecordController.java
new file mode 100644
index 000000000..5c33e5c1b
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberExperienceRecordController.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.member.controller.app.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.experience.AppMemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.service.level.MemberExperienceRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 App - 会员经验记录")
+@RestController
+@RequestMapping("/member/experience-record")
+@Validated
+public class AppMemberExperienceRecordController {
+
+ @Resource
+ private MemberExperienceRecordService experienceLogService;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得会员经验记录分页")
+ @PreAuthenticated
+ public CommonResult> getExperienceRecordPage(
+ @Valid PageParam pageParam) {
+ PageResult pageResult = experienceLogService.getExperienceRecordPage(
+ getLoginUserId(), pageParam);
+ return success(MemberExperienceRecordConvert.INSTANCE.convertPage02(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberLevelController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberLevelController.java
new file mode 100644
index 000000000..d4a4483af
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberLevelController.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.member.controller.app.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.level.AppMemberLevelRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 App - 会员等级")
+@RestController
+@RequestMapping("/member/level")
+@Validated
+public class AppMemberLevelController {
+
+ @Resource
+ private MemberLevelService levelService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得会员等级列表")
+ public CommonResult> getLevelList() {
+ List result = levelService.getEnableLevelList();
+ return success(MemberLevelConvert.INSTANCE.convertList02(result));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/experience/AppMemberExperienceRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/experience/AppMemberExperienceRecordRespVO.java
new file mode 100644
index 000000000..e2d7bb0c3
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/experience/AppMemberExperienceRecordRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.member.controller.app.level.vo.experience;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 会员经验记录 Response VO")
+@Data
+public class AppMemberExperienceRecordRespVO {
+
+ @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "增加经验")
+ private String title;
+
+ @Schema(description = "经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer experience;
+
+ @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "下单增加 100 经验")
+ private String description;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/level/AppMemberLevelRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/level/AppMemberLevelRespVO.java
new file mode 100644
index 000000000..fdade172f
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/level/AppMemberLevelRespVO.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.controller.app.level.vo.level;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 会员等级 Response VO")
+@Data
+public class AppMemberLevelRespVO {
+
+ @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+ private String name;
+
+ @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer level;
+
+ @Schema(description = "升级经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer experience;
+
+ @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")
+ private Integer discountPercent;
+
+ @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
+ private String icon;
+
+ @Schema(description = "等级背景图", example = "https://www.iocoder.cn/yudao.jpg")
+ private String backgroundUrl;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/AppMemberPointRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/AppMemberPointRecordController.java
new file mode 100644
index 000000000..1e63cc7d4
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/AppMemberPointRecordController.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.member.controller.app.point;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.point.vo.AppMemberPointRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.point.MemberPointRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
+import cn.iocoder.yudao.module.member.service.point.MemberPointRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 App - 签到记录")
+@RestController
+@RequestMapping("/member/point/record")
+@Validated
+public class AppMemberPointRecordController {
+
+ @Resource
+ private MemberPointRecordService pointRecordService;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得用户积分记录分页")
+ @PreAuthenticated
+ public CommonResult> getPointRecordPage(@Valid PageParam pageVO) {
+ PageResult pageResult = pointRecordService.getPointRecordPage(getLoginUserId(), pageVO);
+ return success(MemberPointRecordConvert.INSTANCE.convertPage02(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/vo/AppMemberPointRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/vo/AppMemberPointRecordRespVO.java
new file mode 100644
index 000000000..ec95b2e02
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/vo/AppMemberPointRecordRespVO.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.member.controller.app.point.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 用户积分记录 Response VO")
+@Data
+public class AppMemberPointRecordRespVO {
+
+ @Schema(description = "自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "31457")
+ private Long id;;
+
+ @Schema(description = "积分标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "你猜")
+ private String title;
+
+ @Schema(description = "积分描述", example = "你猜")
+ private String description;
+
+ @Schema(description = "积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer point;
+
+ @Schema(description = "发生时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInConfigController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInConfigController.java
new file mode 100644
index 000000000..62a52e3d8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInConfigController.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.controller.app.signin;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.app.signin.vo.config.AppMemberSignInConfigRespVO;
+import cn.iocoder.yudao.module.member.convert.signin.MemberSignInConfigConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
+import cn.iocoder.yudao.module.member.service.signin.MemberSignInConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 App - 签到规则")
+@RestController
+@RequestMapping("/member/sign-in/config")
+@Validated
+public class AppMemberSignInConfigController {
+
+ @Resource
+ private MemberSignInConfigService signInConfigService;
+
+ @GetMapping("/list")
+ @Operation(summary = "获得签到规则列表")
+ public CommonResult> getSignInConfigList() {
+ List pageResult = signInConfigService.getSignInConfigList(CommonStatusEnum.ENABLE.getStatus());
+ return success(MemberSignInConfigConvert.INSTANCE.convertList02(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java
new file mode 100644
index 000000000..2f7afa042
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.member.controller.app.signin;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordSummaryRespVO;
+import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
+import cn.iocoder.yudao.module.member.service.signin.MemberSignInRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.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 javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "管理后台 - 签到记录")
+@RestController
+@RequestMapping("/member/sign-in/record")
+@Validated
+public class AppMemberSignInRecordController {
+
+ @Resource
+ private MemberSignInRecordService signInRecordService;
+
+ @GetMapping("/get-summary")
+ @Operation(summary = "获得个人签到统计")
+ @PreAuthenticated
+ public CommonResult getSignInRecordSummary() {
+ return success(signInRecordService.getSignInRecordSummary(getLoginUserId()));
+ }
+
+ @PostMapping("/create")
+ @Operation(summary = "签到")
+ @PreAuthenticated
+ public CommonResult createSignInRecord() {
+ MemberSignInRecordDO recordDO = signInRecordService.createSignRecord(getLoginUserId());
+ return success(MemberSignInRecordConvert.INSTANCE.coverRecordToAppRecordVo(recordDO));
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得签到记录分页")
+ @PreAuthenticated
+ public CommonResult> getSignRecordPage(PageParam pageParam) {
+ PageResult pageResult = signInRecordService.getSignRecordPage(getLoginUserId(), pageParam);
+ return success(MemberSignInRecordConvert.INSTANCE.convertPage02(pageResult));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/config/AppMemberSignInConfigRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/config/AppMemberSignInConfigRespVO.java
new file mode 100644
index 000000000..a18d3a28e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/config/AppMemberSignInConfigRespVO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.member.controller.app.signin.vo.config;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 签到规则 Response VO")
+@Data
+public class AppMemberSignInConfigRespVO {
+
+ @Schema(description = "签到第 x 天", requiredMode = Schema.RequiredMode.REQUIRED, example = "7")
+ private Integer day;
+
+ @Schema(description = "奖励积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer point;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordRespVO.java
new file mode 100644
index 000000000..2d910d0c6
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.member.controller.app.signin.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 签到记录 Response VO")
+@Data
+public class AppMemberSignInRecordRespVO {
+
+ @Schema(description = "第几天签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer day;
+
+ @Schema(description = "签到的分数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer point;
+
+ @Schema(description = "签到的经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer experience;
+
+ @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordSummaryRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordSummaryRespVO.java
new file mode 100644
index 000000000..30fb66a15
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/vo/record/AppMemberSignInRecordSummaryRespVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.controller.app.signin.vo.record;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 个人签到统计 Response VO")
+@Data
+public class AppMemberSignInRecordSummaryRespVO {
+
+ @Schema(description = "总签到天数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer totalDay;
+
+ @Schema(description = "连续签到第 x 天", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
+ private Integer continuousDay;
+
+ @Schema(description = "今天是否已签到", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean todaySignIn;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java
new file mode 100644
index 000000000..fd5e3fa83
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.member.controller.app.social;
+
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
+import cn.iocoder.yudao.module.member.convert.social.SocialUserConvert;
+import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 App - 社交用户")
+@RestController
+@RequestMapping("/system/social-user")
+@Validated
+public class AppSocialUserController {
+
+ @Resource
+ private SocialUserApi socialUserApi;
+
+ @PostMapping("/bind")
+ @Operation(summary = "社交绑定,使用 code 授权码")
+ public CommonResult socialBind(@RequestBody @Valid AppSocialUserBindReqVO reqVO) {
+ socialUserApi.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
+ return CommonResult.success(true);
+ }
+
+ @DeleteMapping("/unbind")
+ @Operation(summary = "取消社交绑定")
+ @PreAuthenticated
+ public CommonResult socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {
+ socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
+ return CommonResult.success(true);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java
new file mode 100644
index 000000000..49141f935
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.member.controller.app.social.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "用户 APP - 社交绑定 Request VO,使用 code 授权码")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppSocialUserBindReqVO {
+
+ @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @InEnum(SocialTypeEnum.class)
+ @NotNull(message = "社交平台的类型不能为空")
+ private Integer type;
+
+ @Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "授权码不能为空")
+ private String code;
+
+ @Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
+ @NotEmpty(message = "state 不能为空")
+ private String state;
+
+}
\ No newline at end of file
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java
new file mode 100644
index 000000000..159cdcd33
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.member.controller.app.social.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "用户 APP - 取消社交绑定 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppSocialUserUnbindReqVO {
+
+ @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ @InEnum(SocialTypeEnum.class)
+ @NotNull(message = "社交平台的类型不能为空")
+ private Integer type;
+
+ @Schema(description = "社交用户的 openid", requiredMode = Schema.RequiredMode.REQUIRED, example = "IPRmJ0wvBptiPIlGEZiPewGwiEiE")
+ @NotEmpty(message = "社交用户的 openid 不能为空")
+ private String openid;
+
+}
\ No newline at end of file
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.http b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.http
new file mode 100644
index 000000000..745556f75
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.http
@@ -0,0 +1,4 @@
+### 请求 /member/user/profile/get 接口 => 没有权限
+GET {{appApi}}/member/user/get
+Authorization: Bearer test245
+tenant-id: {{appTenentId}}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
new file mode 100644
index 000000000..9322f9146
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
@@ -0,0 +1,76 @@
+package cn.iocoder.yudao.module.member.controller.app.user;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.user.vo.*;
+import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 APP - 用户个人中心")
+@RestController
+@RequestMapping("/member/user")
+@Validated
+@Slf4j
+public class AppMemberUserController {
+
+ @Resource
+ private MemberUserService userService;
+ @Resource
+ private MemberLevelService levelService;
+
+ @GetMapping("/get")
+ @Operation(summary = "获得基本信息")
+ @PreAuthenticated
+ public CommonResult getUserInfo() {
+ MemberUserDO user = userService.getUser(getLoginUserId());
+ MemberLevelDO level = levelService.getLevel(user.getLevelId());
+ return success(MemberUserConvert.INSTANCE.convert(user, level));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "修改基本信息")
+ @PreAuthenticated
+ public CommonResult updateUser(@RequestBody @Valid AppMemberUserUpdateReqVO reqVO) {
+ userService.updateUser(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-mobile")
+ @Operation(summary = "修改用户手机")
+ @PreAuthenticated
+ public CommonResult updateUserMobile(@RequestBody @Valid AppMemberUserUpdateMobileReqVO reqVO) {
+ userService.updateUserMobile(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ @PutMapping("/update-password")
+ @Operation(summary = "修改用户密码", description = "用户修改密码时使用")
+ @PreAuthenticated
+ public CommonResult updatePassword(@RequestBody @Valid AppMemberUserUpdatePasswordReqVO reqVO) {
+ userService.updateUserPassword(getLoginUserId(), reqVO);
+ return success(true);
+ }
+
+ @PutMapping("/reset-password")
+ @Operation(summary = "重置密码", description = "用户忘记密码时使用")
+ public CommonResult resetPassword(@RequestBody @Valid AppMemberUserResetPasswordReqVO reqVO) {
+ userService.resetUserPassword(reqVO);
+ return success(true);
+ }
+
+}
+
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
new file mode 100644
index 000000000..25cceedc2
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.member.controller.app.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Schema(description = "用户 APP - 用户个人信息 Response VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AppMemberUserInfoRespVO {
+
+ @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+ private String nickname;
+
+ @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xxx.png")
+ private String avatar;
+
+ @Schema(description = "用户手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300")
+ private String mobile;
+
+ @Schema(description = "积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private Integer point;
+
+ @Schema(description = "经验值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ private Integer experience;
+
+ @Schema(description = "用户等级")
+ private Level level;
+
+ @Schema(description = "是否成为推广员", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
+ private Boolean brokerageEnabled;
+
+ @Schema(description = "用户 App - 会员等级")
+ @Data
+ public static class Level {
+
+ @Schema(description = "等级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long id;
+
+ @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+ private String name;
+
+ @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Integer level;
+
+ @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
+ private String icon;
+
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java
new file mode 100644
index 000000000..22cbf55ee
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserResetPasswordReqVO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.member.controller.app.user.vo;
+
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "用户 APP - 重置密码 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppMemberUserResetPasswordReqVO {
+
+ @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
+ @NotEmpty(message = "新密码不能为空")
+ @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+ private String password;
+
+ @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String code;
+
+ @Schema(description = "手机号",requiredMode = Schema.RequiredMode.REQUIRED,example = "15878962356")
+ @NotBlank(message = "手机号不能为空")
+ @Mobile
+ private String mobile;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdateMobileReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdateMobileReqVO.java
new file mode 100644
index 000000000..6653506fc
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdateMobileReqVO.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.member.controller.app.user.vo;
+
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "用户 APP - 修改手机 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppMemberUserUpdateMobileReqVO {
+
+ @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String code;
+
+ @Schema(description = "手机号",requiredMode = Schema.RequiredMode.REQUIRED,example = "15823654487")
+ @NotBlank(message = "手机号不能为空")
+ @Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
+ @Mobile
+ private String mobile;
+
+ @Schema(description = "原手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "原手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String oldCode;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java
new file mode 100644
index 000000000..cc78ca832
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdatePasswordReqVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.controller.app.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Pattern;
+
+@Schema(description = "用户 APP - 修改密码 Request VO")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class AppMemberUserUpdatePasswordReqVO {
+
+ @Schema(description = "新密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
+ @NotEmpty(message = "新密码不能为空")
+ @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
+ private String password;
+
+ @Schema(description = "手机验证码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+ @NotEmpty(message = "手机验证码不能为空")
+ @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
+ @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
+ private String code;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdateReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdateReqVO.java
new file mode 100644
index 000000000..a676f6256
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserUpdateReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.member.controller.app.user.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
+
+@Schema(description = "用户 App - 会员用户更新 Request VO")
+@Data
+public class AppMemberUserUpdateReqVO {
+
+ @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+ private String nickname;
+
+ @Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/x.png")
+ @URL(message = "头像必须是 URL 格式")
+ private String avatar;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/package-info.java
new file mode 100644
index 000000000..9e2888c69
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/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 cn.iocoder.yudao.module.member.controller;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java
new file mode 100644
index 000000000..fd5198e14
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/address/AddressConvert.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.member.convert.address;
+
+import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
+import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.address.vo.AddressRespVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
+import cn.iocoder.yudao.module.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);
+
+ AddressRespDTO convert02(MemberAddressDO bean);
+
+ @Named("convertAreaIdToAreaName")
+ default String convertAreaIdToAreaName(Integer areaId) {
+ return AreaUtils.format(areaId);
+ }
+
+ List convertList2(List list);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
new file mode 100644
index 000000000..29e8f4fdc
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/auth/AuthConvert.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.member.convert.auth;
+
+import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
+import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserResetPasswordReqVO;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
+import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
+import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
+import cn.iocoder.yudao.module.system.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, AppSocialUserUnbindReqVO reqVO);
+
+ 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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/config/MemberConfigConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/config/MemberConfigConvert.java
new file mode 100644
index 000000000..9847645f9
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/config/MemberConfigConvert.java
@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.member.convert.config;
+
+import cn.iocoder.yudao.module.member.api.config.dto.MemberConfigRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * 会员配置 Convert
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberConfigConvert {
+
+ MemberConfigConvert INSTANCE = Mappers.getMapper(MemberConfigConvert.class);
+
+ MemberConfigRespVO convert(MemberConfigDO bean);
+
+ MemberConfigDO convert(MemberConfigSaveReqVO bean);
+
+ MemberConfigRespDTO convert01(MemberConfigDO config);
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/group/MemberGroupConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/group/MemberGroupConvert.java
new file mode 100644
index 000000000..06f49d60c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/group/MemberGroupConvert.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.member.convert.group;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupSimpleRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupUpdateReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 用户分组 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberGroupConvert {
+
+ MemberGroupConvert INSTANCE = Mappers.getMapper(MemberGroupConvert.class);
+
+ MemberGroupDO convert(MemberGroupCreateReqVO bean);
+
+ MemberGroupDO convert(MemberGroupUpdateReqVO bean);
+
+ MemberGroupRespVO convert(MemberGroupDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ List convertSimpleList(List list);
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java
new file mode 100644
index 000000000..93f864f08
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.member.convert.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.experience.AppMemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 会员经验记录 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberExperienceRecordConvert {
+
+ MemberExperienceRecordConvert INSTANCE = Mappers.getMapper(MemberExperienceRecordConvert.class);
+
+ MemberExperienceRecordRespVO convert(MemberExperienceRecordDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ MemberExperienceRecordDO convert(Long userId, Integer experience, Integer totalExperience,
+ String bizId, Integer bizType,
+ String title, String description);
+
+ PageResult convertPage02(PageResult page);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java
new file mode 100644
index 000000000..f2282815e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.member.convert.level;
+
+import cn.iocoder.yudao.module.member.api.level.dto.MemberLevelRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelSimpleRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.level.AppMemberLevelRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 会员等级 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberLevelConvert {
+
+ MemberLevelConvert INSTANCE = Mappers.getMapper(MemberLevelConvert.class);
+
+ MemberLevelDO convert(MemberLevelCreateReqVO bean);
+
+ MemberLevelDO convert(MemberLevelUpdateReqVO bean);
+
+ MemberLevelRespVO convert(MemberLevelDO bean);
+
+ List convertList(List list);
+
+ List convertSimpleList(List list);
+
+ List convertList02(List list);
+
+ MemberLevelRespDTO convert02(MemberLevelDO bean);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java
new file mode 100644
index 000000000..d01f1b63c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.convert.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 会员等级记录 Convert
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberLevelRecordConvert {
+
+ MemberLevelRecordConvert INSTANCE = Mappers.getMapper(MemberLevelRecordConvert.class);
+
+ MemberLevelRecordRespVO convert(MemberLevelRecordDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+ default MemberLevelRecordDO copyTo(MemberLevelDO from, MemberLevelRecordDO to) {
+ if (from != null) {
+ to.setLevelId(from.getId());
+ to.setLevel(from.getLevel());
+ to.setDiscountPercent(from.getDiscountPercent());
+ to.setExperience(from.getExperience());
+ }
+ return to;
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/package-info.java
new file mode 100644
index 000000000..6523a6656
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 提供 POJO 类的实体转换
+ *
+ * 目前使用 MapStruct 框架
+ */
+package cn.iocoder.yudao.module.member.convert;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/point/MemberPointRecordConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/point/MemberPointRecordConvert.java
new file mode 100644
index 000000000..1d2c146d2
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/point/MemberPointRecordConvert.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.member.convert.point;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod.MemberPointRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.app.point.vo.AppMemberPointRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 用户积分记录 Convert
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberPointRecordConvert {
+
+ MemberPointRecordConvert INSTANCE = Mappers.getMapper(MemberPointRecordConvert.class);
+
+ default PageResult convertPage(PageResult pageResult, List users) {
+ PageResult voPageResult = convertPage(pageResult);
+ // user 拼接
+ Map userMap = convertMap(users, MemberUserRespDTO::getId);
+ voPageResult.getList().forEach(record -> MapUtils.findAndThen(userMap, record.getUserId(),
+ memberUserRespDTO -> record.setNickname(memberUserRespDTO.getNickname())));
+ return voPageResult;
+ }
+ PageResult convertPage(PageResult pageResult);
+
+ PageResult convertPage02(PageResult pageResult);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInConfigConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInConfigConvert.java
new file mode 100644
index 000000000..5acd87151
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInConfigConvert.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.member.convert.signin;
+
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.config.MemberSignInConfigCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.config.MemberSignInConfigRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.config.MemberSignInConfigUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.signin.vo.config.AppMemberSignInConfigRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 签到规则 Convert
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberSignInConfigConvert {
+
+ MemberSignInConfigConvert INSTANCE = Mappers.getMapper(MemberSignInConfigConvert.class);
+
+ MemberSignInConfigDO convert(MemberSignInConfigCreateReqVO bean);
+
+ MemberSignInConfigDO convert(MemberSignInConfigUpdateReqVO bean);
+
+ MemberSignInConfigRespVO convert(MemberSignInConfigDO bean);
+
+ List convertList(List list);
+
+ List convertList02(List list);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java
new file mode 100644
index 000000000..0526ed64a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/signin/MemberSignInRecordConvert.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.member.convert.signin;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * 签到记录 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface MemberSignInRecordConvert {
+
+ MemberSignInRecordConvert INSTANCE = Mappers.getMapper(MemberSignInRecordConvert.class);
+
+ default PageResult convertPage(PageResult pageResult, List users) {
+ PageResult voPageResult = convertPage(pageResult);
+ // user 拼接
+ Map userMap = convertMap(users, MemberUserRespDTO::getId);
+ voPageResult.getList().forEach(record -> MapUtils.findAndThen(userMap, record.getUserId(),
+ memberUserRespDTO -> record.setNickname(memberUserRespDTO.getNickname())));
+ return voPageResult;
+ }
+
+ PageResult convertPage(PageResult pageResult);
+
+ PageResult convertPage02(PageResult pageResult);
+
+ AppMemberSignInRecordRespVO coverRecordToAppRecordVo(MemberSignInRecordDO memberSignInRecordDO);
+
+ default MemberSignInRecordDO convert(Long userId, MemberSignInRecordDO firstRecord, List signInConfigs) {
+ // 1. 计算今天是第几天签到
+ long day = ChronoUnit.DAYS.between(firstRecord.getCreateTime(), LocalDateTime.now());
+ // 2. 初始化签到信息
+ // TODO @puhui999:signInRecord=》record
+ MemberSignInRecordDO signInRecord = new MemberSignInRecordDO().setUserId(userId)
+ .setDay(Integer.parseInt(Long.toString(day))) // 设置签到天数 TODO @puhui999:day 应该跟着第几天签到走;不是累加哈;另外 long 转 int,应该 (int) day 就可以了。。。
+ .setPoint(0) // 设置签到积分默认为
+ .setExperience(0); // 设置签到经验默认为 0
+ // 3. 获取签到对应的积分数
+ MemberSignInConfigDO lastConfig = signInConfigs.get(signInConfigs.size() - 1); // 最大签到天数
+ if (day > lastConfig.getDay()) { // 超出范围按第一天的经验计算
+ // TODO @puhui999:不能直接取 0,万一它 day 不匹配哈。就是第一天没奖励。。。
+ signInRecord.setPoint(signInConfigs.get(0).getPoint());
+ signInRecord.setExperience(signInConfigs.get(0).getExperience());
+ return signInRecord;
+ }
+ // TODO @puhui999:signInConfig 可以改成 config;
+ MemberSignInConfigDO signInConfig = CollUtil.findOne(signInConfigs, config -> ObjUtil.equal(config.getDay(), day));
+ if (signInConfig == null) {
+ return signInRecord;
+ }
+ signInRecord.setPoint(signInConfig.getPoint());
+ signInRecord.setExperience(signInConfig.getExperience());
+ return signInRecord;
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java
new file mode 100644
index 000000000..3c9288ba8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/social/SocialUserConvert.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.member.convert.social;
+
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
+import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface SocialUserConvert {
+
+ SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);
+
+ SocialUserBindReqDTO convert(Long userId, Integer userType, AppSocialUserBindReqVO reqVO);
+
+ SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/tag/MemberTagConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/tag/MemberTagConvert.java
new file mode 100644
index 000000000..9d3a41f1a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/tag/MemberTagConvert.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.member.convert.tag;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagUpdateReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 会员标签 Convert
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface MemberTagConvert {
+
+ MemberTagConvert INSTANCE = Mappers.getMapper(MemberTagConvert.class);
+
+ MemberTagDO convert(MemberTagCreateReqVO bean);
+
+ MemberTagDO convert(MemberTagUpdateReqVO bean);
+
+ MemberTagRespVO convert(MemberTagDO bean);
+
+ List convertList(List list);
+
+ PageResult convertPage(PageResult page);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java
new file mode 100644
index 000000000..aae9a7601
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.member.convert.user;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserInfoRespVO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+@Mapper(uses = {AddressConvert.class})
+public interface MemberUserConvert {
+
+ MemberUserConvert INSTANCE = Mappers.getMapper(MemberUserConvert.class);
+
+ AppMemberUserInfoRespVO convert(MemberUserDO bean);
+
+ @Mapping(source = "level", target = "level")
+ @Mapping(source = "bean.experience", target = "experience")
+ AppMemberUserInfoRespVO convert(MemberUserDO bean, MemberLevelDO level);
+
+ 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);
+
+ default PageResult convertPage(PageResult pageResult,
+ List tags,
+ List levels,
+ List groups) {
+ PageResult result = convertPage(pageResult);
+ // 处理关联数据
+ Map tagMap = convertMap(tags, MemberTagDO::getId, MemberTagDO::getName);
+ Map levelMap = convertMap(levels, MemberLevelDO::getId, MemberLevelDO::getName);
+ Map groupMap = convertMap(groups, MemberGroupDO::getId, MemberGroupDO::getName);
+ // 填充关联数据
+ result.getList().forEach(user -> {
+ user.setTagNames(convertList(user.getTagIds(), tagMap::get));
+ user.setLevelName(levelMap.get(user.getLevelId()));
+ user.setGroupName(groupMap.get(user.getGroupId()));
+ });
+ return result;
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
new file mode 100644
index 000000000..8153487b7
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md
@@ -0,0 +1 @@
+
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/address/MemberAddressDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/address/MemberAddressDO.java
new file mode 100644
index 000000000..f2e43b563
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/address/MemberAddressDO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.address;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 用户收件地址 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("member_address")
+@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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java
new file mode 100644
index 000000000..6efb4a1c0
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/config/MemberConfigDO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 会员配置 DO
+ *
+ * @author QingX
+ */
+@TableName(value = "member_config", autoResultMap = true)
+@KeySequence("member_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberConfigDO extends BaseDO {
+
+ /**
+ * 自增主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 积分抵扣开关
+ */
+ private Boolean pointTradeDeductEnable;
+ /**
+ * 积分抵扣,单位:分
+ *
+ * 1 积分抵扣多少分
+ */
+ private Integer pointTradeDeductUnitPrice;
+ /**
+ * 积分抵扣最大值
+ */
+ private Integer pointTradeDeductMaxPrice;
+ /**
+ * 1 元赠送多少分
+ */
+ private Integer pointTradeGivePoint;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/group/MemberGroupDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/group/MemberGroupDO.java
new file mode 100644
index 000000000..c9a82ab5d
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/group/MemberGroupDO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.group;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 用户分组 DO
+ *
+ * @author owen
+ */
+@TableName("member_group")
+@KeySequence("member_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberGroupDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 名称
+ */
+ private String name;
+ /**
+ * 备注
+ */
+ private String remark;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberExperienceRecordDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberExperienceRecordDO.java
new file mode 100644
index 000000000..d7c06d4ba
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberExperienceRecordDO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.level;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 会员经验记录 DO
+ *
+ * @author owen
+ */
+@TableName("member_experience_record")
+@KeySequence("member_experience_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberExperienceRecordDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 用户编号
+ *
+ * 关联 {@link MemberUserDO#getId()} 字段
+ */
+ private Long userId;
+ /**
+ * 业务类型
+ *
+ * 枚举 {@link MemberExperienceBizTypeEnum}
+ */
+ private Integer bizType;
+ /**
+ * 业务编号
+ */
+ private String bizId;
+ /**
+ * 标题
+ */
+ private String title;
+ /**
+ * 描述
+ */
+ private String description;
+ /**
+ * 经验
+ */
+ private Integer experience;
+ /**
+ * 变更后的经验
+ */
+ private Integer totalExperience;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelDO.java
new file mode 100644
index 000000000..05035ffe5
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelDO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.level;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 会员等级 DO
+ *
+ * 配置每个等级需要的积分
+ *
+ * @author owen
+ */
+@TableName("member_level")
+@KeySequence("member_level_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberLevelDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 等级名称
+ */
+ private String name;
+ /**
+ * 等级
+ */
+ private Integer level;
+ /**
+ * 升级经验
+ */
+ private Integer experience;
+ /**
+ * 享受折扣
+ */
+ private Integer discountPercent;
+
+ /**
+ * 等级图标
+ */
+ private String icon;
+ /**
+ * 等级背景图
+ */
+ private String backgroundUrl;
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelRecordDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelRecordDO.java
new file mode 100644
index 000000000..8b5451d45
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/level/MemberLevelRecordDO.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.level;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 会员等级记录 DO
+ *
+ * 用户每次等级发生变更时,记录一条日志
+ *
+ * @author owen
+ */
+@TableName("member_level_record")
+@KeySequence("member_level_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberLevelRecordDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 用户编号
+ *
+ * 关联 {@link MemberUserDO#getId()} 字段
+ */
+ private Long userId;
+ /**
+ * 等级编号
+ *
+ * 关联 {@link MemberLevelDO#getId()} 字段
+ */
+ private Long levelId;
+ /**
+ * 会员等级
+ *
+ * 冗余 {@link MemberLevelDO#getLevel()} 字段
+ */
+ private Integer level;
+ /**
+ * 享受折扣
+ */
+ private Integer discountPercent;
+ /**
+ * 升级经验
+ */
+ private Integer experience;
+ /**
+ * 会员此时的经验
+ */
+ private Integer userExperience;
+ /**
+ * 备注
+ */
+ private String remark;
+ /**
+ * 描述
+ */
+ private String description;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointRecordDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointRecordDO.java
new file mode 100644
index 000000000..f884f08d8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/point/MemberPointRecordDO.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.point;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 用户积分记录 DO
+ *
+ * @author QingX
+ */
+@TableName("member_point_record")
+@KeySequence("member_point_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberPointRecordDO extends BaseDO {
+
+ /**
+ * 自增主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 用户编号
+ *
+ * 对应 MemberUserDO 的 id 属性
+ */
+ private Long userId;
+
+ /**
+ * 业务编码
+ */
+ private String bizId;
+ /**
+ * 业务类型
+ *
+ * 枚举 {@link MemberPointBizTypeEnum}
+ */
+ private Integer bizType;
+
+ /**
+ * 积分标题
+ */
+ private String title;
+ /**
+ * 积分描述
+ */
+ private String description;
+
+ /**
+ * 变动积分
+ *
+ * 1、正数表示获得积分
+ * 2、负数表示消耗积分
+ */
+ private Integer point;
+ /**
+ * 变动后的积分
+ */
+ private Integer totalPoint;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInConfigDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInConfigDO.java
new file mode 100644
index 000000000..76d55c9bf
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInConfigDO.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.signin;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 签到规则 DO
+ *
+ * @author QingX
+ */
+@TableName("member_sign_in_config")
+@KeySequence("member_sign_in_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberSignInConfigDO extends BaseDO {
+
+ /**
+ * 规则自增主键
+ */
+ @TableId
+ private Long id;
+ /**
+ * 签到第 x 天
+ */
+ private Integer day;
+ /**
+ * 奖励积分
+ */
+ private Integer point;
+ /**
+ * 奖励经验
+ */
+ private Integer experience;
+
+ /**
+ * 状态
+ *
+ * 枚举 {@link CommonStatusEnum}
+ */
+ private Integer status;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInRecordDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInRecordDO.java
new file mode 100644
index 000000000..b07b5efbc
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/signin/MemberSignInRecordDO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.signin;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 签到记录 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("member_sign_in_record")
+@KeySequence("member_sign_in_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberSignInRecordDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 签到用户
+ */
+ private Long userId;
+ /**
+ * 第几天签到
+ */
+ private Integer day;
+ /**
+ * 签到的积分
+ */
+ private Integer point;
+ /**
+ * 签到的经验
+ */
+ private Integer experience;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/tag/MemberTagDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/tag/MemberTagDO.java
new file mode 100644
index 000000000..b984064e0
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/tag/MemberTagDO.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.tag;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 会员标签 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("member_tag")
+@KeySequence("member_tag_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MemberTagDO extends BaseDO {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 标签名称
+ */
+ private String name;
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java
new file mode 100644
index 000000000..97ddc191d
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java
@@ -0,0 +1,145 @@
+package cn.iocoder.yudao.module.member.dal.dataobject.user;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
+import cn.iocoder.yudao.framework.ip.core.Area;
+import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
+import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.system.enums.common.SexEnum;
+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 lombok.*;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 会员用户 DO
+ *
+ * uk_mobile 索引:基于 {@link #mobile} 字段
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "member_user", autoResultMap = true)
+@KeySequence("member_user_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/address/MemberAddressMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/address/MemberAddressMapper.java
new file mode 100644
index 000000000..3df68c51a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/address/MemberAddressMapper.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.member.dal.mysql.address;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO;
+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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/config/MemberConfigMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/config/MemberConfigMapper.java
new file mode 100644
index 000000000..e03938378
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/config/MemberConfigMapper.java
@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.member.dal.mysql.config;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 积分设置 Mapper
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberConfigMapper extends BaseMapperX {
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/group/MemberGroupMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/group/MemberGroupMapper.java
new file mode 100644
index 000000000..da4f7b7a8
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/group/MemberGroupMapper.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.dal.mysql.group;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 用户分组 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberGroupMapper extends BaseMapperX {
+
+ default PageResult selectPage(MemberGroupPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(MemberGroupDO::getName, reqVO.getName())
+ .eqIfPresent(MemberGroupDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(MemberGroupDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MemberGroupDO::getId));
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(MemberGroupDO::getStatus, status);
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java
new file mode 100644
index 000000000..4e5f6f567
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.member.dal.mysql.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 会员经验记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberExperienceRecordMapper extends BaseMapperX {
+
+ default PageResult selectPage(MemberExperienceRecordPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(MemberExperienceRecordDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(MemberExperienceRecordDO::getBizId, reqVO.getBizId())
+ .eqIfPresent(MemberExperienceRecordDO::getBizType, reqVO.getBizType())
+ .eqIfPresent(MemberExperienceRecordDO::getTitle, reqVO.getTitle())
+ .betweenIfPresent(MemberExperienceRecordDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MemberExperienceRecordDO::getId));
+ }
+
+ default PageResult selectPage(Long userId, PageParam pageParam) {
+ return selectPage(pageParam, new LambdaQueryWrapper()
+ .eq(MemberExperienceRecordDO::getUserId, userId)
+ .orderByDesc(MemberExperienceRecordDO::getId));
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java
new file mode 100644
index 000000000..d2dcb6cb4
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.member.dal.mysql.level;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 会员等级 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberLevelMapper extends BaseMapperX {
+
+ default List selectList(MemberLevelListReqVO reqVO) {
+ return selectList(new LambdaQueryWrapperX()
+ .likeIfPresent(MemberLevelDO::getName, reqVO.getName())
+ .eqIfPresent(MemberLevelDO::getStatus, reqVO.getStatus())
+ .orderByAsc(MemberLevelDO::getLevel));
+ }
+
+
+ default List selectListByStatus(Integer status) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(MemberLevelDO::getStatus, status)
+ .orderByAsc(MemberLevelDO::getLevel));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelRecordMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelRecordMapper.java
new file mode 100644
index 000000000..6808b957a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelRecordMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.member.dal.mysql.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 会员等级记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface MemberLevelRecordMapper extends BaseMapperX {
+
+ default PageResult selectPage(MemberLevelRecordPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(MemberLevelRecordDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(MemberLevelRecordDO::getLevelId, reqVO.getLevelId())
+ .betweenIfPresent(MemberLevelRecordDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MemberLevelRecordDO::getId));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/point/MemberPointRecordMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/point/MemberPointRecordMapper.java
new file mode 100644
index 000000000..5c3370929
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/point/MemberPointRecordMapper.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.member.dal.mysql.point;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.point.vo.recrod.MemberPointRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Set;
+
+/**
+ * 用户积分记录 Mapper
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberPointRecordMapper extends BaseMapperX {
+
+ default PageResult selectPage(MemberPointRecordPageReqVO reqVO, Set userIds) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .inIfPresent(MemberPointRecordDO::getUserId, userIds)
+ .eqIfPresent(MemberPointRecordDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(MemberPointRecordDO::getBizType, reqVO.getBizType())
+ .likeIfPresent(MemberPointRecordDO::getTitle, reqVO.getTitle())
+ .orderByDesc(MemberPointRecordDO::getId));
+ }
+
+ default PageResult selectPage(Long userId, PageParam pageVO) {
+ return selectPage(pageVO, new LambdaQueryWrapperX()
+ .eq(MemberPointRecordDO::getUserId, userId)
+ .orderByDesc(MemberPointRecordDO::getId));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java
new file mode 100644
index 000000000..211ead33d
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInConfigMapper.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.member.dal.mysql.signin;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 签到规则 Mapper
+ *
+ * @author QingX
+ */
+@Mapper
+public interface MemberSignInConfigMapper extends BaseMapperX {
+
+ default MemberSignInConfigDO selectByDay(Integer day) {
+ return selectOne(MemberSignInConfigDO::getDay, day);
+ }
+
+ default List selectListByStatus(Integer status) {
+ return selectList(MemberSignInConfigDO::getStatus, status);
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java
new file mode 100644
index 000000000..01fe75c14
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/signin/MemberSignInRecordMapper.java
@@ -0,0 +1,84 @@
+package cn.iocoder.yudao.module.member.dal.mysql.signin;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.signin.vo.record.MemberSignInRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.signin.MemberSignInRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 签到记录 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface MemberSignInRecordMapper extends BaseMapperX {
+
+ default PageResult selectPage(MemberSignInRecordPageReqVO reqVO, Set userIds) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .inIfPresent(MemberSignInRecordDO::getUserId, userIds)
+ .eqIfPresent(MemberSignInRecordDO::getUserId, reqVO.getUserId())
+ .eqIfPresent(MemberSignInRecordDO::getDay, reqVO.getDay())
+ .betweenIfPresent(MemberSignInRecordDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MemberSignInRecordDO::getId));
+ }
+
+ default PageResult selectPage(Long userId, PageParam pageParam) {
+ return selectPage(pageParam, new LambdaQueryWrapperX()
+ .eq(MemberSignInRecordDO::getUserId, userId)
+ .orderByDesc(MemberSignInRecordDO::getId));
+ }
+
+ // TODO @puhui999:这 2 个方法,是不是一个 first;一个 last 就可以了。。
+ /**
+ * 获取用户最近的签到记录信息,根据签到时间倒序
+ *
+ * @param userId 用户编号
+ * @return 签到记录列表
+ */
+ default MemberSignInRecordDO selectLastRecordByUserIdDesc(Long userId) {
+ return selectOne(new QueryWrapper()
+ .eq("user_id", userId)
+ .orderByDesc("create_time")
+ .last("limit 1"));
+ }
+
+ /**
+ * 获取用户最早的签到记录信息,根据签到时间倒序
+ *
+ * @param userId 用户编号
+ * @return 签到记录列表
+ */
+ default MemberSignInRecordDO selectLastRecordByUserIdAsc(Long userId) {
+ return selectOne(new QueryWrapper()
+ .eq("user_id", userId)
+ .orderByAsc("create_time")
+ .last("limit 1"));
+ }
+
+ default Long selectCountByUserId(Long userId) {
+ // TODO @puhui999:可以使用 selectCount 里面允许传递字段的方法
+ return selectCount(new LambdaQueryWrapperX()
+ .eq(MemberSignInRecordDO::getUserId, userId));
+ }
+
+ /**
+ * 获取用户的签到记录列表信息,根据签到时间倒序
+ *
+ * @param userId 用户编号
+ * @return 签到记录信息
+ */
+ // TODO @puhui999:这个排序,可以交给 service 哈;
+ default List selectListByUserId(Long userId) {
+ return selectList(new LambdaQueryWrapperX()
+ .eq(MemberSignInRecordDO::getUserId, userId)
+ .orderByDesc(MemberSignInRecordDO::getCreateTime));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/tag/MemberTagMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/tag/MemberTagMapper.java
new file mode 100644
index 000000000..f4723e282
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/tag/MemberTagMapper.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.dal.mysql.tag;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.tag.vo.MemberTagPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 会员标签 Mapper
+ *
+ * @author 芋道源码
+ */
+@Mapper
+public interface MemberTagMapper extends BaseMapperX {
+
+ default PageResult selectPage(MemberTagPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .likeIfPresent(MemberTagDO::getName, reqVO.getName())
+ .betweenIfPresent(MemberTagDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(MemberTagDO::getId));
+ }
+
+ default MemberTagDO selelctByName(String name) {
+ return selectOne(MemberTagDO::getName, name);
+ }
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
new file mode 100644
index 000000000..3f871020c
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.module.member.dal.mysql.user;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/package-info.java
new file mode 100644
index 000000000..a45c2a161
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/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 cn.iocoder.yudao.module.member.dal;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/redis/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/redis/package-info.java
new file mode 100644
index 000000000..8dfa9fb20
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/redis/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 占位,后续有类后,可以删除,避免 package 无法提交到 Git 上
+ */
+package cn.iocoder.yudao.module.member.dal.redis;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java
new file mode 100644
index 000000000..7e9ca95de
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 member 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.module.member.framework;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/config/MemberWebConfiguration.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/config/MemberWebConfiguration.java
new file mode 100644
index 000000000..82c70034e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/config/MemberWebConfiguration.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.member.framework.web.config;
+
+import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
+import org.springdoc.core.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * member 模块的 web 组件的 Configuration
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+public class MemberWebConfiguration {
+
+ /**
+ * member 模块的 API 分组
+ */
+ @Bean
+ public GroupedOpenApi memberGroupedOpenApi() {
+ return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("member");
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/package-info.java
new file mode 100644
index 000000000..3a964cfc2
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/framework/web/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * member 模块的 web 配置
+ */
+package cn.iocoder.yudao.module.member.framework.web;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/UserCreateMessage.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/UserCreateMessage.java
new file mode 100644
index 000000000..509527b09
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/UserCreateMessage.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.mq.message.user;
+
+import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 会员用户创建消息
+ *
+ * @author owen
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UserCreateMessage extends AbstractStreamMessage {
+
+ /**
+ * 用户编号
+ */
+ @NotNull(message = "用户编号不能为空")
+ private Long userId;
+
+ @Override
+ public String getStreamKey() {
+ return "member.create.send";
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/UserCreateProducer.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/UserCreateProducer.java
new file mode 100644
index 000000000..6337b9f00
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/UserCreateProducer.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.member.mq.producer.user;
+
+import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
+import cn.iocoder.yudao.module.member.mq.message.user.UserCreateMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 会员用户创建 Producer
+ *
+ * @author owen
+ */
+@Slf4j
+@Component
+public class UserCreateProducer {
+
+ @Resource
+ private RedisMQTemplate redisMQTemplate;
+
+ /**
+ * 发送 {@link UserCreateMessage} 消息
+ *
+ * @param userId 用户编号
+ */
+ public void sendUserCreateMessage(Long userId) {
+ redisMQTemplate.send(new UserCreateMessage().setUserId(userId));
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/package-info.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/package-info.java
new file mode 100644
index 000000000..405aa4cbf
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * member 模块,我们放会员业务。
+ * 例如说:会员中心等等
+ *
+ * 1. Controller URL:以 /member/ 开头,避免和其它 Module 冲突
+ * 2. DataObject 表名:以 member_ 开头,方便在数据库中区分
+ */
+package cn.iocoder.yudao.module.member;
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressService.java
new file mode 100644
index 000000000..099c49c42
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressService.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.member.service.address;
+
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO;
+
+import javax.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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImpl.java
new file mode 100644
index 000000000..901f1b340
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/address/AddressServiceImpl.java
@@ -0,0 +1,97 @@
+package cn.iocoder.yudao.module.member.service.address;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
+import cn.iocoder.yudao.module.member.convert.address.AddressConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.address.MemberAddressDO;
+import cn.iocoder.yudao.module.member.dal.mysql.address.MemberAddressMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
new file mode 100644
index 000000000..9ab878817
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthService.java
@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.member.service.auth;
+
+import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
+import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
+
+import javax.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 登陆信息
+ * @param terminal 终端 {@link TerminalEnum}
+ * @return 登录结果
+ */
+ AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, Integer terminal);
+
+ /**
+ * 社交登录,使用 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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
new file mode 100644
index 000000000..b6e88fd41
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
@@ -0,0 +1,264 @@
+package cn.iocoder.yudao.module.member.service.auth;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
+import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
+import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
+import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import cn.iocoder.yudao.module.system.api.logger.LoginLogApi;
+import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
+import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
+import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
+import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
+import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
+import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;
+import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
+import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
+import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
+import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
+import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
+
+/**
+ * 会员的认证 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()));
+ }
+
+ // 创建 Token 令牌,记录登录日志
+ return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, openid);
+ }
+
+ @Override
+ @Transactional
+ public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO, Integer terminal) {
+ // 校验验证码
+ String userIp = getClientIP();
+ smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));
+
+ // 获得获得注册用户
+ MemberUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp, terminal);
+ Assert.notNull(user, "获取用户失败,结果为空");
+
+ // 如果 socialType 非空,说明需要绑定社交用户
+ String openid = null;
+ if (reqVO.getSocialType() != null) {
+ openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
+ reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
+ }
+
+ // 创建 Token 令牌,记录登录日志
+ return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, openid);
+ }
+
+ @Override
+ public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) {
+ // 使用 code 授权码,进行登录。然后,获得到绑定的用户编号
+ SocialUserRespDTO socialUser = socialUserApi.getSocialUser(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
+ reqVO.getCode(), reqVO.getState());
+ if (socialUser == null) {
+ throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
+ }
+
+ // 自动登录
+ MemberUserDO user = userService.getUser(socialUser.getUserId());
+ 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());
+ 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(), ""));
+
+ // 创建 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));
+ // 构建返回结果
+ return AuthConvert.INSTANCE.convert(accessTokenRespDTO, openid);
+ }
+
+ @Override
+ public String getSocialAuthorizeUrl(Integer type, String redirectUri) {
+ return socialClientApi.getAuthorizeUrl(type, UserTypeEnum.MEMBER.getValue(), redirectUri);
+ }
+
+ 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 (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.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);
+ // 更新最后登录时间
+ 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);
+ 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);
+ }
+ }
+
+ // 执行发送
+ smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));
+ }
+
+ @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);
+ 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);
+ }
+
+ 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/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigService.java
new file mode 100644
index 000000000..fc4545425
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigService.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.member.service.config;
+
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+
+import javax.validation.Valid;
+
+/**
+ * 会员配置 Service 接口
+ *
+ * @author QingX
+ */
+public interface MemberConfigService {
+
+ /**
+ * 保存会员配置
+ *
+ * @param saveReqVO 更新信息
+ */
+ void saveConfig(@Valid MemberConfigSaveReqVO saveReqVO);
+
+ /**
+ * 获得会员配置
+ *
+ * @return 积分配置
+ */
+ MemberConfigDO getConfig();
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigServiceImpl.java
new file mode 100644
index 000000000..be56f8a8a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/config/MemberConfigServiceImpl.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.member.service.config;
+
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.module.member.controller.admin.config.vo.MemberConfigSaveReqVO;
+import cn.iocoder.yudao.module.member.convert.config.MemberConfigConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO;
+import cn.iocoder.yudao.module.member.dal.mysql.config.MemberConfigMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 会员配置 Service 实现类
+ *
+ * @author QingX
+ */
+@Service
+@Validated
+public class MemberConfigServiceImpl implements MemberConfigService {
+
+ @Resource
+ private MemberConfigMapper memberConfigMapper;
+
+ @Override
+ public void saveConfig(MemberConfigSaveReqVO saveReqVO) {
+ // 存在,则进行更新
+ MemberConfigDO dbConfig = getConfig();
+ if (dbConfig != null) {
+ memberConfigMapper.updateById(MemberConfigConvert.INSTANCE.convert(saveReqVO).setId(dbConfig.getId()));
+ return;
+ }
+ // 不存在,则进行插入
+ memberConfigMapper.insert(MemberConfigConvert.INSTANCE.convert(saveReqVO));
+ }
+
+ @Override
+ public MemberConfigDO getConfig() {
+ List list = memberConfigMapper.selectList();
+ return CollectionUtils.getFirst(list);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupService.java
new file mode 100644
index 000000000..54c7882e0
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupService.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.member.service.group;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupUpdateReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 用户分组 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberGroupService {
+
+ /**
+ * 创建用户分组
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createGroup(@Valid MemberGroupCreateReqVO createReqVO);
+
+ /**
+ * 更新用户分组
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateGroup(@Valid MemberGroupUpdateReqVO updateReqVO);
+
+ /**
+ * 删除用户分组
+ *
+ * @param id 编号
+ */
+ void deleteGroup(Long id);
+
+ /**
+ * 获得用户分组
+ *
+ * @param id 编号
+ * @return 用户分组
+ */
+ MemberGroupDO getGroup(Long id);
+
+ /**
+ * 获得用户分组列表
+ *
+ * @param ids 编号
+ * @return 用户分组列表
+ */
+ List getGroupList(Collection ids);
+
+ /**
+ * 获得用户分组分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 用户分组分页
+ */
+ PageResult getGroupPage(MemberGroupPageReqVO pageReqVO);
+
+
+ /**
+ * 获得指定状态的用户分组列表
+ *
+ * @param status 状态
+ * @return 用户分组列表
+ */
+ List getGroupListByStatus(Integer status);
+
+
+ /**
+ * 获得开启状态的用户分组列表
+ *
+ * @return 用户分组列表
+ */
+ default List getEnableGroupList() {
+ return getGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java
new file mode 100644
index 000000000..cdf1e4fee
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.member.service.group;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.group.vo.MemberGroupUpdateReqVO;
+import cn.iocoder.yudao.module.member.convert.group.MemberGroupConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.group.MemberGroupDO;
+import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
+import cn.iocoder.yudao.module.member.service.user.MemberUserService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.GROUP_HAS_USER;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.GROUP_NOT_EXISTS;
+
+/**
+ * 用户分组 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberGroupServiceImpl implements MemberGroupService {
+
+ @Resource
+ private MemberGroupMapper memberGroupMapper;
+
+ @Resource
+ private MemberUserService memberUserService;
+
+ @Override
+ public Long createGroup(MemberGroupCreateReqVO createReqVO) {
+ // 插入
+ MemberGroupDO group = MemberGroupConvert.INSTANCE.convert(createReqVO);
+ memberGroupMapper.insert(group);
+ // 返回
+ return group.getId();
+ }
+
+ @Override
+ public void updateGroup(MemberGroupUpdateReqVO updateReqVO) {
+ // 校验存在
+ validateGroupExists(updateReqVO.getId());
+ // 更新
+ MemberGroupDO updateObj = MemberGroupConvert.INSTANCE.convert(updateReqVO);
+ memberGroupMapper.updateById(updateObj);
+ }
+
+ @Override
+ public void deleteGroup(Long id) {
+ // 校验存在
+ validateGroupExists(id);
+ // 校验分组下是否有用户
+ validateGroupHasUser(id);
+ // 删除
+ memberGroupMapper.deleteById(id);
+ }
+
+ void validateGroupExists(Long id) {
+ if (memberGroupMapper.selectById(id) == null) {
+ throw exception(GROUP_NOT_EXISTS);
+ }
+ }
+
+ void validateGroupHasUser(Long id) {
+ Long count = memberUserService.getUserCountByGroupId(id);
+ if (count > 0) {
+ throw exception(GROUP_HAS_USER);
+ }
+ }
+
+ @Override
+ public MemberGroupDO getGroup(Long id) {
+ return memberGroupMapper.selectById(id);
+ }
+
+ @Override
+ public List getGroupList(Collection ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return ListUtil.empty();
+ }
+ return memberGroupMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult getGroupPage(MemberGroupPageReqVO pageReqVO) {
+ return memberGroupMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List getGroupListByStatus(Integer status) {
+ return memberGroupMapper.selectListByStatus(status);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordService.java
new file mode 100644
index 000000000..76470f72a
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordService.java
@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+
+/**
+ * 会员经验记录 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberExperienceRecordService {
+
+ /**
+ * 获得会员经验记录
+ *
+ * @param id 编号
+ * @return 会员经验记录
+ */
+ MemberExperienceRecordDO getExperienceRecord(Long id);
+
+ /**
+ * 【管理员】获得会员经验记录分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 会员经验记录分页
+ */
+ PageResult getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO);
+
+ /**
+ * 【会员】获得会员经验记录分页
+ *
+ * @param userId 用户编号
+ * @param pageParam 分页查询
+ * @return 会员经验记录分页
+ */
+ PageResult getExperienceRecordPage(Long userId, PageParam pageParam);
+
+ /**
+ * 根据业务类型, 创建 经验变动记录
+ *
+ * @param userId 会员编号
+ * @param experience 变动经验值
+ * @param totalExperience 会员当前的经验
+ * @param bizType 业务类型
+ * @param bizId 业务ID
+ */
+ void createExperienceRecord(Long userId, Integer experience, Integer totalExperience,
+ MemberExperienceBizTypeEnum bizType, String bizId);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java
new file mode 100644
index 000000000..80ecc84d6
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.dal.mysql.level.MemberExperienceRecordMapper;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 会员经验记录 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberExperienceRecordServiceImpl implements MemberExperienceRecordService {
+
+ @Resource
+ private MemberExperienceRecordMapper experienceLogMapper;
+
+ @Override
+ public MemberExperienceRecordDO getExperienceRecord(Long id) {
+ return experienceLogMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO) {
+ return experienceLogMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public PageResult getExperienceRecordPage(Long userId, PageParam pageParam) {
+ return experienceLogMapper.selectPage(userId, pageParam);
+ }
+
+ @Override
+ public void createExperienceRecord(Long userId, Integer experience, Integer totalExperience,
+ MemberExperienceBizTypeEnum bizType, String bizId) {
+ String description = StrUtil.format(bizType.getDescription(), experience);
+ MemberExperienceRecordDO record = MemberExperienceRecordConvert.INSTANCE.convert(
+ userId, experience, totalExperience,
+ bizId, bizType.getType(), bizType.getTitle(), description);
+ experienceLogMapper.insert(record);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java
new file mode 100644
index 000000000..b5e4f669e
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+
+/**
+ * 会员等级记录 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberLevelRecordService {
+
+ /**
+ * 获得会员等级记录
+ *
+ * @param id 编号
+ * @return 会员等级记录
+ */
+ MemberLevelRecordDO getLevelRecord(Long id);
+
+ /**
+ * 获得会员等级记录分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 会员等级记录分页
+ */
+ PageResult getLevelRecordPage(MemberLevelRecordPageReqVO pageReqVO);
+
+ /**
+ * 创建会员等级记录
+ *
+ * @param levelRecord 会员等级记录
+ */
+ void createLevelRecord(MemberLevelRecordDO levelRecord);
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java
new file mode 100644
index 000000000..810961241
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
+import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelRecordMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 会员等级记录 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberLevelRecordServiceImpl implements MemberLevelRecordService {
+
+ @Resource
+ private MemberLevelRecordMapper levelLogMapper;
+
+ @Override
+ public MemberLevelRecordDO getLevelRecord(Long id) {
+ return levelLogMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getLevelRecordPage(MemberLevelRecordPageReqVO pageReqVO) {
+ return levelLogMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public void createLevelRecord(MemberLevelRecordDO levelRecord) {
+ levelLogMapper.insert(levelRecord);
+ }
+
+}
diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java
new file mode 100644
index 000000000..76d46e5c3
--- /dev/null
+++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java
@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.member.service.level;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelCreateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelListReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateLevelReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 会员等级 Service 接口
+ *
+ * @author owen
+ */
+public interface MemberLevelService {
+
+ /**
+ * 创建会员等级
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createLevel(@Valid MemberLevelCreateReqVO createReqVO);
+
+ /**
+ * 更新会员等级
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updateLevel(@Valid MemberLevelUpdateReqVO updateReqVO);
+
+ /**
+ * 删除会员等级
+ *
+ * @param id 编号
+ */
+ void deleteLevel(Long id);
+
+ /**
+ * 获得会员等级
+ *
+ * @param id 编号
+ * @return 会员等级
+ */
+ MemberLevelDO getLevel(Long id);
+
+ /**
+ * 获得会员等级列表
+ *
+ * @param ids 编号
+ * @return 会员等级列表
+ */
+ List getLevelList(Collection