前言

通过 Python 爬取十万博文之后,最重要的是要让互联网用户访问到,那么如何做呢?

选型

从后台框架、前端模板、数据库连接池、缓存、代理服务、限流等组件多个维度选型。

  • 后台框架 SpringBoot2+、JPA
  • 前端框架 Vue
  • 模块框架 Thymeleaf
  • 数据库连接池 HikariCP
  • 缓存 Redis
  • 限流 Guava
  • 代理服务 Nginx
  • 文章编辑 Markdown

架构

博文

我们可以通过以下方式访问:

  1. https://blog.52itstyle.top/49.html

亦或是:

  1. https://blog.52itstyle.top/49.shtml

当然,如果你愿意你也可以显示为:

  1. https://blog.52itstyle.top/49.php
  2. https://blog.52itstyle.top/49.asp
  3. https://blog.52itstyle.top/49.jsp

只需要在后台配置对应的映射关系即可:

  1. /**
  2. * 博文
  3. */
  4. @RequestMapping("{id}.html")
  5. public String blog(@PathVariable("id") Long id, ModelMap model) {
  6. Blog blog = blogService.getById(id);
  7. model.addAttribute("blog",blog);
  8. return "article";
  9. }

由于数据库存储的是 markedown 格式的数据,前台我们通过 editormd 转为 html 代码显示,这里只展示部分代码:

  1. <script type='text/javascript' src='js/jquery.min.js'></script>
  2. <!--省略部分代码-->
  3. <script type='text/javascript' src="editor/editormd.min.js"></script>
  4. <!--省略部分代码-->
  5. <div id="article">
  6. <textarea th:text="${blog.content}" style="display:none;" placeholder="markdown语言">
  7. </textarea>
  8. </div>
  9. <!--省略部分代码-->
  10. <script>
  11. editormd.markdownToHTML("article", {
  12. htmlDecode : "style,script,iframe",
  13. emoji : true,
  14. taskList : true,
  15. tex : true, // 默认不解析
  16. flowChart : true, // 默认不解析
  17. sequenceDiagram : true // 默认不解析
  18. });
  19. </script>

缓存

爬取的博文一般、基本、大概不会修改,所以我们完全可以缓存起来,避免跟数据库直接交互,顺便提升一下访问速速。正好手头有个 256MB 的阿里云 Redis 服务,拿来就用了。

首相引入以下组件:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-cache</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-data-redis</artifactId>
  8. </dependency>

配置 redis:

  1. spring.redis.database=1
  2. spring.redis.host=r-m5e4873fd882de14.redis.rds.aliyuncs.com
  3. spring.redis.port=6379
  4. spring.redis.password=6347888
  5. spring.redis.pool.max-active=8
  6. spring.redis.pool.max-wait=-1
  7. spring.redis.pool.max-idle=8
  8. spring.redis.pool.min-idle=0
  9. spring.redis.timeout=3000ms
  10. spring.cache.type = redis

接口实现,引入 Cacheable 注解:

  1. @Override
  2. @Cacheable(cacheNames ="blog")
  3. public Blog getById(Long id) {
  4. String nativeSql = "SELECT * FROM blog WHERE id=?";
  5. return dynamicQuery.nativeQuerySingleResult(Blog.class,nativeSql,new Object[]{id});
  6. }

配置完成之后,我们打开数据库配置,多次访问博文地址,如果只是初次打印 SQL 说明配置成功:

  1. spring.jpa.show-sql = true

限流

万一哪天流量暴涨亦或是有人恶意攻击,尔等小服务器根本扛不住,所以有时候我们需要一定的手段进行限流,比如限制IP访问的频率次数。

这里我们使用开源的第三方组件库,引入以下组件:

  1. <dependency>
  2. <groupId>com.google.guava</groupId>
  3. <artifactId>guava</artifactId>
  4. <version>25.1-jre</version>
  5. </dependency>

