【wiki知识库】09.欢迎页面展示(浏览量统计)SpringBoot部分

🍊 编程有易不绕弯,成长之路不孤单!

大家好,我是熊哈哈,这个项目从我接手到现在有了两个多月的时间了吧,其实本来我在七月初就做完的吧,但是六月份的时候生病了,在家里休息了一个月的时间,后来回到学校考试,考完试之后就出来实习了,这个项目也就一直拖了下去,最近时间够了就在努力的去完成这个项目。

总体来说这个项目不是很难,有很多的东西不够细节,处理的也不是很到位,请大家谅解一下。在我考虑到的不充分的地方一个就是在删除电子书的时候,并没有删除电子书对应存在数据库当中的文章,还有就是在登录的时候,密码加密应该在前端就开始加密了,不应该传到后端在进行加密处理,其实还有很多不到位的地方需要大家去查找,我在提示一下,有没有考虑到为接口添加事物?

此外,项目还可以有改进部分,一个是添加AOP做日志管理,还有自定义异常类,这些都是很重要的模块,在实际的开发当中,一定不要像代码当中的那么随意。其他的我还没有想到,不过我记得我之前说过这个项目的权限校验不太合理,有兴趣的可以了解一下RBAC还有SpringSecurity。

目录

🍊 编程有易不绕弯,成长之路不孤单!

一、今日目标

二、SpringBoot代码修改

2.1 新增IpUtil

2.2 添加访问量统计功能

2.3 新增点赞功能

2.4 实现数据统计

2.4.1 定时更新ebook数据

2.4.2 定时更新ebook_snapshot

2.5 展示总浏览量和30天内的数据变化


一、今日目标

上篇文章链接:【wiki知识库】08.添加用户登录功能--后端SpringBoot部分-CSDN博客

上篇文章做了登录功能的后端逻辑,实现了登录拦截还有权限校验部分,为网站提供了一定的安全性保障,在这篇文章就要实现最后的部分:浏览量的统计和点赞功能。

这一部分就是纯Sql,还有自动化任务。

二、SpringBoot代码修改

2.1 新增IpUtil

这个工具类的作用就是在你访问接口的时候,可以获取到你的真实IP。

public class IpUtil {
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST = "127.0.0.1";
    private static final String SEPARATOR = ",";

