springboot项目浏览次数增加实现方案

     1.背景说明
     2.实现方案分析
     3.处理过程

1.背景说明

    在现实开发工作中,一定会遇到过类似于浏览量统计的需求,关于实现方案有很多,下面结合业务实际场景说下实现方案以及处理过程,希望对有同样需求的同学可以作为参考、有所帮助。
    简单交代一下业务场景:
    现在有自研的招聘平台,招聘端发布岗位之后求职端可以实时查看,现在需要统计每个岗位的浏览次数,只要进入到岗位详情页就算一次有效的浏览。

2.实现方案分析

    这类需求最简单的实现方式就是进入到页面之后,调用岗位查询接口,接口中除了返回岗位详情信息之外,再进行浏览次数加一的入表逻辑即可。实现起来是简单了但是考虑一下这种方式有什么弊端?由于岗位详情查看这个动作触发的次数会很多,频繁的调用接口务必会给服务器带来很大的压力,另外浏览量增加这种数据对于主要业务岗位详情查询来讲并不是必要的,架构上可以考虑进行业务分离,降低耦合度之后后期维护也会方便。不进行过多铺垫了,直接说自己的实现方案:
    aop进行业务分离,降低耦合
    redis代替数据库频繁写入缓解服务器压力
    定期缓存处理,批量写入,保证数据同步;

3.处理过程

    下面按照实现思路进行代码实现:
    aop进行业务分离,降低耦合;
自定义切面BrowCountAspect

@Component
@Aspect
@Slf4j
public class BrowCountAspect {

    @Autowired
    private RedisTemplate redisTemplate;

  

    // add by txm 2022/12/17 仅对findPostDetailInfoVo方法进行拦截
    @Pointcut("execution(* com.XXX.XXX.XXX.XXX.controller.WantedJobPostController.findPostDetailInfoVo(..))")
    public void browseTimePoint() {
    }
	
	/**
     * @Author: txm
     * @Description: 接口响应成功之后触发
     * @Param: [responsePostDetailInfoVo]
     * @return: void
     * @Date:  2022/12/17 11:13
     **/
    @AfterReturning(returning = "result",pointcut = "browseTimePoint()")
    public void doAfterRuturn(JoinPoint joinPoint,Object result) {

        ResultVo<PostDetailInfoVo> responsePostDetailInfoVo=(ResultVo<PostDetailInfoVo>) result;
        if(ObjectUtil.isNotNull(responsePostDetailInfoVo) && ObjectUtil.isNotNull(responsePostDetailInfoVo.getData())){
            handleBrowseCount(responsePostDetailInfoVo.getData().getPostId(),responsePostDetailInfoVo.getData().getBrowseCount());

        }

    }

    /**
     * @Author: txm
     * @Description: 浏览次数加一
     * @Param: [responsePostDetailInfoVo]
     * @return: void
     * @Date:  2022/12/17 11:13
     **/
    private synchronized void handleBrowseCount(Long postId,Integer browseCount) {
        // 设置岗位浏览数为数据库中实际的浏览次数,setIfAbsent方法作用:添加过一次之后不会重新添加,每天第一次浏览岗位会记录岗位信息以及浏览次数,在此浏览只会浏览次数加一处理.定时任务会在每天凌晨进行获取当天每个岗位的浏览次数然后批量写入数据库,最后清除缓存中岗位浏览次数
      redisTemplate.opsForValue().setIfAbsent(String.format("jobBrowseCount:post_%s",postId),browseCount));
        redisTemplate.opsForValue().increment(String.format("jobBrowseCount:post_%s",postId));
    }

}

说明
    业务场景只有对岗位详情查看是才实现浏览次数加一,所以此处切入点表达式直接限制到具体的方法即可.

 @Pointcut("execution(* com.XXX.XXX.XXX.XXX.controller.WantedJobPostController.findPostDetailInfoVo(..))")
    public void browseTimePoint() {
    }

    aop实现浏览次数加一处理,切面处理逻辑放到方法执行之后.实现逻辑是每次需要设置岗位id以及对应的浏览次数,如果岗位信息已经添加过则直接进行浏览次数加一处理.其中result对象为接口响应返回参数.需要强转成岗位信息获取岗位信息中的浏览次数.
    相关的实体类PostDetailInfoVo以及接口固定的响应参数ResultVo:
PostDetailInfoVo.java:

/**
 * @ClassName: PostDetailInfoVo
 * @Desc:岗位详情信息
 * @Author: txm
 * @Date: 2022/6/21 16:23
 **/
@ApiModel("岗位详情信息")
@Data
@ToString
public class PostDetailInfoVo implements Serializable {
    private static final long serialVersionUID = -3411333643335244983L;

    @ApiModelProperty(value = "求职岗位id",example = "1",dataType = "Long")
    private Long postId;

    // add by txm 2022/12/17 添加岗位浏览次数
    @ApiModelProperty(value = "岗位浏览次数",dataType = "Integer" ,example = "2")
    private Integer browseCount=0;

// 省略其余岗位属性信息


}

