فهرست منبع

会员:后台调整会员等级

owen 1 سال پیش
والد
کامیت
8ffbc15cec
21فایلهای تغییر یافته به همراه413 افزوده شده و 61 حذف شده
  1. 20 47
      sql/mysql/member_level.sql
  2. 21 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java
  3. 1 0
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java
  4. 9 5
      yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java
  5. 25 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java
  6. 2 3
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java
  7. 9 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/MemberUserController.java
  8. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserBaseVO.java
  9. 4 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserPageReqVO.java
  10. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserRespVO.java
  11. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/user/vo/MemberUserUpdateReqVO.java
  12. 11 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java
  13. 3 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java
  14. 13 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/user/MemberUserMapper.java
  15. 20 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogService.java
  16. 22 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogServiceImpl.java
  17. 27 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogService.java
  18. 51 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogServiceImpl.java
  19. 33 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java
  20. 123 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java
  21. 10 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

+ 20 - 47
sql/mysql/member_level.sql

@@ -9,7 +9,7 @@ create table member_level
     name           varchar(30)  default ''                not null comment '等级名称',
     experience     int          default 0                 not null comment '升级经验',
     level          int          default 0                 not null comment '等级',
-    discount       int(4)       default 100               not null comment '享受折扣',
+    discount       tinyint      default 100               not null comment '享受折扣',
     icon           varchar(255) default ''                not null comment '等级图标',
     background_url varchar(255) default ''                not null comment '等级背景图',
     status         tinyint      default 0                 not null comment '状态',
@@ -28,9 +28,9 @@ create table member_level_log
     user_id         bigint       default 0                 not null comment '用户编号',
     level_id        bigint       default 0                 not null comment '等级编号',
     level           int          default 0                 not null comment '会员等级',
-    discount        int(4)       default 100               not null comment '享受折扣',
-    experience      int(4)       default 0                 not null comment '升级经验',
-    user_experience int(4)       default 0                 not null comment '会员此时的经验',
+    discount        tinyint      default 100               not null comment '享受折扣',
+    experience      int          default 0                 not null comment '升级经验',
+    user_experience int          default 0                 not null comment '会员此时的经验',
     remark          varchar(255) default ''                not null comment '备注',
     description     varchar(255) default ''                not null comment '描述',
     creator         varchar(64)  default ''                null comment '创建者',
@@ -68,54 +68,27 @@ create index idx_user_biz_type on member_experience_log (user_id, biz_type) comm
 
 -- 增加字典
 insert system_dict_type(name, type) values ('会员经验业务类型', 'member_experience_biz_type');
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '系统', '0', 0);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '订单', '1', 1);
-insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '签到', '2', 2);
+insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '管理员调整', '0', 0);
+insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '邀新奖励', '1', 1);
+insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '下单奖励', '2', 2);
+insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '退单扣除', '3', 3);
+insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '签到奖励', '4', 4);
+insert system_dict_data(dict_type, label, value, sort) values ('member_experience_biz_type', '抽奖奖励', '5', 5);
 
 -- 菜单 SQL
-INSERT INTO system_menu(
-    name, permission, type, sort, parent_id,
-    path, icon, component, status, component_name
-)
-VALUES (
-    '会员等级', '', 2, 3, 2262,
-    'level', '', 'member/level/index', 0, 'MemberLevel'
-);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status, component_name)
+VALUES ('会员等级', '', 2, 3, 2262, 'level', '', 'member/level/index', 0, 'MemberLevel');
 
 -- 按钮父菜单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:level:query', 3, 1, @parentId,