自定义注解:

  1. /**
  2. * 自定义注解 限流
  3. */
  4. @Target({ElementType.PARAMETER, ElementType.METHOD})
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Documented
  7. public @interface ServiceLimit {
  8. /**
  9. * 描述
  10. */
  11. String description() default "";
  12. /**
  13. * key
  14. */
  15. String key() default "";
  16. /**
  17. * 类型
  18. */
  19. LimitType limitType() default LimitType.CUSTOMER;
  20. enum LimitType {
  21. /**
  22. * 自定义key
  23. */
  24. CUSTOMER,
  25. /**
  26. * 根据请求者IP
  27. */
  28. IP
  29. }
  30. }

限流逻辑:

  1. /**
  2. * 限流 AOP
  3. */
  4. @Aspect
  5. @Configuration
  6. public class LimitAspect {
  7. //根据IP分不同的令牌桶, 每天自动清理缓存
  8. private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder()
  9. .maximumSize(1000)
  10. .expireAfterWrite(1, TimeUnit.DAYS)
  11. .build(new CacheLoader<String, RateLimiter>() {
  12. @Override
  13. public RateLimiter load(String key){
  14. // 新的IP初始化 每秒只发出5个令牌
  15. return RateLimiter.create(5);
  16. }
  17. });
  18. //Service层切点 限流
  19. @Pointcut("@annotation(com.itstyle.blog.common.limit.ServiceLimit)")
  20. public void ServiceAspect() {
  21. }
  22. @Around("ServiceAspect()")
  23. public Object around(ProceedingJoinPoint joinPoint) {
  24. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  25. Method method = signature.getMethod();
  26. ServiceLimit limitAnnotation = method.getAnnotation(ServiceLimit.class);
  27. ServiceLimit.LimitType limitType = limitAnnotation.limitType();
  28. String key = limitAnnotation.key();
  29. Object obj;
  30. try {
  31. if(limitType.equals(ServiceLimit.LimitType.IP)){
  32. key = IPUtils.getIpAddr();
  33. }
  34. RateLimiter rateLimiter = caches.get(key);
  35. Boolean flag = rateLimiter.tryAcquire();
  36. if(flag){
  37. obj = joinPoint.proceed();
  38. }else{
  39. throw new RrException("小同志,你访问的太频繁了");
  40. }
  41. } catch (Throwable e) {
  42. throw new RrException("小同志,你访问的太频繁了");
  43. }
  44. return obj;
  45. }
  46. }

收录

完事具备,就差被搜索引擎收录了,我们可以通过手动生成网站地图,提交给百度。

  1. /**
  2. * 生成地图
  3. * 参见:https://blog.52itstyle.top/sitemap.xml
  4. */
  5. @Component
  6. public class SitemapTask {
  7. @Autowired
  8. private DynamicQuery dynamicQuery;
  9. protected Logger logger = LoggerFactory.getLogger(getClass());
  10. @Value("${blog.url}")
  11. private String blogUrl;
  12. //每天23点执行一次
  13. @Scheduled(cron = "0 0 23 * * ?")
  14. public void createSitemap() {
  15. logger.info("定时提交百度收录开始");
  16. StringBuffer xml = new StringBuffer();
  17. xml.append("<?xml version='1.0' encoding='utf-8'?>\n");
  18. xml.append("<urlset>\n");
  19. String nativeSql = "SELECT id,create_time FROM blog";
  20. List<Object[]> list = dynamicQuery.query(nativeSql,new Object[]{});
  21. list.forEach(blog -> {
  22. String url = blogUrl+blog[0]+".html";
  23. xml.append(" <url>\n");
  24. xml.append(" <loc>"+url+"</loc>\n");
  25. xml.append(" <lastmod>"+blog[1]+"</lastmod>\n");
  26. xml.append(" </url>\n");
  27. });
  28. xml.append("</urlset>\n");
  29. saveAsFileWriter(xml.toString());
  30. logger.info("定时提交百度收录结束");
  31. }
  32. private static void saveAsFileWriter(String content) {
  33. String path = ClassUtils.getDefaultClassLoader().getResource("").getPath();
  34. String filePath = path + "static"+ SystemConstant.SF_FILE_SEPARATOR+"sitemap.xml";
  35. FileWriter fwriter = null;
  36. try {
  37. fwriter = new FileWriter(filePath, false);
  38. fwriter.write(content);
  39. } catch (IOException ex) {
  40. ex.printStackTrace();
  41. } finally {
  42. try {
  43. fwriter.flush();
  44. fwriter.close();
  45. } catch (IOException ex) {
  46. ex.printStackTrace();
  47. }
  48. }
  49. }
  50. }

