Kaynağa Gözat

会员: 增加会员分组功能

owen 1 yıl önce
ebeveyn
işleme
8ae3401452
25 değiştirilmiş dosya ile 807 ekleme ve 29 silme
  1. 36 0
      sql/mysql/member_group.sql
  2. 5 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  3. 91 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/MemberGroupController.java
  4. 26 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupBaseVO.java
  5. 14 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupCreateReqVO.java
  6. 30 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupPageReqVO.java
  7. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupRespVO.java
  8. 18 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupSimpleRespVO.java
  9. 20 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupUpdateReqVO.java
  10. 1 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/MemberLevelSimpleRespVO.java
  11. 17 7
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
  12. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java
  13. 4 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java
  14. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java
  15. 35 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/group/MemberGroupConvert.java
  16. 5 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java
  17. 45 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/group/MemberGroupDO.java
  18. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java
  19. 31 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/group/MemberGroupMapper.java
  20. 5 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
  21. 86 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupService.java
  22. 102 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java
  23. 160 0
      yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java
  24. 2 1
      yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql
  25. 42 16
      yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql

+ 36 - 0
sql/mysql/member_group.sql

@@ -0,0 +1,36 @@
+create table member_group
+(
+    id          bigint auto_increment comment '编号' primary key,
+    name        varchar(30)  default ''                not null comment '名称',
+    remark      varchar(255) default ''                not null comment '备注',
+    status      tinyint      default 0                 not null comment '状态',
+    creator     varchar(64)  default ''                null comment '创建者',
+    create_time datetime     default CURRENT_TIMESTAMP not null comment '创建时间',
+    updater     varchar(64)  default ''                null comment '更新者',
+    update_time datetime     default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
+    deleted     bit          default b'0'              not null comment '是否删除',
+    tenant_id   bigint       default 0                 not null comment '租户编号'
+)
+    comment '用户分组';
+
+alter table member_user add column group_id bigint null comment '用户分组编号';
+
+-- 菜单 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('用户分组', '', 2, 5, 2262, 'group', '', 'member/group/index', 0, 'MemberGroup');
+
+-- 按钮父菜单ID
+-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮 SQL
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户分组查询', 'member:group:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户分组创建', 'member:group:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户分组更新', 'member:group:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户分组删除', 'member:group:delete', 3, 4, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('用户分组导出', 'member:group:export', 3, 5, @parentId, '', '', '', 0);

+ 5 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -52,4 +52,9 @@ public interface ErrorCodeConstants {
     ErrorCode LEVEL_LOG_NOT_EXISTS = new ErrorCode(1004007100, "会员等级记录不存在");
     ErrorCode EXPERIENCE_LOG_NOT_EXISTS = new ErrorCode(1004007200, "会员经验记录不存在");
     ErrorCode LEVEL_REASON_NOT_EXISTS = new ErrorCode(1004007300, "会员等级调整原因不能为空");
+
+    //========== 用户分组 1004011000 ==========
+    ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1004011000, "用户分组不存在");
+    ErrorCode GROUP_HAS_USER = new ErrorCode(1004011001, "用户分组下存在用户,无法删除");
+
 }

+ 91 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/MemberGroupController.java

