热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

(eblog)9、博客搜索引擎开发、后台精选

小Hub领读:继续我们的eblog,今天来完成博客的搜索引擎,数据同步,后台精选哈!项目名称:e

小Hub领读:

继续我们的eblog,今天来完成博客的搜索引擎,数据同步,后台精选哈!


项目名称:eblog

项目 Git 仓库:https://github.com/MarkerHub/eblog(给个 star 支持哈)

项目演示地址:https://markerhub.com:8082

前几篇项目讲解文章:

1、(eblog)Github 上最值得学习的 Springboot 开源博客项目!

2、(eblog)小 Hub 手把手教你如何从 0 搭建一个开源项目架构

3、(eblog)整合Redis,以及项目优雅的异常处理与返回结果封装

4、(eblog)用Redis的zset有序集合实现一个本周热议功能

5、(eblog)自定义Freemaker标签实现博客首页数据填充

6、(eblog)博客分类填充、登录注册逻辑

7、(eblog)博客发布收藏、用户中心的设置

8、(eblog)消息异步通知、细节调整


搜索功能

原本我还想拆分成spring cloud项目的,不过博客项目的业务实在是少,没啥必要拆分,而我们之前二期作业已经拆分过项目了,所以想要学习spring cloud的同学可以去看看二期作业,那么这期作业我们就直接搞成springboot项目不拆分模块了。

搜索功能-ES

结合我们学习过的内容,我们之前学习搜索引擎,学过lucene还有elasticsearch,lucene比较适合单体项目,不适合分布式。

这次搜索我们用的是es,es与数据库之间的内容同步我们用的是RabbitMq进行一步同步。下面我们一一来实现这些功能。

首先我们来分析一下我们要开发的功能。

集成elasticsearch的方式有很多,

其中使用ElasticsearchRepository应该是开发量最小的一种方式,使用template或者TransportClient client方式可能会更灵活。

我们之前有学过spring data jpa,一种可以按照命名规则就可以查库的方式,在搜索单表时候特别方便。

这次开发,我们使用ElasticsearchRepository的方式,当然,引入了这个包之后,你也可以使用ElasticsearchTemplate来开发。spring都会自动帮你注入生成。

我们之前按照的es版本是elasticsearch-6.4.3.tar.gz。所以我们选择引入包的使用最好也使用对应版本的。

在maven仓库上搜索一下对应的包版本,发现前面的几个基本都可以满足要求。我们这里就选用最新的2.1.1.RELEASE版本。

除了es,我们还需要引入feign、rabbitmq等包,等下我们需要用到。这里我先统一给出一下。


org.springframework.bootspring-boot-starter-data-elasticsearch2.1.1.RELEASE


org.springframework.bootspring-boot-starter-amqp

org.modelmappermodelmapper1.1.0

es和mq的安装我们课程上有介绍过,同学们可以去回顾一下,或者多看一下我们社区文档即可完成部署。

业务分析

我们再来分析一下,因为我们已经决定了选用ElasticsearchRepository方式来访问我们的elasticsearch,所以按照这个思路,我们需要准备一个model、一个repository,这是访问存储介质es的基础,新建repository很简单,因为是spring data jpa,所以直接继承ElasticsearchRepository就可以了:

@Repository
public interface PostRepository extends ElasticsearchRepository {
}

那model的内容是啥呢?我们来看看前端搜索列表,列表需要啥数据,我们就存啥数据就行。我们使用了搜索引擎,那么搜索的结果最好就不用再需要经过我们的数据库查询,这样我们就能直接把搜索的结果直接返回给前端显示。从而提升搜索的速度。比如我们看数据列表显示。在这里我们可以看到,需要标题,作者名称,作者id、创建时间,阅读数量等等。

新建一个类PostDocment放在model包下。其实基本和我们的postVo差不多就行了。

@Document(indexName = "post", type = "post")
@Data
public class PostDocument implements Serializable {@Idprivate Long id;// 中文分词器 -> https://github.com/medcl/elasticsearch-analysis-ik@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String title;
// @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
// private String content;private Long authorId;@Field(type = FieldType.Keyword)private String authorName;private String authorVip;private String authorAvatar;private Long categoryId;@Field(type = FieldType.Keyword)private String categoryName;private Boolean recommend;private Integer level;@Field(type = FieldType.Text)private String tags;private Integer commentCount;private Integer viewCount;@Field(type = FieldType.Date)private Date created;
}