    public static String getIpAddr(HttpServletRequest request) {
        System.out.println(request);
        String ipAddress;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (LOCALHOST.equals(ipAddress)) {
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            // "***.***.***.***".length()
            if (ipAddress != null && ipAddress.length() > 15) {
                if (ipAddress.indexOf(SEPARATOR) > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress = "";
        }
        return ipAddress;
    }
}

2.2 添加访问量统计功能

这个功能一说出来你知道要在哪里添加吗?就是我们之前写过的fint-content接口,当我们点开一篇文章查看的时候,对应的我们要为这一篇文章增加访问量。

找打这个接口,然后修改代码。

    /**
     * 查找某个doc的content内容
     * @param id content的id
     * @return
     */
    @GetMapping("/find-content/{id}")
    public CommonResp findContent(@PathVariable Long id) {
        Content content = contentService.getById(id);
        // 查找的同时更新阅读数
        docService.increaseViewCount(id);
        String message = content.getContent();
        return new CommonResp(true,"查找成功",message);
    }

这是在DocServiceImpl中添加的,代码很简单,压力给到mapper。

public void increaseViewCount(Long id) {
        docMapper.increaseViewCount(id);
    }

走带mapper中,就是要执行我们自己写的sql语句,在这之前大家先来考虑一个问题,为了增加浏览量我这里给大家两种方案。

  1. 通过文档的id查询出文档的信息,然后修改文档的浏览量,然后在存回去。
  2. 通过文档的id直接找到对应的数据,然后直接修改浏览量。

不用想也是第二种。但是一定要注意,如果要执行自定义的Sql,一定要把xml文件放到resources目录下的mapper中。

<update id="increaseViewCount">
        update doc set view_count = view_count + 1 where id = #{id}
</update>

2.3 新增点赞功能

这个功能还是在DocController中添加的。

对应的接口代码如下。

/**
     * 给文章点赞
     * @param id 传入的doc的id
     * @return
     */
    @GetMapping("/vote/{id}")
    public CommonResp vote(@PathVariable Long id) {
        docService.increaseVoteCount(id);
        return  new CommonResp(true,"点赞成功",null);
    }

DocServiceImpl中新增代码。这段代码中,获取了HttpServletRequest对象,通过这个对象我们就可以拿到用户访问时的IP,为什么需要用户的IP?

你有没有经历过这种情况,当你访问某一篇博客的时候,或者看一个视频的时候,在你点赞了之后你就不能够在点赞了,或者你在点赞就是取消点赞了,我们这里解析用户IP也是一样的道理,我们要把当前点赞用户的IP存放在Redis当中,当用户点过一次之后,我们就设置24小时内不能够在点赞。为什么一定要IP呢?因为网站用户不需要账号登陆。

现在你知道了IpUtil的作用了吗。

 public void increaseVoteCount(Long id) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 通过IPUtil获取远程访问的IP
        String ip = IpUtil.getIpAddr(request);
        // 判断用户是否已经投票
        if (redisUtil.validateRepeat("DOC_VOTE_" + id + "_" + ip, 60 * 60 * 24)) {
            docMapper.increaseVoteCount(id);
        } else {
            throw new RuntimeException("您今日已经投过票了");
        }
        docMapper.increaseVoteCount(id);
    }

2.4 实现数据统计

想一下,如果这个功能让你来实现,你会从哪里入手?

先来看看我们的数据库的表格吧。我们点开doc、ebook后都能看到有浏览量的统计,但不同的是doc记录的是每一篇文档的浏览量,而ebook记录的是某个电子书的浏览量,至于下边的ebook_snapshot意味着每一日的电子书快照,什么意思呢?就是每一天当中每一本电子书的总浏览量和今日增长的浏览量。

再回想我们的代码,我们只有在代码中添加了有关doc的浏览量统计,那我们改如何同步三个表格呢?

想想看,doc统计的是文档的浏览量,文档是带有ebook的id的,我们只要把某个ebook下的doc做一个统计就好了,这样就可以的到ebook中的浏览量数据了。

那么ebook_snapshot中的数据呢?就像是下边的图所示。

view_count是电子书访问的总浏览量,这个我们在ebook当中就已经有了,至于vote_count和view_count同理。

那么view_increase呢?这个数据记录着今日访问量。

 我给大家标注了一下,现在不难猜了吧,今日的当前总访问量和昨天截止的访问量相减就是今日访问量了。

 好了知道了这些就会让你更容易地理解Sql怎么写。


2.4.1 定时更新ebook数据

这是一个自动化任务,过一段时间ebook中的数据就要和doc中的数据进行同步。

@Component
public class EbookInfoTask {
    private static final Logger LOG = LoggerFactory.getLogger(EbookInfoTask.class);
    @Resource
    private DocService docService;
    @Resource
    private SnowFlake snowFlake;
    /**
     * 每30秒更新电子书信息
     */
    @Scheduled(cron = "5/30 * * * * ?")
    public void cron() {
        // 增加日志流水号
        MDC.put("LOG_ID", String.valueOf(snowFlake.nextId()));
        LOG.info("更新电子书下的文档数据开始");
        long start = System.currentTimeMillis();
        docService.updateEbookInfo();
        LOG.info("更新电子书下的文档数据结束,耗时:{}毫秒", System.currentTimeMillis() - start);
    }
}

service中的代码如下,这个就好理解了。