@@ -0,0 +1,91 @@
+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.Collection;
+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<Long> createGroup(@Valid @RequestBody MemberGroupCreateReqVO createReqVO) {
+        return success(groupService.createGroup(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新用户分组")
+    @PreAuthorize("@ss.hasPermission('member:group:update')")
+    public CommonResult<Boolean> 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<Boolean> 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<MemberGroupRespVO> getGroup(@RequestParam("id") Long id) {
+        MemberGroupDO group = groupService.getGroup(id);
+        return success(MemberGroupConvert.INSTANCE.convert(group));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得用户分组列表")
+    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
+    @PreAuthorize("@ss.hasPermission('member:group:query')")
+    public CommonResult<List<MemberGroupRespVO>> getGroupList(@RequestParam("ids") Collection<Long> ids) {
+        List<MemberGroupDO> list = groupService.getGroupList(ids);
+        return success(MemberGroupConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/list-all-simple")
+    @Operation(summary = "获取会员分组精简信息列表", description = "只包含被开启的会员分组,主要用于前端的下拉选项")
+    public CommonResult<List<MemberGroupSimpleRespVO>> getSimpleGroupList() {
+        // 获用户列表,只要开启状态的
+        List<MemberGroupDO> list = groupService.getEnableGroupList();
+        return success(MemberGroupConvert.INSTANCE.convertSimpleList(list));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得用户分组分页")
+    @PreAuthorize("@ss.hasPermission('member:group:query')")
+    public CommonResult<PageResult<MemberGroupRespVO>> getGroupPage(@Valid MemberGroupPageReqVO pageVO) {
+        PageResult<MemberGroupDO> pageResult = groupService.getGroupPage(pageVO);
+        return success(MemberGroupConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 26 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/group/vo/MemberGroupBaseVO.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.member.controller.admin.group.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 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 = "状态不能为空")
+    private Integer status;
+
+}

+ 14 - 0
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 {
+
+}

+ 30 - 0
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;
+
+}

+ 22 - 0
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;
+
+}

+ 18 - 0
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;
+
+}

+ 20 - 0
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;
+
+}

+ 1 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/MemberLevelSimpleRespVO.java

@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.member.controller.admin.level.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
 import lombok.ToString;
 
 /**
@@ -10,9 +9,8 @@ import lombok.ToString;
  */
 @Schema(description = "管理后台 - 会员等级 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class MemberLevelSimpleRespVO extends MemberLevelBaseVO {
+public class MemberLevelSimpleRespVO {
 
     @Schema(description = "编号", example = "6103")
     private Long id;

+ 17 - 7
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java

@@ -7,9 +7,11 @@ import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReq
 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.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.service.group.MemberGroupService;
 import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
 import cn.iocoder.yudao.module.member.service.tag.MemberTagService;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
@@ -22,10 +24,7 @@ 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.*;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -42,6 +41,8 @@ public class MemberUserController {
     private MemberTagService memberTagService;
     @Resource
     private MemberLevelService memberLevelService;
+    @Resource
+    private MemberGroupService memberGroupService;
 
     @PutMapping("/update")
     @Operation(summary = "更新会员用户")
@@ -69,18 +70,27 @@ public class MemberUserController {
             return success(PageResult.empty());
         }
 
-        // 处理会员标签返显
+        Set<Long> groupIds = new HashSet<>(pageResult.getList().size());
+
+        // 处理用户标签返显
         Set<Long> tagIds = pageResult.getList().stream()
+                .peek(m -> {
+                    if (m.getGroupId() != null) {
+                        groupIds.add(m.getGroupId());
+                    }
+                })
                 .map(MemberUserDO::getTagIds)
                 .filter(Objects::nonNull)
                 .flatMap(Collection::stream)
                 .collect(Collectors.toSet());
         List<MemberTagDO> tags = memberTagService.getTagList(tagIds);
 
-        // 处理会员级别返显
+        // 处理用户级别返显
         List<MemberLevelDO> levels = memberLevelService.getEnableLevelList();
+        // 处理用户分组返显
+        List<MemberGroupDO> groups = memberGroupService.getGroupList(groupIds);
 
-        return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags, levels));
+        return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags, levels, groups));
     }
 
 }

+ 3 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java

@@ -56,4 +56,7 @@ public class MemberUserBaseVO {
     @Schema(description = "会员等级编号", example = "1")
     private Long levelId;
 
+    @Schema(description = "用户分组编号", example = "1")
+    private Long groupId;
+
 }

+ 4 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java

@@ -35,7 +35,10 @@ public class MemberUserPageReqVO extends PageParam {
     @Schema(description = "会员标签编号列表", example = "[1, 2]")
     private List<Long> tagIds;
 
-    @Schema(description = "会员等级号", example = "1")
+    @Schema(description = "会员等级号", example = "1")
     private Long levelId;
 
+    @Schema(description = "用户分组编号", example = "1")
+    private Long groupId;
+
 }

+ 3 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java

@@ -38,4 +38,7 @@ public class MemberUserRespVO extends MemberUserBaseVO {
     @Schema(description = "会员等级", example = "黄金会员")
     private String levelName;
 
+    @Schema(description = "用户分组", example = "购物达人")
+    private String groupName;
+
 }

+ 35 - 0
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<MemberGroupRespVO> convertList(List<MemberGroupDO> list);
+
+    PageResult<MemberGroupRespVO> convertPage(PageResult<MemberGroupDO> page);
+
+    List<MemberGroupSimpleRespVO> convertSimpleList(List<MemberGroupDO> list);
+}