打包

尽量不要以Jar包形式部署,为了以后方便部署,最好放置到 外置Tomcat 下。

pom.xml 中移除内置 Tomcat:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-tomcat</artifactId>
  4. <scope>provided</scope>
  5. </dependency>

修改启动类:

  1. /**
  2. * 启动类
  3. * 创建者 科帮网
  4. * 创建时间 2019年7月21日
  5. */
  6. @SpringBootApplication
  7. @EnableCaching
  8. @EnableScheduling
  9. public class Application extends SpringBootServletInitializer {
  10. private static final Logger logger = LoggerFactory.getLogger(Application.class);
  11. public static void main(String[] args) {
  12. SpringApplication.run(Application.class, args);
  13. logger.info("项目启动");
  14. }
  15. @Override
  16. protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
  17. return application.sources(Application.class);
  18. }
  19. }

代理

项目部署后,最好加一层代理服务,这里我们使用Nginx:

  1. server {
  2. listen 80;
  3. server_name blog.52itstyle.top;
  4. return 301 https://$server_name$request_uri;
  5. }
  6. server{
  7. listen 443 ssl;
  8. server_name blog.52itstyle.top;
  9. #证书路径
  10. ssl_certificate /usr/local/openresty/nginx/cert/2543486_blog.52itstyle.top.pem;
  11. #私钥路径
  12. ssl_certificate_key /usr/local/openresty/nginx/cert/2543486_blog.52itstyle.top.key;
  13. #缓存有效期
  14. ssl_session_timeout 5m;
  15. #可选的加密算法,顺序很重要,越靠前的优先级越高.
  16. ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
  17. #安全链接可选的加密协议
  18. ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  19. ssl_prefer_server_ciphers on;
  20. location = /500.html {
  21. root /usr/local/openresty/nginx/html;
  22. }
  23. error_page 500 502 503 504 = /503/503.html;
  24. location / {
  25. proxy_pass http://127.0.0.1:8080;
  26. }
  27. location ~ /\.ht {
  28. deny all;
  29. }
  30. }

动静分离,将静态文件交由Nginx处理,加速博客访问:

  1. #静态文件交给nginx处理
  2. location ~ .*\.(js|css|gif|jpg|jpeg|png|bmp)?$
  3. {
  4. root /home/tomcat8/webapps/ROOT/WEB-INF/classes/static;
  5. expires 2h;
  6. }

源码:https://gitee.com/52itstyle/Python

演示:https://blog.52itstyle.top

列表:https://blog.52itstyle.top/index

详情:https://blog.52itstyle.top/49.shtml

小结

撸完整个项目,基本能接触的都用上了,前后端框架、连接池、限流、缓存、动静分离,HTTPS安全认证、百度收录等等,特别适合有一定开发基础的小伙伴!

源码

https://gitee.com/52itstyle/spring-boot-blog