这里我整理了一下所需要的字段。然后需要用上一下jpa的注解。FieldType.Text表示是文本,需要经过分词(这里我们先不讲分词,后面再说)。FieldType.Keyword则需要完全匹配的才行。

这里我把标题和简介用文本搜索,作者和分类则需要完全匹配才能搜索出来。analyzer = "ikmaxword"是关于分词器的,后面我们会讲到。

有了这个实体之后,es会自动帮我们新建数据索引结构。我们就可以使用PostRepository来增删改查我们的es数据了。

接下来我们新建一个搜索search的方法,因为需要分页,所以page、size参数是必要的。然后还有keyword参数。就是对keyword进行查询并分页返回结果。

代码如下:

@RequestMapping("/search")
public String search(@RequestParam(defaultValue = "1") Integer current,@RequestParam(defaultValue = "10")Integer size,String q) {Pageable pageable = PageRequest.of(current - 1, size);Page documents = searchService.query(pageable, q);IPage pageData = new com.baomidou.mybatisplus.extension.plugins.pagination.Page(current, size, documents.getTotalElements());pageData.setRecords(documents.getContent());req.setAttribute("pageData", pageData);req.setAttribute("q", q);return "search";
}

首先我们拼成data jpa的分页封装Pageable,最后得到的Page对象也是jpa的,但是我们因为我们返回的选项是mybatis plus的,所以做了一层转换。最后得到pageData。这里面主要的方法就是这个查询方法searchService.query。

新建SearchService接口和实现类,query方法的查询其实很简单,因为这里我们只有一个关键字查询,没有涉及其他很多复杂查询,所以我们先简单实现,后面我们涉及到权重、分词等问题时候我们可以再调整一下。

搜索的逻辑是,让关键字分别和我需要查询的字段进行多匹配,只要其中一个字段匹配上我们就命中。多字段匹配我们可以使用MultiMatchQueryBuilder来构建。至于字段的名称,我写了一个IndexKey。因为搜索不仅仅是搜索标题,还需要搜索作者名称,分类名称等信息,用or关联起来得到最后的结果

/*** 索引名称*/
public class IndexKey {public static final String POST_TITLE = "title";public static final String POST_DESCRIPTION = "content";public static final String POST_AUTHOR = "authorName";public static final String POST_CATEGORY = "categoryName";public static final String POST_TAGS = "tags";
}

以上这些都是我需要搜索的字段。构建了多字段匹配之后,我们用NativeSearchQueryBuilder 整合起来,并进行分页,得到一个SearchQuery结果,然后我们就可以使用postRepository.search(searchQuery);来得到我们需要的分页结果了,是不是感觉挺简单的哈。关于spring data jpa的复杂查询语法,大家回去看看我们的jpa的课程内容。熟悉一下:

@Override
public Page query(Pageable pageable, String keyword) {//多个字段匹配,只要满足一个即可返回结果MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(keyword,IndexKey.POST_TITLE,IndexKey.POST_DESCRIPTION,IndexKey.POST_AUTHOR,IndexKey.POST_CATEGORY,IndexKey.POST_TAGS);SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(multiMatchQueryBuilder).withPageable(pageable).build();Page page = postRepository.search(searchQuery);log.info("查询 - {} - 的得到结果如下-------------> {}个查询结果,一共{}页",keyword, page.getTotalElements(), page.getTotalPages());return page;
}

multiMatchQuery支持对多个字段进行匹配。这样我们查询关键字的时候,就回去查询这些字段:title、author、category等。最后是构建SearchQuery,并 使用repository查询接口。这样我们就可以实现es的查询功能啦,虽然还没有数据。然后我们来调整一下前端,找到前端的js

中文分词

刚刚我们做了一个搜索功能,但是es的搜索分词是使用默认的标准分词器,我们都知道分词是es很重要的部分,搜索智能不智能就看分词好不好,分词效果好的话搜索出来的结果越精确。

现在我们来给es安装中文IK分词器。我在github上找了个还不错的ik分词器

使用分词器的方法很简单,在安装分词器之前,我们先来测试一下没安装之前的分词效果,和分词之后的分词效果。

使用postman来测试。首先测试默认情况下的标准分词器。

http://47.106.38.101:9200/post/_analyze
Headers Content-Type: application/json
Method POST
Body
{"text":"美国留给伊拉克的是个烂摊子吗",
}