-    '', '', '', 0
-);
-INSERT INTO system_menu(
-    name, permission, type, sort, parent_id,
-    path, icon, component, status
-)
-VALUES (
-    '会员等级创建', 'member:level:create', 3, 2, @parentId,
-    '', '', '', 0
-);
-INSERT INTO system_menu(
-    name, permission, type, sort, parent_id,
-    path, icon, component, status
-)
-VALUES (
-    '会员等级更新', 'member:level:update', 3, 3, @parentId,
-    '', '', '', 0
-);
-INSERT INTO system_menu(
-    name, permission, type, sort, parent_id,
-    path, icon, component, status
-)
-VALUES (
-    '会员等级删除', 'member:level:delete', 3, 4, @parentId,
-    '', '', '', 0
-);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('会员等级查询', 'member:level:query', 3, 1, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('会员等级创建', 'member:level:create', 3, 2, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('会员等级更新', 'member:level:update', 3, 3, @parentId, '', '', '', 0);
+INSERT INTO system_menu(name, permission, type, sort, parent_id, path, icon, component, status)
+VALUES ('会员等级删除', 'member:level:delete', 3, 4, @parentId, '', '', '', 0);

+ 21 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApi.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.member.api.level;
+
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
+
+/**
+ * 会员等级 API接口
+ *
+ * @author owen
+ */
+public interface MemberLevelApi {
+
+    /**
+     * 增加会员经验
+     *
+     * @param userId     会员ID
+     * @param experience 经验
+     * @param bizType    业务类型
+     * @param bizId      业务编号
+     */
+    void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId);
+}

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

@@ -51,4 +51,5 @@ 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, "会员等级调整原因不能为空");
 }

+ 9 - 5
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/MemberExperienceBizTypeEnum.java

@@ -12,13 +12,17 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum MemberExperienceBizTypeEnum {
     /**
-     *
+     * 管理员调整、邀请新用户、下单、退单、签到、抽奖
      */
-    SYSTEM(0, "系统"),
-    ORDER(1, "订单"),
-    SIGN_IN(2, "签到"),
+    ADMIN(0, "管理员调整","管理员调整获得{}经验"),
+    INVITE_REGISTER(1, "邀新奖励","邀请好友获得{}经验"),
+    ORDER(2, "下单奖励", "下单获得{}经验"),
+    REFUND(3, "退单扣除","退单获得{}经验"),
+    SIGN_IN(4, "签到奖励","签到获得{}经验"),
+    LOTTERY(5, "抽奖奖励","抽奖获得{}经验"),
     ;
 
     private final int value;
-    private final String name;
+    private final String title;
+    private final String desc;
 }