+ 5 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java

@@ -7,6 +7,7 @@ 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.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;
@@ -38,17 +39,20 @@ public interface MemberUserConvert {
 
     default PageResult<MemberUserRespVO> convertPage(PageResult<MemberUserDO> pageResult,
                                                      List<MemberTagDO> tags,
-                                                     List<MemberLevelDO> levels) {
+                                                     List<MemberLevelDO> levels,
+                                                     List<MemberGroupDO> groups) {
         PageResult<MemberUserRespVO> result = convertPage(pageResult);
 
         // 处理关联数据
         Map<Long, String> tagMap = convertMap(tags, MemberTagDO::getId, MemberTagDO::getName);
         Map<Long, String> levelMap = convertMap(levels, MemberLevelDO::getId, MemberLevelDO::getName);
+        Map<Long, String> groupMap = convertMap(groups, MemberGroupDO::getId, MemberGroupDO::getName);
 
         // 填充关联数据
         for (MemberUserRespVO vo : result.getList()) {
             vo.setTagNames(convertList(vo.getTagIds(), tagMap::get));
             vo.setLevelName(MapUtil.getStr(levelMap, vo.getLevelId(), StrUtil.EMPTY));
+            vo.setGroupName(MapUtil.getStr(groupMap, vo.getGroupId(), StrUtil.EMPTY));
         }
         return result;
     }

+ 45 - 0
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;
+    /**
+     * 状态
+     * <p>
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+
+}

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/dataobject/user/MemberUserDO.java

@@ -124,6 +124,10 @@ public class MemberUserDO extends TenantBaseDO {
      * 会员经验
      */
     private Integer experience;
+    /**
+     * 用户分组编号
+     */
+    private Long groupId;
 
     // TODO 积分等等
 }

+ 31 - 0
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<MemberGroupDO> {
+
+    default PageResult<MemberGroupDO> selectPage(MemberGroupPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MemberGroupDO>()
+                .likeIfPresent(MemberGroupDO::getName, reqVO.getName())
+                .eqIfPresent(MemberGroupDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(MemberGroupDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(MemberGroupDO::getId));
+    }
+
+    default List<MemberGroupDO> selectListByStatus(Integer status) {
+        return selectList(MemberGroupDO::getStatus, status);
+    }
+}

+ 5 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java

@@ -45,6 +45,7 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
                 .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));
     }
@@ -61,4 +62,8 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
                 .set(MemberUserDO::getExperience, 0)
                 .set(MemberUserDO::getLevelId, null));
     }
+
+    default Long selectCountByGroupId(Long groupId) {
+        return selectCount(MemberUserDO::getGroupId, groupId);
+    }
 }

+ 86 - 0
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<MemberGroupDO> getGroupList(Collection<Long> ids);
+
+    /**
+     * 获得用户分组分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 用户分组分页
+     */
+    PageResult<MemberGroupDO> getGroupPage(MemberGroupPageReqVO pageReqVO);
+
+
+    /**
+     * 获得指定状态的用户分组列表
+     *
+     * @param status 状态
+     * @return 用户分组列表
+     */
+    List<MemberGroupDO> getGroupListByStatus(Integer status);
+
+
+    /**
+     * 获得开启状态的用户分组列表
+     *
+     * @return 用户分组列表
+     */
+    default List<MemberGroupDO> getEnableGroupList() {
+        return getGroupListByStatus(CommonStatusEnum.ENABLE.getStatus());
+    }
+
+}

+ 102 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImpl.java