ResultVo.java:

@Data
@Builder
@Accessors(chain = true)
@ApiModel("响应参数")
public class ResultVo<T> implements Serializable {

    private static final long serialVersionUID = -8054007511410819665L;

    @ApiModelProperty(value = "响应状态码",example = "1",dataType = "Integer")
    private int code;

    // 是否成功标识.true表示成功,false表示失败
    @ApiModelProperty("success标识,true表示成功,false表示失败")
    private boolean success;

    // 操作成功时需要响应给客户端的响应数据
    @ApiModelProperty("响应信息")
    private String msg;

    // 返回给客户端的具体数据内容,NON_NULL:如果为null就不返回data,NON_EMPTY:如果为空字符串就不返回data
    @ApiModelProperty("响应数据")
//    @JsonInclude(JsonInclude.Include.NON_NULL)
    //@JsonInclude(JsonInclude.Include.NON_EMPTY)
    private T data;

    @ApiModelProperty("当前时间")
    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date time;

}

    后台每天会读取前一天的岗位浏览次数,并更新到数据库,并将前一天的缓存数据清除.

@Slf4j
@Component
public class ScheduleHandle {

    @Autowired
    private RedisTemplate redisTemplate;


    @Scheduled(cron = "0 5 0 * * *")  // 每天凌晨5分
    public void handle(){

        handleBrowseCount();

    }

    /**
     * @Author: txm
     * @Description: 批量更新岗位浏览次数
     * @Param: []
     * @return: void
     * @Date:  2022/12/17 15:38
     **/
    public void handleBrowseCount() {
        Set<String> browseCountPostSet = redisTemplate.keys("jobBrowseCount:post_*");
        ArrayList<BrowseCountDto> browseCountDtos = new ArrayList<>();
        BrowseCountDto browseCountDto=null;
        if(CollectionUtil.isNotEmpty(browseCountPostSet)){
            for (String postIdInfo : browseCountPostSet) {
                Long postId =Long.valueOf(postIdInfo.substring(20));
                Integer browseCount = (Integer)redisTemplate.opsForValue().get(String.format("jobBrowseCount:post_%s", postId));
                browseCountDto=new BrowseCountDto(browseCount,postId);
                browseCountDtos.add(browseCountDto);
            }

            // 批量更新岗位浏览量,此处入表逻辑不进行展开
           
            // 删除缓存的浏览次数信息
            redisTemplate.delete(browseCountPostSet);
        }

    }

}

涉及的实体类:

@ApiModel("浏览次数")
@Data
@AllArgsConstructor
public class BrowseCountDto implements Serializable {
    private static final long serialVersionUID = -5948133205817407204L;

    @ApiModelProperty(value = "浏览次数",example = "1",dataType = "Integer")
    private Integer browseCount;

    @ApiModelProperty(value = "岗位id",example = "1",dataType = "Long")
    private Long postId;
}

    至此,以实际项目为例介绍了浏览次数增加的完整实现方案,看到这里如果感觉有帮助欢迎点赞或是评论区留言!

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,针对您的需求,我可以给出以下的设计方案: 1. 文件结构 使用随机存取文件,每个记录包含三个字段: - 商品编号:唯一标识一件商品的记录号; - 商品名称:商品的名称; - 商品信息:商品的价格和库存量,可以用一个结构体来表示。 2. 文件操作 对于文件的操作,可以定义以下几个函数: - `void add_item(int id, const char* name, float price, int stock)`:添加一件商品,参数包括商品编号、商品名称、商品价格和库存量; - `void remove_item(int id)`:删除一件商品,参数是商品编号; - `void update_item(int id, const char* name, float price, int stock)`:更新一件商品的信息,参数包括商品编号、商品名称、商品价格和库存量; - `item_info* query_item(int id)`:查询一件商品的信息,返回指向商品信息结构体的指针。 其中,`item_info` 是一个结构体,包含商品的价格和库存量,定义如下: ```c++ struct item_info { float price; int stock; }; ``` 3. 实现细节 为了实现以上函数,可以使用以下的方式: - 添加一件商品:先检查文件中是否已经存在该商品编号,如果存在,则返回错误信息;否则,在文件末尾添加一条新记录。 - 删除一件商品:先检查文件中是否存在该商品编号,如果不存在,则返回错误信息;否则,将该记录标记为已删除。 - 更新一件商品的信息:先检查文件中是否存在该商品编号,如果不存在,则返回错误信息;否则,更新该记录的商品名称、价格和库存量。 - 查询一件商品的信息:先检查文件中是否存在该商品编号,如果不存在,则返回错误信息;否则,根据该记录的偏移量读取商品信息。 4. 总结 以上是一个基本的设计方案,可以根据实际情况进行调整和优化。需要注意的是,随机存取文件的操作需要考虑并发访问和数据一致性等问题,这些细节需要仔细考虑和处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卖柴火的小伙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值