+ 25 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/level/MemberLevelApiImpl.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.member.api.level;
+
+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;
+
+/**
+ * 会员等级 API 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class MemberLevelApiImpl implements MemberLevelApi {
+
+    @Resource
+    private MemberLevelService memberLevelService;
+
+    public void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId) {
+        memberLevelService.plusExperience(userId, experience, bizType, bizId);
+    }
+}

+ 2 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java

@@ -1,6 +1,5 @@
 package cn.iocoder.yudao.module.member.controller.admin.level;
 
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 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.*;
@@ -77,9 +76,9 @@ public class MemberLevelController {
 
     @GetMapping("/list-all-simple")
     @Operation(summary = "获取会员等级精简信息列表", description = "只包含被开启的会员等级,主要用于前端的下拉选项")
-    public CommonResult<List<MemberLevelSimpleRespVO>> getSimpleUserList() {
+    public CommonResult<List<MemberLevelSimpleRespVO>> getSimpleLevelList() {
         // 获用户列表,只要开启状态的
-        List<MemberLevelDO> list = levelService.getLevelListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        List<MemberLevelDO> list = levelService.getEnableLevelList();
         // 排序后,返回给前端
         return success(MemberLevelConvert.INSTANCE.convertSimpleList(list));
     }

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

@@ -7,8 +7,10 @@ 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.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.level.MemberLevelService;
 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;
@@ -38,6 +40,8 @@ public class MemberUserController {
     private MemberUserService memberUserService;
     @Resource
     private MemberTagService memberTagService;
+    @Resource
+    private MemberLevelService memberLevelService;
 
     @PutMapping("/update")
     @Operation(summary = "更新会员用户")
@@ -72,7 +76,11 @@ public class MemberUserController {
                 .flatMap(Collection::stream)
                 .collect(Collectors.toSet());
         List<MemberTagDO> tags = memberTagService.getTagList(tagIds);
-        return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags));
+
+        // 处理会员级别返显
+        List<MemberLevelDO> levels = memberLevelService.getEnableLevelList();
+
+        return success(MemberUserConvert.INSTANCE.convertPage(pageResult, tags, levels));
     }
 
 }

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

@@ -53,4 +53,7 @@ public class MemberUserBaseVO {
     @Schema(description = "会员标签", example = "[1, 2]")
     private List<Long> tagIds;
 
+    @Schema(description = "会员等级编号", example = "1")
+    private Long levelId;
+
 }

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

@@ -32,7 +32,10 @@ public class MemberUserPageReqVO extends PageParam {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
-    @Schema(description = "会员标签", example = "[1, 2]")
+    @Schema(description = "会员标签编号列表", example = "[1, 2]")
     private List<Long> tagIds;
 
+    @Schema(description = "会员等级标号", example = "1")
+    private Long levelId;
+
 }

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

@@ -35,4 +35,7 @@ public class MemberUserRespVO extends MemberUserBaseVO {
     @Schema(description = "会员标签", example = "[红色, 快乐]")
     private List<String> tagNames;
 
+    @Schema(description = "会员等级", example = "黄金会员")
+    private String levelName;
+
 }

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

@@ -17,4 +17,7 @@ public class MemberUserUpdateReqVO extends MemberUserBaseVO {
     @NotNull(message = "编号不能为空")
     private Long id;
 
+    @Schema(description = "会员级别修改原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "推广需要")
+    private String levelReason;
+
 }

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

@@ -1,10 +1,13 @@
 package cn.iocoder.yudao.module.member.convert.user;
 
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
 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.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;
@@ -34,11 +37,18 @@ public interface MemberUserConvert {
     MemberUserRespVO convert03(MemberUserDO bean);
 
     default PageResult<MemberUserRespVO> convertPage(PageResult<MemberUserDO> pageResult,
-                                                     List<MemberTagDO> tags) {
+                                                     List<MemberTagDO> tags,
+                                                     List<MemberLevelDO> levels) {
         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);
+
+        // 填充关联数据
         for (MemberUserRespVO vo : result.getList()) {
             vo.setTagNames(convertList(vo.getTagIds(), tagMap::get));
+            vo.setLevelName(MapUtil.getStr(levelMap, vo.getLevelId(), StrUtil.EMPTY));
         }
         return result;
     }

+ 3 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java

@@ -26,6 +26,8 @@ public interface MemberLevelMapper extends BaseMapperX<MemberLevelDO> {
 
 
     default List<MemberLevelDO> selectListByStatus(Integer status) {
-        return selectList(MemberLevelDO::getStatus, status);
+        return selectList(new LambdaQueryWrapperX<MemberLevelDO>()
+                .eq(MemberLevelDO::getStatus, status)
+                .orderByAsc(MemberLevelDO::getLevel));
     }
 }

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

@@ -7,6 +7,7 @@ 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;
@@ -47,4 +48,16 @@ public interface MemberUserMapper extends BaseMapperX<MemberUserDO> {
                 .orderByDesc(MemberUserDO::getId));
     }
 
+    /**
+     * 取消会员的等级
+     *
+     * @param userId 会员编号
+     * @return 受影响的行数
+     */
+    default int cancelUserLevel(Long userId) {
+        return update(null, new LambdaUpdateWrapper<MemberUserDO>()
+                .eq(MemberUserDO::getId, userId)
+                .set(MemberUserDO::getExperience, 0)
+                .set(MemberUserDO::getLevelId, null));
+    }
 }

+ 20 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogService.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogExportReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 
 import java.util.Collection;
 import java.util.List;
@@ -54,4 +55,23 @@ public interface MemberExperienceLogService {
      */
     List<MemberExperienceLogDO> getExperienceLogList(MemberExperienceLogExportReqVO exportReqVO);
 
+    /**
+     * 创建 手动调整 经验变动记录
+     *
+     * @param userId          会员编号
+     * @param experience      变动经验值
+     * @param totalExperience 会员当前的经验
+     */
+    void createAdjustLog(Long userId, int experience, int totalExperience);
+
+    /**
+     * 根据业务类型, 创建 经验变动记录
+     *
+     * @param userId          会员编号
+     * @param experience      变动经验值
+     * @param totalExperience 会员当前的经验
+     * @param bizType         业务类型
+     * @param bizId           业务ID
+     */
+    void createBizLog(Long userId, int experience, int totalExperience, MemberExperienceBizTypeEnum bizType, String bizId);
 }