@@ -0,0 +1,102 @@
+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.dal.mysql.user.MemberUserMapper;
+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 groupMapper;
+    @Resource
+    private MemberUserMapper memberUserMapper;
+
+    @Override
+    public Long createGroup(MemberGroupCreateReqVO createReqVO) {
+        // 插入
+        MemberGroupDO group = MemberGroupConvert.INSTANCE.convert(createReqVO);
+        groupMapper.insert(group);
+        // 返回
+        return group.getId();
+    }
+
+    @Override
+    public void updateGroup(MemberGroupUpdateReqVO updateReqVO) {
+        // 校验存在
+        validateGroupExists(updateReqVO.getId());
+        // 更新
+        MemberGroupDO updateObj = MemberGroupConvert.INSTANCE.convert(updateReqVO);
+        groupMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteGroup(Long id) {
+        // 校验存在
+        validateGroupExists(id);
+        // 校验分组下是否有用户
+        validateGroupHasUser(id);
+        // 删除
+        groupMapper.deleteById(id);
+    }
+
+    void validateGroupExists(Long id) {
+        if (groupMapper.selectById(id) == null) {
+            throw exception(GROUP_NOT_EXISTS);
+        }
+    }
+
+    void validateGroupHasUser(Long id) {
+        Long count = memberUserMapper.selectCountByGroupId(id);
+        if (count > 0) {
+            throw exception(GROUP_HAS_USER);
+        }
+    }
+
+    @Override
+    public MemberGroupDO getGroup(Long id) {
+        return groupMapper.selectById(id);
+    }
+
+    @Override
+    public List<MemberGroupDO> getGroupList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return ListUtil.empty();
+        }
+        return groupMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<MemberGroupDO> getGroupPage(MemberGroupPageReqVO pageReqVO) {
+        return groupMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<MemberGroupDO> getGroupListByStatus(Integer status) {
+        return groupMapper.selectListByStatus(status);
+    }
+
+}

+ 160 - 0
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/group/MemberGroupServiceImplTest.java

@@ -0,0 +1,160 @@
+package cn.iocoder.yudao.module.member.service.group;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+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 cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
+import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
+import cn.iocoder.yudao.module.system.enums.common.SexEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.GROUP_HAS_USER;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.GROUP_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link MemberGroupServiceImpl} 的单元测试类
+ *
+ * @author owen
+ */
+@Import(MemberGroupServiceImpl.class)
+public class MemberGroupServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private MemberGroupServiceImpl groupService;
+
+    @Resource
+    private MemberGroupMapper groupMapper;
+    @Resource
+    private MemberUserMapper memberUserMapper;
+
+    @Test
+    public void testCreateGroup_success() {
+        // 准备参数
+        MemberGroupCreateReqVO reqVO = randomPojo(MemberGroupCreateReqVO.class);
+
+        // 调用
+        Long groupId = groupService.createGroup(reqVO);
+        // 断言
+        assertNotNull(groupId);
+        // 校验记录的属性是否正确
+        MemberGroupDO group = groupMapper.selectById(groupId);
+        assertPojoEquals(reqVO, group);
+    }
+
+    @Test
+    public void testUpdateGroup_success() {
+        // mock 数据
+        MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class);
+        groupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        MemberGroupUpdateReqVO reqVO = randomPojo(MemberGroupUpdateReqVO.class, o -> {
+            o.setId(dbGroup.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        groupService.updateGroup(reqVO);
+        // 校验是否更新正确
+        MemberGroupDO group = groupMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, group);
+    }
+
+    @Test
+    public void testUpdateGroup_notExists() {
+        // 准备参数
+        MemberGroupUpdateReqVO reqVO = randomPojo(MemberGroupUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> groupService.updateGroup(reqVO), GROUP_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteGroup_success() {
+        // mock 数据
+        MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class);
+        groupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbGroup.getId();
+
+        // 调用
+        groupService.deleteGroup(id);
+        // 校验数据不存在了
+        assertNull(groupMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteGroup_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> groupService.deleteGroup(id), GROUP_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteGroup_hasUser() {
+        // mock 数据
+        MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class);
+        groupMapper.insert(dbGroup);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbGroup.getId();
+
+        // mock 会员数据
+        MemberUserDO dbUser = randomPojo(MemberUserDO.class, o -> {
+            o.setGroupId(id);
+            o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
+        });
+        memberUserMapper.insert(dbUser);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> groupService.deleteGroup(id), GROUP_HAS_USER);
+    }
+
+    @Test
+    public void testGetGroupPage() {
+        String name = randomString();
+        int status = CommonStatusEnum.ENABLE.getStatus();
+
+        // mock 数据
+        MemberGroupDO dbGroup = randomPojo(MemberGroupDO.class, o -> { // 等会查询到
+            o.setName(name);
+            o.setStatus(status);
+            o.setCreateTime(buildTime(2023, 2, 18));
+        });
+        groupMapper.insert(dbGroup);
+        // 测试 name 不匹配
+        groupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setName("")));
+        // 测试 status 不匹配
+        groupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 createTime 不匹配
+        groupMapper.insert(cloneIgnoreId(dbGroup, o -> o.setCreateTime(null)));
+        // 准备参数
+        MemberGroupPageReqVO reqVO = new MemberGroupPageReqVO();
+        reqVO.setName(name);
+        reqVO.setStatus(status);
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+        // 调用
+        PageResult<MemberGroupDO> pageResult = groupService.getGroupPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbGroup, pageResult.getList().get(0));
+    }
+
+}

