微服务实战项目 —— 知识分享应用(三)

微服务实战项目 —— 知识分享应用(一)
微服务实战项目 —— 知识分享应用(二)

投稿功能

内容中心 domain/dto 新建 ShareSubmitDTO 类,用来封装投稿数据对象

@Data
public class ShareSubmitDTO {
    private Long userId;
    private String author;
    /**
     * 标题
     */
    private String title;
    /**
     * 是否原创 true:是
     */
    private Boolean isOriginal;
    /**
     * 封面
     */
    private String cover;
    /**
     * 概要信息
     */
    private String summary;
    /**
     * 价格(需要的积分)
     */
    private Integer price;
    /**
     * 下载地址
     */
    private String downloadUrl;
}

ShareService 中新增投稿方法,入参为 ShareSubmitDTO 对象,返回受影响的记录条数(1 或 0)

    /**
     * 投稿
     *
     * @param shareSubmitDTO dto
     * @return int
     */    
	public int contribute(ShareSubmitDTO shareSubmitDTO) {
        Share share = Share.builder()
                .id(SnowUtil.getSnowflakeNextId())
                .userId(shareSubmitDTO.getUserId())
                .title(shareSubmitDTO.getTitle())
                .createTime(new Date())
                .updateTime(new Date())
                .isOriginal(shareSubmitDTO.getIsOriginal())
                .author(shareSubmitDTO.getAuthor())
                .cover(shareSubmitDTO.getCover())
                .summary(shareSubmitDTO.getSummary())
                .price(shareSubmitDTO.getPrice())
                .downloadUrl(shareSubmitDTO.getDownloadUrl())
                .buyCount(0)
                .showFlag(false)
                .auditStatus("NOT_YET")
                .reason("暂未审核")
                .build();
        return shareMapper.insert(share);
    }

ShareController 新增投稿接口

    @PostMapping("contribute")
    public CommonResp<Integer> contribute(@RequestBody ShareSubmitDTO shareSubmitDTO,
                                          @RequestHeader(value = "token") String token) {
        shareSubmitDTO.setUserId(getUserIdFromToken(token));
        return CommonResp.success(shareService.contribute(shareSubmitDTO));
    }

提交:投稿功能

我的投稿列表

ShareService 新增查询“我的投稿”方法(带分页)

/**
 * 分页查询我的投稿
 *
 * @param userId   userId
 * @param pageSize pageSize
 * @param pageNo   pageNo
 * @return {@link List}<{@link Share}>
 */
public List<Share> myContributeList(Long userId, Integer pageSize, Integer pageNo) {
    LambdaQueryWrapper<Share> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(Share::getUserId, userId);
    Page<Share> page = Page.of(pageNo, pageSize);
    return shareMapper.selectList(page, wrapper);
}

ShareController 新增查询我的投稿接口

@GetMapping("contributeList")
public CommonResp<List<Share>> getContributeList(
        @RequestParam(required = false, defaultValue = "1") Integer pageNo,
        @RequestParam(required = false, defaultValue = "5") Integer pageSize,
        @RequestHeader(value = "token") String token
) {
    if (pageSize > MAX) {
        pageSize = MAX;
    }
    return CommonResp.success(shareService.myContributeList(getUserIdFromToken(token), pageSize, pageNo));
}

提交:我的投稿功能

管理员待审核分享列表

ShareService 新增查询待审核状态分享列表方法

/**
 * 分页查询待审核列表
 *
 * @param pageSize pageSize
 * @param pageNo   pageNo
 * @return {@link List}<{@link Share}>
 */
public List<Share> notPassShareList(Integer pageSize, Integer pageNo) {
    LambdaQueryWrapper<Share> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(Share::getAuditStatus, AuditStatusEnum.NOT_YET).eq(Share::getShowFlag, false);
    Page<Share> page = Page.of(pageNo, pageSize);
    return shareMapper.selectList(page, wrapper);
}

新建 ShareAdminController 类,用来放管理员相关接口,编写 /share/admin/list 接口,用来返回待管理员审核的资源列表:

@RestController
@RequestMapping("share/admin")
public class ShareAdminController {
    @Resource
    private ShareService shareService;

    @GetMapping("list")
    public CommonResp<List<Share>> getSharesNotYet(@RequestParam(defaultValue = "1", required = false) Integer pageNo,
                                                   @RequestParam(defaultValue = "3", required = false) Integer pageSize,
                                                   @RequestHeader(value = "token") String token) {
        String role = JwtUtil.getJSONObject(token).getStr("roles");
        if (!"admin".equals(role)) {
            return CommonResp.error("无权限");
        }
        return CommonResp.success(shareService.notPassShareList(pageSize, pageNo));
    }

}