测试结果如下如: 

可以看到默认的标准分词器并不认识我们中文,只是简单把每个字分开而已。这样的分词会给搜索结果带来很大的不准确性。

那么接下来我们安装一下刚才说的IK分词器。根据ReadMe的说明。安装的方法有两个,这里我们采用第二种:optional 2 - use elasticsearch-plugin to install ( supported from version v5.5.1 ):

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

上面是使用elasticsearch-plugin命令来安装插件,也给出了NOTE给我们要注意替换版本号。因为我安装的es版本是6.4.3,所以直接替换上面的两个6.3.0。然后直接进入es安装目录执行即可安装成功了。

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.3/elasticsearch-analysis-ik-6.4.3.zip

安装成功之后注意重启一下es,直接kill进程,然后重启就行。重启之后自己可以根据给出的命令来测试一下。这里我继续刚才的测试。在body的json中添加analyzer,然后测试:

{"text":"美国留给伊拉克的是个烂摊子吗","analyzer": "ik_smart"
}

测试结果: 

可以看到分词结果明显不同了。IK分词给出了两种 iksmart** , **ikmax_word。两者区别在于

可以自己动手观察一下结果。https://blog.csdn.net/weixin_44062339/article/details/85006948

以下是我测试用的postman实例。大家可以自己导进去测试一下:

elasticsearch-test.postman_collection.json

安装成功了之后我们就可以在我们代码里面加入我们的分词器了。代码改动很小,我们只需要在PostDocment实体的字段上加上注解属性analyzer和searchAnalyzer。analyzer是保存时候分词,这里我使用ikmaxword ,这样可以搜索的词语更多。searchAnalyzer表示搜索时候的分词,我只用ik_smart,挑关键词语搜索。这里大家可以查看搜索结果来调整。

删除之前的索引,然后重启hw-search项目,查看索引信息里面可以看到,description和title已经有了分词器(elasticsearch-head)。

以上,我们就完成了中文分词效果。

初始同步

我们已经完成了es搜索引擎的查询功能,但是现在还没有数据,初始化数据的话我们可以批量查询数据库然后插入到es中,比较普遍也比较简单的方式:

这个数据同步的操作我们放在后台管理中,只能超级管理员有权限操作,所以我们新建一个AdminController,然后/admin开头,然后在shiro中,我们需要拦截这个admin开头的链接,需要admin角色权限才有权限操作。

hashMap.put("/admin/**", "auth, roles[admin]");

然后我们看看controller