+ 2 - 1
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/clean.sql

@@ -1,4 +1,5 @@
 DELETE FROM "member_user";
 DELETE FROM "member_address";
 DELETE FROM "member_tag";
-DELETE FROM "member_level";
+DELETE FROM "member_level";
+DELETE FROM "member_group";

+ 42 - 16
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql

@@ -1,19 +1,30 @@
-CREATE TABLE IF NOT EXISTS "member_user"  (
-    "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
-    "nickname" varchar(30)  NOT NULL DEFAULT '' COMMENT '用户昵称',
-    "avatar" varchar(255)  NOT NULL DEFAULT '' COMMENT '头像',
-    "status" tinyint NOT NULL COMMENT '状态',
-    "mobile" varchar(11)  NOT NULL COMMENT '手机号',
-    "password" varchar(100)  NOT NULL DEFAULT '' COMMENT '密码',
+CREATE TABLE IF NOT EXISTS "member_user"
+(
+    "id"          bigint       NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
+    "nickname"    varchar(30)  NOT NULL DEFAULT '' COMMENT '用户昵称',
+    "name"        varchar(30)  NULL COMMENT '真实名字',
+    sex           tinyint      null comment '性别',
+    birthday      datetime     null comment '出生日期',
+    area_id       int          null comment '所在地',
+    mark          varchar(255) null comment '用户备注',
+    point         int                   default 0 null comment '积分',
+    "avatar"      varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
+    "status"      tinyint      NOT NULL COMMENT '状态',
+    "mobile"      varchar(11)  NOT NULL COMMENT '手机号',
+    "password"    varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
     "register_ip" varchar(32)  NOT NULL COMMENT '注册 IP',
-    "login_ip" varchar(50) NULL DEFAULT '' COMMENT '最后登录IP',
-    "login_date" datetime NULL DEFAULT NULL COMMENT '最后登录时间',
-    "creator" varchar(64)  NULL DEFAULT '' COMMENT '创建者',
-    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    "updater" varchar(64)  NULL DEFAULT '' COMMENT '更新者',
-    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
-    "deleted" bit(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
-    "tenant_id" bigint not null default  '0',
+    "login_ip"    varchar(50)  NULL     DEFAULT '' COMMENT '最后登录IP',
+    "login_date"  datetime     NULL     DEFAULT NULL COMMENT '最后登录时间',
+    "tag_ids"     varchar(255) NULL     DEFAULT NULL COMMENT '用户标签编号列表,以逗号分隔',
+    "level_id"    bigint       NULL     DEFAULT NULL COMMENT '等级编号',
+    "experience"  bigint       NULL     DEFAULT NULL COMMENT '经验',
+    "group_id"    bigint       NULL     DEFAULT NULL COMMENT '用户分组编号',
+    "creator"     varchar(64)  NULL     DEFAULT '' COMMENT '创建者',
+    "create_time" datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    "updater"     varchar(64)  NULL     DEFAULT '' COMMENT '更新者',
+    "update_time" datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    "deleted"     bit(1)       NOT NULL DEFAULT '0' COMMENT '是否删除',
+    "tenant_id"   bigint       not null default '0',
     PRIMARY KEY ("id")
 ) COMMENT '会员表';
 
@@ -63,4 +74,19 @@ CREATE TABLE IF NOT EXISTS "member_level"
     "tenant_id"      bigint   not null default '0',
     "status"         int      NOT NULL,
     PRIMARY KEY ("id")
-) COMMENT '会员等级';
+) COMMENT '会员等级';
+
+CREATE TABLE IF NOT EXISTS "member_group"
+(
+    "id"          bigint   NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "name"        varchar  NOT NULL,
+    "remark"      varchar  NOT NULL,
+    "status"      varchar  NOT NULL,
+    "creator"     varchar           DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater"     varchar           DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted"     bit      NOT NULL DEFAULT FALSE,
+    "tenant_id"      bigint   not null default '0',
+    PRIMARY KEY ("id")
+) COMMENT '用户分组';