+ 22 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceLogServiceImpl.java

@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.module.member.service.level;
 
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogExportReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceLogPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceLogDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberExperienceLogMapper;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -61,4 +63,24 @@ public class MemberExperienceLogServiceImpl implements MemberExperienceLogServic
         return experienceLogMapper.selectList(exportReqVO);
     }
 
+    @Override
+    public void createAdjustLog(Long userId, int experience, int totalExperience) {
+        // 管理员调整时, 没有业务编号, 记录对应的枚举值
+        String bizId = MemberExperienceBizTypeEnum.ADMIN.getValue() + "";
+        this.createBizLog(userId, experience, totalExperience, MemberExperienceBizTypeEnum.ADMIN, bizId);
+    }
+
+    @Override
+    public void createBizLog(Long userId, int experience, int totalExperience, MemberExperienceBizTypeEnum bizType, String bizId) {
+        MemberExperienceLogDO experienceLogDO = new MemberExperienceLogDO();
+        experienceLogDO.setUserId(userId);
+        experienceLogDO.setExperience(experience);
+        experienceLogDO.setTotalExperience(totalExperience);
+        experienceLogDO.setBizId(bizId);
+        experienceLogDO.setBizType(bizType.getValue());
+        experienceLogDO.setTitle(bizType.getTitle());
+        experienceLogDO.setDescription(StrUtil.format(bizType.getDesc(), experience));
+        experienceLogMapper.insert(experienceLogDO);
+    }
+
 }

+ 27 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogService.java

@@ -3,7 +3,9 @@ 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.log.MemberLevelLogExportReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 
 import java.util.Collection;
 import java.util.List;
@@ -54,4 +56,29 @@ public interface MemberLevelLogService {
      */
     List<MemberLevelLogDO> getLevelLogList(MemberLevelLogExportReqVO exportReqVO);
 
+    /**
+     * 创建记录: 取消等级
+     *
+     * @param userId 会员编号
+     * @param reason 调整原因
+     */
+    void createCancelLog(Long userId, String reason);
+
+    /**
+     * 创建记录: 手动调整
+     *
+     * @param user       会员
+     * @param level      等级
+     * @param experience 变动经验值
+     * @param reason     调整原因
+     */
+    void createAdjustLog(MemberUserDO user, MemberLevelDO level, int experience, String reason);
+
+    /**
+     * 创建记录: 自动升级
+     *
+     * @param user  会员
+     * @param level 等级
+     */
+    void createAutoUpgradeLog(MemberUserDO user, MemberLevelDO level);
 }

+ 51 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelLogServiceImpl.java

@@ -3,7 +3,9 @@ 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.log.MemberLevelLogExportReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelLogPageReqVO;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelLogDO;
+import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelLogMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -61,4 +63,53 @@ public class MemberLevelLogServiceImpl implements MemberLevelLogService {
         return levelLogMapper.selectList(exportReqVO);
     }
 
+    @Override
+    public void createCancelLog(Long userId, String reason) {
+        MemberLevelLogDO levelLogDO = new MemberLevelLogDO();
+        levelLogDO.setUserId(userId);
+        levelLogDO.setRemark(reason);
+        levelLogDO.setDescription("管理员取消");
+        levelLogMapper.insert(levelLogDO);
+
+        // 给会员发送等级变动消息
+        notifyMember(userId, levelLogDO);
+    }
+
+    @Override
+    public void createAdjustLog(MemberUserDO user, MemberLevelDO level, int experience, String reason) {
+        MemberLevelLogDO levelLogDO = new MemberLevelLogDO();
+        levelLogDO.setUserId(user.getId());
+        levelLogDO.setLevelId(level.getId());
+        levelLogDO.setLevel(level.getLevel());
+        levelLogDO.setDiscount(level.getDiscount());
+        levelLogDO.setUserExperience(level.getExperience());
+        levelLogDO.setExperience(experience);
+        levelLogDO.setRemark(reason);
+        levelLogDO.setDescription("管理员调整为:" + level.getName());
+        levelLogMapper.insert(levelLogDO);
+
+        // 给会员发送等级变动消息
+        notifyMember(user.getId(), levelLogDO);
+    }
+
+    @Override
+    public void createAutoUpgradeLog(MemberUserDO user, MemberLevelDO level) {
+        MemberLevelLogDO levelLogDO = new MemberLevelLogDO();
+        levelLogDO.setUserId(user.getId());
+        levelLogDO.setLevelId(level.getId());
+        levelLogDO.setLevel(level.getLevel());
+        levelLogDO.setDiscount(level.getDiscount());
+        levelLogDO.setExperience(level.getExperience());
+        levelLogDO.setUserExperience(user.getExperience());
+        levelLogDO.setDescription("成为:" + level.getName());
+        levelLogMapper.insert(levelLogDO);
+
+        // 给会员发送等级变动消息
+        notifyMember(user.getId(), levelLogDO);
+    }
+
+    private void notifyMember(Long userId, MemberLevelLogDO level) {
+        //todo: 给会员发消息
+    }
+
 }