提交:查询管理员待审核分享列表

管理员审核分享功能

安装 RocktMQ

参考博客:使用docker安装RocketMQ

注意:修改博客中两处主机地址,并且打开四个端口:9999,10909,10911,9876

在这里插入图片描述

后端实现

RocketMQ 消息队列依赖,父项目管理版本,然后在公共模块引入

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.3</version>
</dependency>

用户中心和内容中心都编写一下 RocketMQ 的配置

主要配置了消息队列的 broker 服务器地址和端口,以及生产者的分组

rocketmq:
  name-server: IP:PORT
  producer:
  	# 必须指定 group
  	group: test-group

准备结束,开始编写内容中心业务层的管理员审核分享功能

生产者-内容中心

新建审核状态枚举类 AuditStatusEnum :

@Getter
@AllArgsConstructor
public enum AuditStatusEnum {
    PASS,
    NOT_YET,
    REJECTED
}

然后在 domain/dto 新建一个审核相关的 DTO 类:ShareAuditDTO,封装所示属性:

@Data
public class ShareAuditDTO {
    private AuditStatusEnum auditStatusEnum;
    private String reason;
    private Boolean showFlag;
}

ShareService 中新增审核方法

/**
 * 审核
 * 
 * @param shareId id
 * @param shareAuditDTO dto
 * @return {@link Share}
 */
@Transactional(rollbackFor = Exception.class)
public Share auditById(Long shareId, ShareAuditDTO shareAuditDTO) {
    // 查看share是否存在
    Share share = shareMapper.selectById(shareId);
    if (share == null) {
        throw new IllegalArgumentException("非法参数,资源不存在");
    }
    if (!Objects.equals("NOT_YET", share.getAuditStatus())) {
        throw new IllegalArgumentException("非法参数,该分享已被审核");
    }
    // 审核资源
    share.setAuditStatus(shareAuditDTO.getAuditStatusEnum().toString());
    share.setReason(shareAuditDTO.getReason());
    share.setShowFlag(shareAuditDTO.getShowFlag());
    shareMapper.updateById(share);
    // 向 关联表 插入数据
    midUserShareMapper.insert(MidUserShare.builder()
            .id(SnowUtil.getSnowflakeNextId())
            .userId(share.getUserId())
            .shareId(shareId)
            .build());
    // 如果是 PASS 那么发送消息给 mq,让用户中心去消费,并添加积分
    if (AuditStatusEnum.PASS.equals(shareAuditDTO.getAuditStatusEnum())) {
        rocketMQTemplate.convertAndSend("add-bonus", UpdateBonusMqDTO.builder()
                .userId(share.getUserId())
                .bonus(50)
                .build());
    }
    return share;
}

其中需要一个 DTO 支持,用于和消息队列传输数据:

@Data
@Builder
public class UpdateBonusMqDTO {
    private Integer bonus;
    private Long userId;
}

启动失败,报错

在这里插入图片描述

解决方案:

在内容中心模块下 resources 目录下创建:META_INF / spring / org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,并写入内容:org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration,观察文件图标变化,成功启动。参考博客:Spring boot 3.0整合RocketMQ及不兼容的问题

在这里插入图片描述

进行http测试

在这里插入图片描述

测试通过,查看RocketMQ控制台,观察到一条消息

在这里插入图片描述

生产者的代码实现成功,接下来开始写消费者的代码

消费者-用户中心

用户中心不需要主动调用,写一个监听器,只要消息队列有了某个用户的积分,就会通知他去消费加分

用户中心新建 rocketmq 子包,新建 AddBonusListener 监听类,如下

@Service
@RocketMQMessageListener(consumerGroup = "consumer", topic = "add-bonus")
public class AddBonusListener implements RocketMQListener<UpdateBonusMqDTO> {

    @Resource
    private UserMapper userMapper;
    @Resource
    private BonusEventLogMapper bonusEventLogMapper;

    @Override
    public void onMessage(UpdateBonusMqDTO updateBonusMqDTO) {
        // 1. 给用户加积分
        Long userId = updateBonusMqDTO.getUserId();
        User user = userMapper.selectById(userId);
        user.setBonus(user.getBonus() + updateBonusMqDTO.getBonus());
        userMapper.updateById(user);

        // 2. 写入日志
        bonusEventLogMapper.insert(BonusEventLog.builder()
                .id(SnowUtil.getSnowflakeNextId())
                .userId(updateBonusMqDTO.getUserId())
                .value(updateBonusMqDTO.getBonus())
                .event("CONTRIBUTE")
                .createTime(new Date())
                .description("投稿加积分")
                .build());
    }
}