    <update id="updateEbookInfo">
        update ebook t1, (select ebook_id, count(1) doc_count, sum(view_count) view_count, 
          sum(vote_count) vote_count
        from doc group by ebook_id) t2
        set t1.doc_count = t2.doc_count, t1.view_count = t2.view_count, t1.vote_count = 
          t2.vote_count
        where t1.id = t2.ebook_id
    </update>

2.4.2 定时更新ebook_snapshot

@Component
public class EbookSnapshotTask {

    private static final Logger LOG = LoggerFactory.getLogger(EbookSnapshotTask.class);
    @Resource
    private EbookSnapshotService ebookSnapshotService;
    @Resource
    private SnowFlake snowFlake;
    /**
     * 自定义cron表达式跑批
     * 只有等上一次执行完成,下一次才会在下一个时间点执行,错过就错过
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void doSnapshot() {
        // 增加日志流水号
        MDC.put("LOG_ID", String.valueOf(snowFlake.nextId()));
        LOG.info("生成今日电子书快照开始");
        Long start = System.currentTimeMillis();
        ebookSnapshotService.genSnapshot();
        LOG.info("生成今日电子书快照结束,耗时:{}毫秒", System.currentTimeMillis() - start);
    }
}

下边直接给出大家sql语句。

<update id="genSnapshot">
        INSERT INTO ebook_snapshot(ebook_id, `date`, view_count, vote_count, view_increase, vote_increase)
        SELECT t1.id, CURDATE(), 0, 0, 0, 0
        FROM ebook t1
        WHERE NOT EXISTS (
                SELECT 1
                FROM ebook_snapshot t2
                WHERE t1.id = t2.ebook_id
                  AND t2.`date` = CURDATE()
            );

        UPDATE ebook_snapshot t1
            JOIN ebook t2 ON t1.ebook_id = t2.id
        SET t1.view_count = t2.view_count,
            t1.vote_count = t2.vote_count
        WHERE t1.`date` = CURDATE();

        UPDATE ebook_snapshot t1
            LEFT JOIN (
                SELECT ebook_id, view_count, vote_count
                FROM ebook_snapshot
                WHERE `date` = DATE_SUB(CURDATE(), INTERVAL 1 DAY)
            ) t2 ON t1.ebook_id = t2.ebook_id
        SET t1.view_increase = (t1.view_count - IFNULL(t2.view_count, 0)),
            t1.vote_increase = (t1.vote_count - IFNULL(t2.vote_count, 0))
        WHERE t1.`date` = CURDATE();
    </update>

不过你也别被吓到,这个update中一共有三个sql,现在来一条一条分析。

这是一条插入语句目的是什么呢,先看where语句,这是一个带有条件的插入sql,当我们从ebook_snapshot中查找数据时,如果没有发现日期是今天,并且存在于ebook中的数据时候就会执行,说白了就是看一下在当天有没有往这个表格中插入ebook的统计信息,没有的话就插进去。

INSERT INTO ebook_snapshot(ebook_id, `date`, view_count, vote_count, view_increase, vote_increase)
        SELECT t1.id, CURDATE(), 0, 0, 0, 0
        FROM ebook t1
        WHERE NOT EXISTS (
                SELECT 1
                FROM ebook_snapshot t2
                WHERE t1.id = t2.ebook_id
                  AND t2.`date` = CURDATE()
            );

再来看下一条,这是一条更新语句,更新的是表格当中的电子书总浏览量和总点赞量,注意是当天的数据。

 UPDATE ebook_snapshot t1
            JOIN ebook t2 ON t1.ebook_id = t2.id
        SET t1.view_count = t2.view_count,
            t1.vote_count = t2.vote_count
        WHERE t1.`date` = CURDATE();

来看最后一条,还是更新语句,只不过更新的是当天的每个电子书的当日访问量和点赞量。

两张ebook_snapshot表格做连接,拿到一张表格中今天所有电子书的数据,还有昨天所有电子书的数据,然后更新今天所有电子书的view_increase和vote_increase,数值就是前边说的相减。

 UPDATE ebook_snapshot t1
            LEFT JOIN (
                SELECT ebook_id, view_count, vote_count
                FROM ebook_snapshot
                WHERE `date` = DATE_SUB(CURDATE(), INTERVAL 1 DAY)
            ) t2 ON t1.ebook_id = t2.ebook_id
        SET t1.view_increase = (t1.view_count - IFNULL(t2.view_count, 0)),
            t1.vote_increase = (t1.vote_count - IFNULL(t2.vote_count, 0))
        WHERE t1.`date` = CURDATE();

2.5 展示总浏览量和30天内的数据变化

@RestController
@RequestMapping("/ebook-snapshot")
public class EbookSnapshotController {

    @Resource
    private EbookSnapshotService ebookSnapshotService;

    @GetMapping("/get-statistic")
    public CommonResp getStatistic() {
        List<StatisticVo> statisticResp = ebookSnapshotService.getStatistic();
        return new CommonResp<>(true,"success",statisticResp);
    }

    @GetMapping("/get-30-statistic")
    public CommonResp get30Statistic() {
        List<StatisticVo> statisticResp = ebookSnapshotService.get30Statistic();
        return new CommonResp<>(true,"success",statisticResp);
    }
}

话不多说,我们直接上sql,因为代码里直接一路调到mapper了。

这个是统计总浏览量和总点赞量的,我这里统计多了,其实直接让date等于curdate()就可以了。

<select id="getStatistic" resultType="com.my.hawiki.vo.StatisticVo">
        select
            t1.`date` as `date`,
            sum(t1.view_count) as viewCount,
            sum(t1.vote_count) as voteCount,
            sum(t1.view_increase) as viewIncrease,
            sum(t1.vote_increase) as voteIncrease
        from
            ebook_snapshot t1
        where
            t1.`date` >= date_sub(curdate(), interval 1 day)
        group by
            t1.`date`
        order by
            t1.`date` desc;
    </select>

这个呢就是统计三十日内的数据信息,统计的是每一天的浏览量和点赞数,但是不包括当日。 

 <select id="get30Statistic" resultType="com.my.hawiki.vo.StatisticVo">
        select
            t1.`date` as `date`,
            sum(t1.view_increase) as viewIncrease,
            sum(t1.vote_increase) as voteIncrease
        from
            ebook_snapshot t1
        where
            t1.`date` between date_sub(curdate(), interval 30 day) and date_sub(curdate(), interval 1 day)
        group by
            t1.`date`
        order by
            t1.`date` asc;
    </select>

🍊 🍊 🍊 到了这里也就结束了,如果在代码上有问题或者想要获取源码的同学们可以私信联系我,或者联系易编橙。🍊 🍊 🍊

易编橙·终身成长社群,相遇已是上上签!-CSDN博客

文章底部有链接

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot知识库系统是基于SpringBoot和MyBatis实现的一个系统,它采用了Docker容器化部署。该系统设计了6个不同的角色,可以实现电信知识的存储和管理。[2] 在SpringBoot项目中,可以使用两种不同格式的配置文件来配置系统。一种是Properties文件,另一种是YML文件。默认情况下,Spring Boot会使用application.properties作为系统配置文件,并将其放置在resources目录下。在这个配置文件中,我们可以设置系统的全局配置,例如端口号、数据库连接、日志、启动图案等。除了application.properties,Spring Boot还会读取其他指定位置的配置文件,包括项目根目录下的config目录、classpath下的config目录以及classpath目录。这样,我们可以根据不同的需求和场景来灵活配置系统。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Spring Boot+Vue3前后端分离实战wiki知识库系统之Spring Boot项目搭建](https://blog.csdn.net/Lbsssss/article/details/127600804)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【Springboot项目】电信知识库系统](https://blog.csdn.net/weixin_48180029/article/details/123241071)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值