作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/6f25/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
由于版权原因,请阅读原文 --> 使用Spring Boot实现博客统计服务

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/6f25/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/6f25/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
作为一个后端开发,在微服务,server mesh等概念满天飞的时代,持续学习能力是不能丢的,因此楼主最近也研究好多RPC,NETTY,Spring Boot等技术。此外,楼主博客的阅读统计功能是用的是与HEXO相匹配的第三方的数量统计功能,也就诞生了楼主这次更换成自己开发的基础功能的装逼之旅。
通过SPRING INITIALIZR生成工程

如上图,通过Spring官方的 Spring Initial 网站生成项目,项目的目录结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| - src -main -java -package #主函数,启动类,运行它如果运行了 Tomcat、Jetty、Undertow 等容器 -SpringbootApplication -resouces #存放静态资源 js/css/images 等 - statics #存放 html 模板文件 - templates #主要的配置文件,SpringBoot启动时候会自动加载application.yml/application.properties - application.yml #测试文件存放目录 -test # pom.xml 文件是Maven构建的基础,里面包含了我们所依赖JAR和Plugin的信息 - pom
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.*</groupId> <artifactId>*-*</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging>
<name>base-service</name> <description>Demo project for Spring Boot</description>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> </parent>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.6</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.almende.eve</groupId> <artifactId>eve-bundle-full</artifactId> <version>3.1.1</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin>
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
|
实现redis存储逻辑
选择redis而没选择数据库的原因是redis提供了丰富的数据结构与数据持久化策略,另外redis是基于内存的,相对于数据库来说,快了不止一个数量级。而统计阅读次数的场景对接口处理的速度还是有一定的要求的,因此楼主选择了redis作为阅读次数统计的db。
下面就是redis操作的基础代码,比较简单楼主贴一下代码,不做进一步的阐述
redis的接口类
1 2 3 4 5
| public interface RedisService { public boolean set(final String key, final String value); public String get(final String key); public String incr(final String key); }
|
redis的实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Service public class RedisServiceImpl implements RedisService {
@Resource private RedisTemplate<String, ?> redisTemplate;
@Override public boolean set(final String key, final String value) { boolean result = redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { RedisSerializer<String> serializer = redisTemplate.getStringSerializer(); connection.set(serializer.serialize(key), serializer.serialize(value)); return true; } }); return result; }
@Override public String incr(final String key) { String incr = redisTemplate.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection redisConnection) throws DataAccessException { RedisSerializer<String> serializer = redisTemplate.getStringSerializer(); Long value = redisConnection.incr(serializer.serialize(key)); return String.valueOf(value); } }); return incr; }
@Override public String get(final String key){ String result = redisTemplate.execute(new RedisCallback<String>() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { RedisSerializer<String> serializer = redisTemplate.getStringSerializer(); byte[] value = connection.get(serializer.serialize(key)); return serializer.deserialize(value); } }); return result; }
}
|
博客阅读次数统计接口实现
博客阅读次数统计的基本业务逻辑就是,对应每篇博客的blogId作为redis的key,而访问次数就是这个key所对应的value,每访问一次该接口就要将对应的blogId自增一次,并返回对应的value。这里楼主选择的redis的数据结构是redis的Stirng,下面是楼主实现该逻辑的主要代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
@RestController @RequestMapping("/") public class BlogReadCountController {
private static String ALLOW_REQUEST_URL = "******"; private static String ILLEGAL_CHARACTERS = "*"; private static String DEFAULT_READ_COUNT = "1"; private static Logger logger = LoggerFactory.getLogger(BlogReadCountController.class);
@Autowired private RedisService redisService;
@ResponseBody @RequestMapping("/*_*") public ResultCode blogReadCountIncr(HttpServletRequest request,String blogId) { ResultCode resultCode = new ResultCode(); try { logger.info(">>>>>> method blogReadCountIncr exec , request params is : {}",blogId); String readCount = redisService.get(blogId); if (StringUtils.isBlank(readCount)) { if (!blogId.startsWith(ALLOW_REQUEST_URL)||blogId.contains(ILLEGAL_CHARACTERS)) { resultCode.setCode(Messages.API_ERROR_CODE); resultCode.setMsg(Messages.API_ERROR_MSG); return resultCode; }
redisService.set(blogId,DEFAULT_READ_COUNT); readCount = DEFAULT_READ_COUNT; } else { readCount = redisService.incr(blogId); } logger.info(">>>>>> readCount is : {}",readCount); resultCode.setCode(Messages.SUCCESS_CODE); resultCode.setMsg(Messages.SUCCESS_MSG); resultCode.setData(readCount); return resultCode; } catch (Exception e) { e.printStackTrace(); resultCode.setCode(Messages.API_ERROR_CODE); resultCode.setMsg(Messages.API_ERROR_MSG); return resultCode; } } }
|
实现过程中遇到的坑
Spring Boot应用默认的应用访问的path还是 “/“,楼主在这里吃了点苦头,使用https://项目名:port访问服务,愣是访问不通,在配置文件中设置如下所示
1
| server.servlet.context-path=/项目名
|
小结
目前很多大佬都写过关于 SpringBoot 的教程了,如有雷同,请略过不看,本文通过自己的亲身实战以及楼主自己踩到的坑完成的,另外本文是基于最新的 spring-boot-starter-parent:2.0.3.RELEASE编写。
号外
楼主造了一个轮子,LIGHTCONF 是一个基于Netty实现的一个配置管理平台,其核心设计目标是“为业务提供统一的配置管理服务”,可以做到开箱即用。感兴趣的给个star支持一下。

作 者:haifeiWu
原文链接:https://www.hchstudio.cn/article/2018/6f25/
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。