+ 33 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelService.java

@@ -1,11 +1,15 @@
 package cn.iocoder.yudao.module.member.service.level;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelPageReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelUpdateReqVO;
 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.enums.MemberExperienceBizTypeEnum;
 
+import javax.annotation.Nullable;
 import javax.validation.Valid;
 import java.util.Collection;
 import java.util.List;
@@ -71,4 +75,33 @@ public interface MemberLevelService {
      * @return 会员等级列表
      */
     List<MemberLevelDO> getLevelListByStatus(Integer status);
+
+
+    /**
+     * 获得开启状态的会员等级列表
+     *
+     * @return 会员等级列表
+     */
+    default List<MemberLevelDO> getEnableLevelList() {
+        return getLevelListByStatus(CommonStatusEnum.ENABLE.getStatus());
+    }
+
+    /**
+     * 修改会员的等级
+     *
+     * @param user        会员
+     * @param levelId     要修改的等级编号,编号为空时,代表取消会员的等级
+     * @param levelReason 修改原因
+     */
+    void updateUserLevel(MemberUserDO user, @Nullable Long levelId, String levelReason);
+
+    /**
+     * 增加会员经验
+     *
+     * @param userId     会员ID
+     * @param experience 经验
+     * @param bizType    业务类型
+     * @param bizId      业务编号
+     */
+    void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId);
 }

+ 123 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java

@@ -1,6 +1,9 @@
 package cn.iocoder.yudao.module.member.service.level;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelCreateReqVO;
@@ -8,14 +11,19 @@ import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelPageR
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelUpdateReqVO;
 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.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
+import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
+import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -33,6 +41,12 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
     @Resource
     private MemberLevelMapper levelMapper;