@Controller
@RequestMapping("/admin")
public class AdminController extends BaseController {&#64;AutowiredSearchService searchService;&#64;ResponseBody&#64;PostMapping("/initEsData")public Result initEsData() {int total &#61; 0;int size &#61; 10000;Page page &#61; new Page<>();for(int i &#61; 1; i <1000; i &#43;&#43;) {page.setCurrent(i);page.setSize(size);IPage paging &#61; postService.paging(page, null, null, null, null, "created");int num &#61; searchService.initEsIndex(paging.getRecords());total &#43;&#61; num;if(num }

上面的逻辑其实很简单&#xff0c;就是批量查询出数据让&#xff0c;然后保存到es中&#xff0c;当查询出来的数量比每页数量少时候说明已经是最后一页了&#xff0c;这时候break结束。searchService.initEsIndex是比较关键的逻辑&#xff0c;其实就是把PostVo映射成PostDocment&#xff0c;然后就可以使用repository保存了。

&#64;Override
public int initEsIndex(List datas) {if(datas &#61;&#61; null || datas.isEmpty()) return 0;List docs &#61; new ArrayList<>();for(PostVo vo : datas) {PostDocument doc &#61; modelMapper.map(vo, PostDocument.class);docs.add(doc);}//批量保存postRepository.saveAll(docs);return docs.size();
}

然后现在我们得到一个链接就是/admin/initEsData&#xff0c;这个按钮在哪里发起呢&#xff0c;因为我们现在没有后台&#xff0c;所以把这个管理员操作放在了用户中心的基本设置中&#xff0c;添加了一个新的tab&#xff08;管理中心&#xff09;&#xff0c;里面有个按钮就是同步ES&#xff0c;点击按钮就会发起form表单提交。 

具体代码&#xff1a;

<&#64;shiro.hasRole name&#61;"admin">




注意这个按钮其实是一个form表单的提交按钮&#xff0c;这样我们就不再需要写js了&#xff0c;因为已经有全局的form表单的js。

改动同步

接下来我们做些数据改动同步的问题&#xff0c;当我们添加修改或者删除了文章数据时候&#xff0c;es能同步修改。

这里我们使用的是mq&#xff0c;数据发送变化时候&#xff0c;发送一条消息到MQ&#xff0c;然后mq消费端接受消息然后把这条消息的最新状态同步到ES中

首先类配置一下mq。新建一个RabbitMqConfig类放在config包下。我们先来回顾一下消息队列的内容&#xff0c;RabbitMQ里面发送接收消息有几种类型

  • Direct类型&#xff08;路由模式&#xff09;

  • Fanout 类型&#xff08;发布订阅模式&#xff09;

  • Topic类型&#xff08;通配符模式&#xff09;

这里我们直接使用Direct模式即可。这里会涉及到队列Queue、交换机Exchange、还有路由键RouteKey&#xff08;BindingKey&#xff09;。

发布者发送消息到交换机&#xff0c;通过交换机和队列的路由键&#xff0c;把消息推向队列并保存起来&#xff0c;然后消费者订阅队列处理消息即可。

所以在RabbitMqConfig里面我们需要声明一下queue、exchange、routekey。并且绑定起来。

  • com.example.config.RabbitMqConfig

&#64;Configuration
public class RabbitMqConfig {// 队列名称public final static String ES_QUEUE &#61; "es_queue";public final static String ES_EXCHANGE &#61; "es_exchange";public final static String ES_BIND_KEY &#61; "es_index_message";/*** 声明队列* &#64;return*/&#64;Beanpublic Queue exQueue() {return new Queue(ES_QUEUE);}/*** 声明交换机* &#64;return*/&#64;BeanDirectExchange exchange() {return new DirectExchange(ES_EXCHANGE);}/*** 绑定交换机和队列* &#64;param exQueue* &#64;param exchange* &#64;return*/&#64;BeanBinding bindingExchangeMessage(Queue exQueue, DirectExchange exchange) {return BindingBuilder.bind(exQueue).to(exchange).with(ES_BIND_KEY);}
}

关于消费者和生产者之间的消息&#xff0c;我们需要一个共同约定类型。这里我需要新建一个消息模板&#xff0c;生产者发布消息时候只需要往消息模板填写&#xff0c;然后发送过来&#xff0c;消费者就能根据消息模板来处理消息。消息模板的内容需要斟酌一下&#xff0c;首先需要一个类型&#xff0c;因为是es与数据库数据的同步&#xff08;比如新发表、更新、删除了一篇文章&#xff09;。不同类型需要不同的处理手段。然后文章的id需要的。然后如果es处理数据失败的话我们需要重试&#xff0c;重试次数是有限的&#xff0c;所以这里我们定义一个重试次数的字段。

代码如下&#xff1a;

  • com.example.search.common.PostMqIndexMessage

/*** 用于服务之间消息通讯模板*/
&#64;Data
public class PostMqIndexMessage {public static final String CREATE &#61; "create";public static final String UPDATE &#61; "update";public static final String REMOVE &#61; "remove";public static final int MAX_RETRY &#61; 3;private Long postId;private String type;private int retry &#61; 0;public PostMqIndexMessage() {}public PostMqIndexMessage(Long postId, String type) {this.postId &#61; postId;this.type &#61; type;}public PostMqIndexMessage(Long postId, String type, int retry) {this.postId &#61; postId;this.type &#61; type;this.retry &#61; retry;}
}

因为消息发送都是通过经过序列化的json数据&#xff0c;所以我们先用String类型把消息内容接受&#xff0c;然后让内容与消息模板进行转换。可以使用ObjectMapper这个工具。然后接下来就是根据消息类型来处理消息了。

  • com.example.mq.HandlerMessage

/*** 监听异步消息队列* 更新搜索内容*/
&#64;Slf4j
&#64;Component
&#64;RabbitListener(queues &#61; RabbitMqConfig.ES_QUEUE)
public class HandlerMessage {&#64;Autowiredprivate ObjectMapper objectMapper;&#64;AutowiredSearchService searchService;&#64;RabbitHandlerpublic void handler(String content) {try {PostMqIndexMessage message &#61; objectMapper.readValue(content, PostMqIndexMessage.class);switch (message.getType()) {case PostMqIndexMessage.CREATE:case PostMqIndexMessage.UPDATE:searchService.createOrUpdateIndex(message);break;case PostMqIndexMessage.REMOVE:searchService.removeIndex(message);break;default:log.warn("没有找到对应的消息类型&#xff0c;请注意&#xff01;&#xff01;&#xff01;, ---> {}", content);break;}} catch (IOException e) {log.error("这是内容----> {}", content);log.error("处理HandlerMessage失败 --> ", e);}}
}

接下来我们就去SearchService里去写对应的方法。首先看createOrUpdateIndex方法&#xff0c;在ElasticsearchRepository里面&#xff0c;更新或者新建都是用save方法&#xff0c;所以步骤基本都一直&#xff0c;新建类型里我先删掉原来的&#xff08;如果有&#xff09;。其他没啥不一样了。

然后我们注重看下createOrUpdateIndex和removeIndex两个方法&#xff0c;创建或修改其实很简单&#xff0c;只需要把修改的数据查询出来&#xff0c;然后转换成PostDocument就可以直接持久化了&#xff0c;

  • com.example.service.impl.SearchServiceImpl

/*** 异步创建或者更新*/
public void createOrUpdateIndex(PostMqIndexMessage message) {long postId &#61; message.getPostId();PostVo postVo &#61; postService.selectOne(new QueryWrapper().eq("p.id", postId));log.info("需要更新的post --------> {}", postVo.toString());if(PostMqIndexMessage.CREATE.equals(message.getType())) {if(postRepository.existsById(postId)) {this.removeIndex(message);}}PostDocument postDocument &#61; new PostDocument();modelMapper.map(postVo, postDocument);PostDocument saveDoc &#61; postRepository.save(postDocument);log.info("es 索引更新成功&#xff01;--> {}" , saveDoc.toString());
}

删除也差不多&#xff0c;直接通过id就可以删除

&#64;Override
public void removeIndex(PostMqIndexMessage message) {long postId &#61; message.getPostId();postRepository.deleteById(postId);log.info("es 索引删除成功&#xff01;--> {}" , message.toString());

}

有了mq的消费端&#xff0c;那么在哪里发送mq消息呢&#xff1f;在增删改数据的时候发送一条mq消息。我们找到对应的方法&#xff1a;

  • com.example.controller.PostController#submit

其中mq的发送模板是AmqpTemplate &#xff0c;直接注入即可。

&#64;Autowired
AmqpTemplate amqpTemplate;

删除也类似&#xff1a;

  • com.example.controller.PostController#delete

发送了mq之后&#xff0c;消费端就会同步es的数据&#xff0c;所以我们能保证es的数据和数据库的数据实时保持一致。

致此&#xff0c;搜索功能已经完成。

后台管理

置顶精选

关于文章的指定精选删除等功能&#xff0c;都是后台管理员能操作的动作&#xff0c;指定和精选其实都是post的一个状态问题&#xff0c;所以逻辑上特别简单&#xff0c;而且原js已经帮我们写好了方法&#xff0c;所以我们只需修改一下链接&#xff0c;然后在AdminController中添加对应的处理逻辑即可。

先来看下前端&#xff1a;

  • static/res/mods/jie.js

//设置置顶、状态
,set: function(div){var othis &#61; $(this);fly.json(&#39;/admin/jie-set/&#39;, {id: div.data(&#39;id&#39;),rank: othis.attr(&#39;rank&#39;),field: othis.attr(&#39;field&#39;)}, function(res){if(res.status &#61;&#61;&#61; 0){location.reload();}});
}

可以看到&#xff0c;发起链接时候有几个参数&#xff0c;id&#xff0c;rank&#xff0c;field等。然后看看html&#xff1a;

  • templates/post/view.ftl

<&#64;shiro.hasRole name&#61;"admin">删除<#if post.level &#61;&#61; 0>置顶<#if post.level gt 0>取消置顶<#if !post.recommend>加精<#if post.recommend>取消加精

因为是后台管理操作&#xff0c;所以需要<&#64;shiro.hasRole name&#61;"admin">这个admin权限&#xff0c;然后我们需要在Shiro的Realm中声明一下权限的问题&#xff0c;这里我比较简单&#xff0c;没有写权限模块&#xff0c;直接写死了admin作为超级管理员&#xff1a;

  • com.example.shiro.AccountRealm

&#64;Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {AccountProfile principal &#61; (AccountProfile) principalCollection.getPrimaryPrincipal();// 硬编码if(principal.getUsername().equals("admin") || principal.getId() &#61;&#61; 1){SimpleAuthorizationInfo info &#61; new SimpleAuthorizationInfo();info.addRole("admin");return info;}return null;
}

可以看到硬编码写死了用户admin或者id为1有角色admin&#xff0c;其他都没有任何权限和角色。然后我们再看看AdminController