SpringBoot开发案例之打造十万博文Web篇的更多相关文章

  1. SpringBoot开发案例之打造私有云网盘

    前言 最近在做工作流的事情,正好有个需求,要添加一个附件上传的功能,曾找过不少上传插件,都不是特别满意.无意中发现一个很好用的开源web文件管理器插件 elfinder,功能比较完善,社区也很活跃,还 ...

  2. 「玩转Python」打造十万博文爬虫篇

    前言 这里以爬取博客园文章为例,仅供学习参考,某些AD满天飞的网站太浪费爬虫的感情了. 爬取 使用 BeautifulSoup 获取博文 通过 html2text 将 Html 转 Markdown ...

  3. SpringBoot开发案例从0到1构建分布式秒杀系统

    前言 ​最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场 ...

  4. SpringBoot开发案例之多任务并行+线程池处理

    前言 前几篇文章着重介绍了后端服务数据库和多线程并行处理优化,并示例了改造前后的伪代码逻辑.当然了,优化是无止境的,前人栽树后人乘凉.作为我们开发者来说,既然站在了巨人的肩膀上,就要写出更加优化的程序 ...

  5. SpringBoot开发案例之整合Activiti工作流引擎

    前言 JBPM是目前市场上主流开源工作引擎之一,在创建者Tom Baeyens离开JBoss后,JBPM的下一个版本jBPM5完全放弃了jBPM4的基础代码,基于Drools Flow重头来过,目前官 ...

  6. SpringBoot开发案例之整合Dubbo分布式服务

    前言 在 SpringBoot 很火热的时候,阿里巴巴的分布式框架 Dubbo 不知是处于什么考虑,在停更N年之后终于进行维护了.在之前的微服务中,使用的是当当维护的版本 Dubbox,整合方式也是使 ...

  7. SpringBoot开发案例之整合Kafka实现消息队列

    前言 最近在做一款秒杀的案例,涉及到了同步锁.数据库锁.分布式锁.进程内队列以及分布式消息队列,这里对SpringBoot集成Kafka实现消息队列做一个简单的记录. Kafka简介 Kafka是由A ...

  8. 转载-SpringBoot开发案例之整合日志管理

    转载:https://cloud.tencent.com/developer/article/1097579 有一种力量无人能抵挡,它永不言败生来倔强.有一种理想照亮了迷茫,在那写满荣耀的地方. 00 ...

  9. SpringBoot开发案例之分布式集群共享Session

    前言 在分布式系统中,为了提升系统性能,通常会对单体项目进行拆分,分解成多个基于功能的微服务,如果有条件,可能还会对单个微服务进行水平扩展,保证服务高可用. 那么问题来了,如果使用传统管理 Sessi ...

随机推荐

  1. 机器学习之使用Python完成逻辑回归

    一.任务基础 我们将建立一个逻辑回归模型来预测一个学生是否被大学录取.假设你是一个大学系的管理员,你想根据两次考试的结果来决定每个申请人的录取机会.你有以前的申请人的历史数据,你可以用它作为逻辑回归的 ...

  2. Codeforces 730B:Minimum and Maximum(交互式问题)

    http://codeforces.com/problemset/problem/730/B 题意:一个交互式问题,给出一个n代表有n个数字,你可以问下标为x和y的数的大小,会给出"> ...

  3. django基础知识之POST属性:

    POST属性 QueryDict类型的对象 包含post请求方式的所有参数 与form表单中的控件对应 问:表单中哪些控件会被提交? 答:控件要有name属性,则name属性的值为键,value属性的 ...

  4. mybatis的example类

    1. 场景描述 idea下使用mybatis_generator自动生成mapper文件,默认生成了一大堆的example文件及方法,使用规则类似于Hibernate,给了一大堆参数,感觉没必要,只所 ...

  5. MMM 数位dp学习记

    数位dp学习记 by scmmm 开始日期 2019/7/17 前言 状压dp感觉很好理解(本质接近于爆搜但是又有广搜的感觉),综合了dp的高效性(至少比dfs,bfs优),又能解决普通dp难搞定的问 ...

  6. WinForm控件之【ListBox】

    基本介绍 列表控件,将一个或多个数据项列表展示供选择处理. 常设置属性 DataSource:绑定加载项的数据源,设置属性DisplayMember绑定需要显示字段名: ColumnWidth:当属性 ...

  7. py+selenium运行时报错Can not connect to the Service IEDriverServer.exe

    问题: 运行用例时,出现报错(host文件已加入127.0.0.1 localhost): raise WebDriverException("Can not connect to the ...

  8. 【带着canvas去流浪(12)】用Three.js制作简易的MARVEL片头动画(上)

    目录 一. 大作业说明 二.基本思路 三.视频纹理表面修复--UV映射 3.1 问题描述 3.2 纹理贴图的基本原理-UV映射 3.3 关键示例代码 四.小结 示例代码托管在:http://www.g ...

  9. 洛谷P2472 [SCOI2007]蜥蜴 题解

    题目链接: https://www.luogu.org/problemnew/show/P2472 分析: 这道题用最大流解决. 首先构建模型. 一根柱子可以跳入和跳出,于是拆成两个点:入点和出点. ...

  10. python,看看有没有你需要的列表元祖和range知识!

    列表--list 列表:列表是python的基础数据类型之一,存储多种数据类型 可变 支持索引 可切片 方便取值 li = ['alex',123,Ture,(1,2,3,'wusir'),[1,2, ...