+    @Resource
+    private MemberLevelLogService memberLevelLogService;
+    @Resource
+    private MemberExperienceLogService memberExperienceLogService;
+    @Resource
+    private MemberUserMapper memberUserMapper;
 
     @Override
     public Long createLevel(MemberLevelCreateReqVO createReqVO) {
@@ -153,4 +167,113 @@ public class MemberLevelServiceImpl implements MemberLevelService {
     public List<MemberLevelDO> getLevelListByStatus(Integer status) {
         return levelMapper.selectListByStatus(status);
     }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void updateUserLevel(MemberUserDO user, Long levelId, String reason) {
+        // 未调整的情况1
+        if (user.getLevelId() == null && levelId == null) {
+            return;
+        }
+        // 未调整的情况2
+        if (ObjUtil.equal(user.getLevelId(), levelId)) {
+            return;
+        }
+
+        // 需要后台用户填写为什么调整会员的等级
+        if (StrUtil.isBlank(reason)) {
+            throw exception(LEVEL_REASON_NOT_EXISTS);
+        }
+
+        int experience;
+        int totalExperience = 0;
+        // 记录等级变动
+        if (levelId == null) {
+            experience = -user.getExperience();
+
+            // 取消了会员的等级
+            memberLevelLogService.createCancelLog(user.getId(), reason);
+            memberUserMapper.cancelUserLevel(user.getId());
+        } else {
+            MemberLevelDO level = validateLevelExists(levelId);
+            // 变动经验值 = 等级的升级经验 - 会员当前的经验;正数为增加经验,负数为扣减经验
+            experience = level.getExperience() - user.getExperience();
+            // 会员当前的经验 = 等级的升级经验
+            totalExperience = level.getExperience();
+
+            memberLevelLogService.createAdjustLog(user, level, experience, reason);
+
+            // 更新会员表上的等级编号、经验值
+            updateUserLevelIdAndExperience(user.getId(), levelId, totalExperience);
+        }
+
+
+        // 记录会员经验变动
+        memberExperienceLogService.createAdjustLog(user.getId(), experience, totalExperience);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void plusExperience(Long userId, Integer experience, MemberExperienceBizTypeEnum bizType, String bizId) {
+        if (experience == 0) {
+            return;
+        }
+
+        MemberUserDO user = memberUserMapper.selectById(userId);
+        if (user.getExperience() == null) {
+            user.setExperience(0);
+        }
+
+        // 防止扣出负数
+        int userExperience = NumberUtil.max(user.getExperience() + experience, 0);
+
+        // 创建经验记录
+        memberExperienceLogService.createBizLog(userId, experience, userExperience, bizType, bizId);
+
+        // 计算会员等级
+        Long levelId = calcLevel(user, userExperience);
+
+        // 更新会员表上的等级编号、经验值
+        updateUserLevelIdAndExperience(user.getId(), levelId, userExperience);
+    }
+
+    private void updateUserLevelIdAndExperience(Long userId, Long levelId, Integer experience) {
+        memberUserMapper.updateById(new MemberUserDO()
+                .setId(userId)
+                .setLevelId(levelId).setExperience(experience)
+        );
+    }
+
+    /**
+     * 计算会员等级
+     *
+     * @param user           会员
+     * @param userExperience 会员当前的经验值
+     * @return 会员等级编号,null表示无变化
+     */
+    private Long calcLevel(MemberUserDO user, int userExperience) {
+        List<MemberLevelDO> list = getEnableLevelList();
+        if (CollUtil.isEmpty(list)) {
+            log.warn("计算会员等级失败:会员等级配置不存在");
+            return null;
+        }
+
+        MemberLevelDO matchLevel = list.stream()
+                .filter(level -> userExperience >= level.getExperience())
+                .max(Comparator.nullsFirst(Comparator.comparing(MemberLevelDO::getLevel)))
+                .orElse(null);
+        if (matchLevel == null) {
+            log.warn("计算会员等级失败:未找到会员{}经验{}对应的等级配置", user.getId(), userExperience);
+            return null;
+        }
+
+        // 等级没有变化
+        if (ObjectUtil.equal(matchLevel.getId(), user.getLevelId())) {
+            return null;
+        }
+
+        // 保存等级变更记录
+        memberLevelLogService.createAutoUpgradeLog(user, matchLevel);
+        return matchLevel.getId();
+    }
 }

+ 10 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -8,13 +8,14 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserPageReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.user.vo.MemberUserUpdateReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserResetPasswordReqVO;
+import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdatePasswordReqVO;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateReqVO;
-import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO;
 import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
 import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
@@ -55,6 +56,9 @@ public class MemberUserServiceImpl implements MemberUserService {
     @Resource
     private PasswordEncoder passwordEncoder;
 
+    @Resource
+    private MemberLevelService memberLevelService;
+
     @Override
     public MemberUserDO getUserByMobile(String mobile) {
         return memberUserMapper.selectByMobile(mobile);
@@ -180,16 +184,20 @@ public class MemberUserServiceImpl implements MemberUserService {
         return passwordEncoder.encode(password);
     }
 
+    @Transactional(rollbackFor = Exception.class)
     @Override
     public void updateUser(MemberUserUpdateReqVO updateReqVO) {
         // 校验存在
-        validateUserExists(updateReqVO.getId());
+        MemberUserDO user = validateUserExists(updateReqVO.getId());
         // 校验手机唯一
         validateMobileUnique(updateReqVO.getId(), updateReqVO.getMobile());
 
         // 更新
         MemberUserDO updateObj = MemberUserConvert.INSTANCE.convert(updateReqVO);
         memberUserMapper.updateById(updateObj);
+
+        // 会员级别修改
+        memberLevelService.updateUserLevel(user, updateReqVO.getLevelId(), updateReqVO.getLevelReason());
     }
 
     @VisibleForTesting