  • com.example.controller.AdminController

&#64;ResponseBody
&#64;PostMapping("/jie-set")
public Result jieSet(Long id, Integer rank, String field) {Post post &#61; postService.getById(id);Assert.isTrue(post !&#61; null, "该文章已被删除");if("delete".equals(field)) {postService.removeById(id);return Result.succ(null);} else if("stick".equals(field)) {post.setLevel(rank);} else if("status".equals(field)) {post.setRecommend(rank &#61;&#61; 1);}postService.updateById(post);return Result.succ(null);
}

直接就调用updateById修改状态即可。这里还有一个删除功能&#xff0c;也就是说不仅仅本人可以删除&#xff0c;超级管理员也有权限删除文章了。

 给eblog一个star&#xff0c;感谢支持哈



推荐阅读
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • 本文总结了初学者在使用dubbo设计架构过程中遇到的问题,并提供了相应的解决方法。问题包括传输字节流限制、分布式事务、序列化、多点部署、zk端口冲突、服务失败请求3次机制以及启动时检查。通过解决这些问题,初学者能够更好地理解和应用dubbo设计架构。 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
  • 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储模式
    代码已上传Github+Gitee,文末有地址  书接上文:前几回文章中,我们花了三天的时间简单了解了下接口文档Swagger框架,已经完全解放了我们的以前的Word说明文档,并且可以在线进行调 ... [详细]
  • 五、RabbitMQ Java Client基本使用详解
    JavaClient的5.x版本系列需要JDK8,用于编译和运行。在Android上,仅支持Android7.0或更高版本。4.x版本系列支持7.0之前 ... [详细]
  • zuul 路由不生效_Zuul网关到底有何牛逼之处?竟然这么多人在用~
    作者:kosamino来源:cnblogs.comjing99p11696192.html哈喽,各位新来的小伙伴们,大家好& ... [详细]
  • Ribbon使用Hystrix
    1、导入依赖spring-cloud-starter-hystrixorg.springframework.cloud ... [详细]
  • 这也太简单了!轻松操作Feign 服务调用使用 Zipkin 链路追踪!
    0、介绍分布式微服务时代,方便了业务的快速增长和服务的稳定,但是系统出现问题后,面对同业务多服务排查起来令人头大。这时候领导就想着集成分布式追踪系统。Zipkin是T ... [详细]
  • 1.0为什么要做这个博客站?  在工作学习中,经常要搜索查找各种各样的资料,每次找到相关资料后都会顺手添加到浏览器书签中,时间一长,书签也就满了。而且下次再点击这个书签时,可能就会忘记当时为什么要添加这个书签了,更有可能书签连接已经无效。这样一来,也就不方便 ... [详细]
  • 微服务应用性能分析实战15 数据磐石:APM 收集端的存储模型
    分布式监控的重要设计就是数据存储模型,而SkyWalking的分布式追踪数据模型就是一个经典代表,这也是它会在APM领域脱颖而出的原因。所以今天我就以 ... [详细]
  • 此版本重点升级了Online代码生成器,支持更多的控件生成,所见即所得,极大的提高开发效率;同时做了数据库兼容专项工作,让Online开发兼容更多数据库:Mysql、SqlServer、Oracle、Postgresql等!!!项目介绍 ... [详细]
  • 领域驱动设计 领域事件DDD分层架构
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了领域驱动设计领域事件DDD分层架构相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 目录摘要SQL的现在NoSQL,NotOnlySQL要分布式,也要SQL总结引用摘要毫不夸张的说,关系数据库是企业软件系统的核心,企业形形色色信息行为的背后,都有关系数据库的支撑。 ... [详细]
  • celery 爬虫使用
    简介celery是一个基于分布式消息传输的异步任务队列,它专注于实时处理,同时也支持任务调度。它由三部分组成,消息中间件, ... [详细]
author-avatar
-MP5
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有