别忘了在resources目录下添加配置以及配置文件

重启服务,可以看到用户中心已经监听到,并且已经增加了积分,日志表也插入了日志

在这里插入图片描述

提交:管理员审核实现

我的兑换

我的兑换列表需要查询到 MidUserShare 中间表的数据后再查询 share 的具体信息,ShareService 创建方法

    /**
     * 我的兑换
     *
     * @param userId id
     * @param pageSize size
     * @param pageNo no
     * @return {@link List}<{@link Share}>
     */
    public List<Share> getMyExchange(Long userId, Integer pageSize, Integer pageNo) {
        LambdaQueryWrapper<MidUserShare> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MidUserShare::getUserId, userId);
        // 中间表数据,收集分享id
        List<Long> idList = midUserShareMapper.selectList(Page.of(pageNo, pageSize), wrapper).stream().map(MidUserShare::getShareId).toList();
        // 根据id批量获取share数据
        return shareMapper.selectBatchIds(idList);
    }

ShareController 实现我的兑换接口实现

@GetMapping("myExchangeList")
public CommonResp<List<Share>> getExchangeList(
        @RequestParam(required = false, defaultValue = "1") Integer pageNo,
        @RequestParam(required = false, defaultValue = "5") Integer pageSize,
        @RequestHeader(value = "token") String token
) {
    if (pageSize > MAX) {
        pageSize = MAX;
    }
    return CommonResp.success(shareService.getMyExchange(getUserIdFromToken(token), pageSize, pageNo));
}

提交:我的兑换接口

积分明细

积分明细列表只需要简单的分页查询积分日志表,UserService 实现 userBonusLog 方法

/**
 * 积分明细
 *
 * @param userId id
 * @param pageSize size
 * @param pageNo no
 * @return {@link List}<{@link BonusEventLog}>
 */
public List<BonusEventLog> userBonusLog(Long userId, Integer pageSize, Integer pageNo) {
    Page<BonusEventLog> page = Page.of(pageNo, pageSize);
    LambdaQueryWrapper<BonusEventLog> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(BonusEventLog::getUserId, userId);
    return bonusEventLogMapper.selectList(page, wrapper);
}

UserController 调用 userBonusLog 方法实现积分明细接口

    @GetMapping("bonus")
    public CommonResp<List<BonusEventLog>> getUserBonusLog(@RequestParam(defaultValue = "1", required = false) Integer pageNo,
                                                           @RequestParam(defaultValue = "10", required = false) Integer pageSize,
                                                           @RequestHeader(value = "token") String token) {
        return CommonResp.success(userService.userBonusLog(JwtUtil.getJSONObject(token).getLong("id"), pageSize, pageNo));
    }

由于使用的分页查询,记得在 config 包中添加 MybatisPlus 的分页器配置

提交:积分明细列表

签到

签到功能的实现关键在于判断当前用户今日是否登录过,UserService 实现 dailyCheck 方法

/**
     * 签到
     *
     * @param userId id
     */
    @Transactional(rollbackFor = Exception.class)
    public void dailyCheck(Long userId) {
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw new IllegalArgumentException("用户异常");
        }
        LambdaQueryWrapper<BonusEventLog> wrapper = new LambdaQueryWrapper<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        // 构造开始时间
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        Date start = calendar.getTime();
        // 构造结束时间
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        Date end = calendar.getTime();
        // 构造查询条件,用户今日是否有签到的积分日志记录
        wrapper.eq(BonusEventLog::getUserId, userId).eq(BonusEventLog::getEvent, "DAILY_CHECK").between(BonusEventLog::getCreateTime, start, end);
        // 查询今日是否签到
        BonusEventLog bonusEventLog = bonusEventLogMapper.selectOne(wrapper);
        if (bonusEventLog != null) {
            throw new BusinessException(BusinessExceptionEnum.ALREADY_HAS_CHECK);
        }
        // 签到成功,插入数据
        bonusEventLogMapper.insert(BonusEventLog.builder()
                .id(SnowUtil.getSnowflakeNextId())
                .userId(userId)
                .value(10)
                .event("DAILY_CHECK")
                .description("签到")
                .createTime(new Date())
                .build());

        // 添加积分
        user.setBonus(user.getBonus() + 10);
        userMapper.updateById(user);
    }

UserController 简单调用实现签到接口

	@PostMapping("check")
    public CommonResp<Object> dailyCheck(@RequestHeader(value = "token") String token) {
        userService.dailyCheck(JwtUtil.getJSONObject(token).getLong("id"));
        return CommonResp.success();
    }

提交:签到

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值