Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作。您可以使用 Kibana 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作。您可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。

目录

  1. 引子
  2. ELK快速安装
  3. Elasticsearch快速入门
  4. SpringBoot整合ELK日志中心

ELK是Elastic公司提供的一套完整的日志收集以及展示的解决方案,也就是我们常说的日志中心,其中E代表Elasticsearch,L代表Logstash,K就是本文的中心Kibana。

Elasticsearch在其中担当了数据源的作用,用于对数据快速搜索定位,这也是Github搜索使用的解决方案。

在超大数据规模的情况下,能够将查询条件匹配然后返回,高亮显示,你应该知道Elasticsearch的强大了。

Logstash的作用是日志收集器,可以将不同来源的日志数据进行处理输出。下图是我的Logstash配置文件logstash.conf,配置分为两个核心模块,input和output,input中是logstash开放的端口5000,用于应用将日志提交给logstash,在output中是logstash处理完日志后将日志输出的地方,可以看到是输出到Elasticsearch,至于下面的用户名密码,如果你的Elasticsearch有设置即填,没有设置则忽略。

Kibana的作用是对Elasticsearch中存储的数据进行数据分析和可视化处理。

Kibana主要的作用是对日志进行可视化处理,同样的还有Grafana,不过Grafana可以指定多种数据源,比如Prometheus,Mysql,Elasticsearch等

一. ELK快速安装

​需要了解的ELK开放端口

  • 5000: Logstash TCP input
  • 9200: Elasticsearch HTTP
  • 9300: Elasticsearch TCP transport
  • 5601: Kibana
  • 方式一:

    使用docker安装,克隆改仓库https://github.com/deviantony/docker-elk

    在目录下执行docker-compose up

  • 方式二:

    使用同版本的包,此处ELK三个包版本必须相同,以免使用出现问题,我本地都是7.1.0。

    我已经将三个同版本的包打包上传好了,公众号回复【ELK】获得网盘下载地址

    将包解压后,在各个bin目录下执行相应程序即可。

    1. # ..\elasticsearch-7.1.0\bin  .\elasticsearch.bat
    1. # E:\kibana-7.1.0\bin  .\kibana.bat

    类似这样,先启动elasticsearch。

    浏览器访问 localhost:9200,可以看到elasticsearch的启动状态,然后启动kibana,浏览器访问 localhost:5601。

二. Elasticsearch快速入门

Ⅰ. 索引,文档和REST API

1. 文档
  • Elasticsearch是面向文档的,文档是所有可搜索数据的最小单位。

    • 日志文件中的日志项
    • 一部电影的具体信息 / 一张唱片的详细信息
    • MP3播放器中的一首歌 / 一篇PDF文档中的具体内容
  • 文档会被序列化成JSON格式,保存在ElasticSearch中
    • JSON对象由字段组成
    • 每个字段都有对应的字段类型(字符串/数值/布尔/日期/二进制/范围类型)
  • 每个文档都有一个Unique ID
    • 你可以自己指定ID
    • 或者通过Elasticsearch自动生成
2. JSON文档
  • 一篇文档包含了一系列字段。类似数据库表中一条记录

  • JSON文档,格式灵活,不需要预先定义格式

    • 字段的类型可以指定或者通过ElasticSearch自动推算
    • 支持数组/支持嵌套

3. 文档的元数据
  • 元数据,用于标注文档的相关信息

    • _index 文档所属的索引名
    • _type 文档所属的类型名
    • _id 文档唯一id
    • _source 文档的原始Json数据
    • _all 整合所有字段内容到该字段,已被废除
    • _version 文档的版本信息
    • _score 相关性打分

4. 索引
  • Index — 索引是文档的容器,是一类文档的结合

    • Index体现了逻辑空间的概念:每个索引都有自己的Mapping定义,用于定义包含的文档的字段名和字段类型
    • Shard体现了物理空间的概念:索引中数据分散在Shard上
  • 索引的Mapping与Setting

    • Mapping定义文档字段的类型
    • Setting定义不同的数据分布
  • 索引的不同语义

索引(动词)文档到ElasticSearch的索引(名词)中

  • 名词:一个ElasticSearch集群中,可以创建很多个不同的索引
  • 动词:保存一个文档到ElasticSearch的过程也叫索引(Indexing)
    • ES中创建一个倒排索引的过程
  • 名词: 一个B树索引,一个倒排索引
5. Type

  • 在7.0之前,一个Index可以设置多个Types
  • 6.0开始,Type已经被Deprecated。7.0开始,一个索引只能有一个Type—"_doc"
6. 与关系型数据库类比
RDBMS ElasticSearch
Table Index
Row Document
Column Filed
Schema Mapping
SQL DSL
  • 传统关系型数据库与ElasticSearch的区别

    • ElasticSearch — Schemaless / 相关性高 / 高性能全文检索
    • RDMS — 事物性 / Join
7. REST API — 很容易被各种语言调用

8. 一些基本的API
  • Indices

    • 创建Index

      • PUT Movies
    • 查看所有Index
      • _cat/indices
  1. // 查看指定索引的相关信息 Mapping/Setting的设置
  2. GET index_name
  3. // 查看指定索引的文档总数
  4. GET index_name/_count
  5. // 查看指定索引的前10条文档
  6. POST index_name/_search
  7. // _cat indices API
  8. // 对索引名称进行通配符查询
  9. GET /_cat/indices/kibana*v&s=index
  10. // 查看索引状态为绿的索引
  11. GET /_cat/indices/v&health=green
  12. // 按照文档个数排序
  13. GET /_cat/indices/v&s=docs.count:desc

Ⅱ. 节点,集群,分片和副本

1. 分布式系统的可用性与扩展性
  • 高可用性

    • 服务可用性 — 允许有节点停止服务
    • 数据可用性 — 部分节点丢失,不会丢失数据
  • 可扩展性
    • 请求量提示 / 数据的不断增长 (将数据分布到所有节点上)
2. 分布式特性
  • ElasticSearch的分布式架构的好处

    • 存储的水平扩容
    • 提升系统的可用性,部分节点停止服务,整个集群的服务不受影响
  • Elasticsearch的分布式架构

    • 不同的集群通过不同的名字来区分,默认名字"elasticsearch"
    • 通过配置文件修改,或者在命令行中 -E cluster.name=clustername 进行设定
    • 一个集群可以有一个或多个节点
3. 节点
  • 节点是一个Elasticsearch的实例

    • 本质上就是一个JAVA进程
    • 一台机器上可以运行多个Elasticsearch进程,但生产环境一个一机一个
  • 每个节点都有名字,通过配置文件,或者启动时通过 -E node.name=nodename进行设定
  • 每个节点在启动之后,会分配一个UID,保存在data目录下
4. Master-ekigible nodes 和 Master Node
  • 每个节点启动后,默认就是一个Master eligible节点

    • 可以设置node.master: false 禁止
  • Master-eligible节点可以参与选主流程,成为Master节点
  • 当第一个节点启动时,他会将自己选举为Master节点
  • 每个节点上都保存了集群的状态,只有Master节点才能修改集群的状态信息
    • 集群状态,维护了一个集群中,必要的信息

      • 所有的节点信息
      • 所有的索引和其相关的Mapping与Setting信息
      • 分片的路由信息
    • 任意节点都能修改信息会导致数据的不一致性
5. Data Node & Coordinating Node
  • Data Node

    • 可以保存数据的节点,叫做Data Node。负责保存分片数据。在数据扩展上起到了至关重要的作用
  • Coordinating Node
    • 负责接收Client的请求,将请求发到合适的节点,最终把结果汇聚到一起
    • 每个节点默认都起到Coordinating Node的职责
6. 其他的节点类型
  • Hot & Warm Node

    • 不同硬件配置的Data Node,用来实现Hot & Warm架构,降低集群部署的成本
  • Machine Learning Node
    • 负责跑机器学习的Job,用来做异常检测
  • Tribe Node
    • Tribe Node连接到不同的Elasticsearch集群,并且支持将这些集群当成一个单独的集群处理
7. 配置节点类型

8. 分片(Primary Shard & Replica Shard)
  • 主分片,用以解决数据水平扩展的问题。通过主分片,可以将数据发布到集群内所有节点上

    • 一个分片是一个运行的Lucene实例
    • 主分片在索引创建时指定,后续不允许修改,除非Reindex
  • 副本,用以解决数据高可用的问题。分片是主分片的拷贝
    • 副本分片数,可以动态调整
    • 增加副本数,还可以在一定程度上提高服务的可用性(读取的吞吐)
9. 分片的设定
  • 对于生产环境中分片的设定,需要提前做好容量规划

    • 分片数设置过小

      • 导致后续无法增加节点实现水品扩展
      • 单个分片的数据量太大,导致数据重新分配耗时
    • 分片数设置过大,7.0开始,默认主分片设置成1,解决了over- sharding的问题
      • 影响搜索结果的相关性打分,影响统计结果的准确性
      • 单个节点上过多的分片,会导致资源浪费,同时也会影响性能
10. 查看集群状态
  • 使用REST API

    • _cat/nodes
    • _cat/shard
  • 使用cerebro

    • 启动访问localhost:9000

Ⅲ. 文档的基本CRUD与批量操作

1. 文档的CRUD
  • Index (如果ID不存在,创建新的文档,如果存在,删除原有的再创建,版本会增加)

    1. PUT my_index/_doc/1
    2. {
    3. "user": "mike",
    4. "comment": "hello"
    5. }
  • Create

    1. PUT my_index/_create/1
    2. {
    3. "user": "mike",
    4. "comment": "hello"
    5. }
    6. POST my_index/_doc(不用指定ID,自动生成)
    7. {
    8. "user": "mike",
    9. "comment": "hello"
    10. }
  • Read

    1. GET my_index/_doc/1
  • Update 文档必须存在,更新字段

    1. POST my_index/_update/1
    2. {
    3. "user": "mike",
    4. "comment": "hello es"
    5. }
  • Delete

    1. DELETE my_index/_doc/1
2. Bulk API

支持在一次API调用中,对不同的索引进行操作

  • 支持四种类型操作

    • Index
    • Create
    • Update
    • Delete
  • 可以在URL中指定Index,也可以在请求的Payload中进行
  • 操作中单个操作失败,不影响其他操作
  • 返回结果包括了每一条操作执行的结果
  1. POST _bulk
  2. {"index": {"_index": "test", "_id": "1"}}
  3. {"field1": "value1"}
  4. {"delete": {"_index": "test", "_id":"2"}}
3. 批量读取 mget

批量操作,可以减少网络连接所产生的开销,提高性能

  1. GET _mget
  2. {
  3. "docs": [
  4. {
  5. "_index": "user",
  6. "_id": 1
  7. },
  8. {
  9. "_index": "comment",
  10. "_id": 1
  11. }
  12. ]
  13. }
4. 批量查询 msearch
  1. POST my_index/_msearch
  2. {}
  3. {"query": {"match_all":{}, "from":0, "size":10}}
  4. {}
  5. {"query": {"match_all":{}}
  6. {"index": "twitter2"}
  7. {"query": {"match_all":{}}

Ⅳ. 倒排索引

1. 正排与倒排索引
  • 目录页,根据目录页索引,正排索引,章名称+页码号
  • 根据关键字进行索引到目录页,倒排索引

在搜索引擎中

  • 正排索引 — 文档ID到文档内容和单词的关联
  • 倒排索引 — 单词到文档ID的关系

2. 倒排索引的核心组成
  • 倒排索引包含两个部分

    • 单词词典,记录所有文档的单词,记录单词到倒排列表的关联关系

      • 单词词典一般比较大,可以通过B+树或者哈希拉链法实现,以满足高性能的插入与查询
    • 倒排列表,记录了单词对应的文档结合,由倒排索引项组成
      • 倒排索引项

        • 文档ID
        • 词频TF
        • 位置 — 单词在文档中分词的位置。用于语句搜索
        • 偏移 — 记录单词的开始结束位置,实现高亮显示

3. Elasticsearch的倒排索引
  • Elasticsearch的JSON文档中的每个字段,都有自己的倒排索引
  • 可以指定对某些字段不做索引
    • 优点: 节省存储空间
    • 缺点:字段无法被搜索

Ⅴ. 通过Analyzer进行分词

1. Analysis与Analyzer
  • Analysis — 文本分析是把全文本转换为一系列单词(term/token)的过程,也叫分词

  • Analysis是通过Analyzer来实现的

    • 可使用Elasticsearch内置的分析器/或者按需定制分析器
  • 除了在数据写入时转换词条,匹配Query语句时也需要用相同的分析器对查询语句进行分析

2. Analyzer的组成
  • 分词器是专门处理分词的组件,Analyzer由三部分组成

    • Character Filter,针对原始文本处理,例如去除html / Tokenizer 按照规则切分为单词 / Token Filter 将切分的单词进行加工,小写,删除stopwords,增加同义词

3. Elasticsearch内置分词器

4. 使用_analyzer API
  • 直接指定Analyzer进行测试

    1. GET /_analyze
    2. {
    3. "analyzer": "standard",
    4. "text": "Mastering Elasticsearch, elasticsearch in Action"
    5. }
  • 指定索引字段进行测试

    1. POST books/_analyze
    2. {
    3. "field": "title",
    4. "text": "Mastering Elasticsearch"
    5. }
  • 自定义分词进行测试

  1. POST /_analyze
  2. {
  3. "tokenizer": "standard",
  4. "filter": ["lowercase"],
  5. "text": "Mastering Elasticsearch"
  6. }
5. 中文分词
  • 中文句子,切分成一个一个词,而不是字

  • 英文中,单词有自然空格分隔

  • 一句中文,不同上下文有不同理解

    • 这个苹果,不大好吃 / 这个苹果,不大,好吃!
  • 安装ICU Analyzer

在elasticsearch的bin目录下执行

  1. elasticsearch-plugin install analysis-icu

提供了Unicode的支持,更好的支持亚洲语言

6. 更多好用的中文分词器
  • LK

    • 支持自定义词库,支持热更新分词字典
  • THULAC
    • THU Lexuacal Analyzer for Chinese,清华大学自然语言处理和社会人文计算实验室的一套中文分词器

Ⅵ. Search API概览

1. Search API
  • URI Search

    • 在URL中使用查询参数
  • Request Body Search
    • 使用Elasticsearch提供的,基于JSON格式的更加完备的Query Domain Specific Language(DSL)
2. 指定查询的索引
  • /_search 集群上所有的索引
  • /index1/_search 索引index1
  • /index1,index2/_search 索引index1和2
  • /index*/_search 查询index开头的索引
3. Request Body

4. 搜索Response

Ⅶ. URI Search

1. 通过URI query实现搜索
  1. GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=10&timeout=1s
  2. {
  3. "profile": true
  4. }
  • q 指定查询语句,使用Query String Syntax
  • df 默认字段,不指定时,会对所有字段进行查询
  • sort 排序 / from和size用于分页
  • profile 可以查看查询是如何被执行的
2. Query String Syntax
  • 指定字段 v.s 范查询

    • q=title:2012 / q=2012

      查询title存在2012的,查询存在2012的

  • Term v.s Phrase

    • Beautiful Mind 等效于Beautiful OR Mind
    • "Beautiful Mind",等效于Beautiful AND Mind。Phrase查询,要求前后顺序一致
  • 分组与引号

    • title:(Beautiful AND Mind) ---> title:Beautiful title:Mind
    • title="Beautiful Mind" ---> title:Beautiful Mind
  • 布尔操作

    • AND / OR / NOT 或者 && / || / !

      • 必须大写
      • title:(matrix NOT reloaded)
  • 分组

    • + 表示must
    • -表示must_not
    • title:(+matrix -reloaded)
  • 通配符查询(不建议使用,占用内存大,查询效率低)

    • ? 代表1个字符,*代表0或者多个

      • title:mi?d
      • title:be*
  • 正则表达

    • title:[bt]oy 匹配boy / toy
  • 模糊匹配与近似匹配

    • title:befutifl~1 允许输入错一个
    • title:"lord rings"~2 可以不用相邻,挨着两个

Ⅷ. Request Body Search与Query DSL

1. Request Body Search
  • 将查询语句通过HTTP Request Body发送给Elasticsearch

  • Query DSL

    1. POST /movies,404_idx/_search?ignore_unavailable=true
    2. {
    3. "profile": true,
    4. "query": {
    5. "match_all": {}
    6. }
    7. }
2. 使用查询表达式 Match
  1. GET /comments/_doc/_search
  2. {
  3. "query": {
  4. "match": {
  5. "comment": "Last Christmas"
  6. }
  7. }
  8. }
  9. GET /comments/_doc/_search
  10. {
  11. "query": {
  12. "match": {
  13. "comment": {
  14. "query":"Last Christmas",
  15. "operator": "AND"
  16. }
  17. }
  18. }
  19. }
3. Query String Query

类似URI Query

  1. POST users/_search
  2. {
  3. "query": {
  4. "query_string": {
  5. "default_field": "name",
  6. "query": "Ruan AND Ming"
  7. }
  8. }
  9. }

三. SpringBoot整合ELK日志中心

SpringBoot整合ELK的核心在于将日志发送给logstash的开放端口,常用的方案就是使用logstash提供好的日志上传包。

  1. <dependency>
  2. <groupId>net.logstash.logback</groupId>
  3. <artifactId>logstash-logback-encoder</artifactId>
  4. <version>5.1</version>
  5. </dependency>

添加该依赖,该依赖提供了一个appender类LogstashTcpSocketAppender,是logback日志框架的上报日志需要的类的实现,在logback.xml中添加相应的appender规则即可,指定输出到logstash端口。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3. <include resource="org/springframework/boot/logging/logback/base.xml" />
  4. <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
  5. <destination>127.0.0.1:5000</destination>
  6. <!-- 上述端口为elk-docker默认 -->
  7. <!-- 日志输出编码 -->
  8. <encoder charset="UTF-8"
  9. class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
  10. <providers>
  11. <timestamp>
  12. <timeZone>UTC</timeZone>
  13. </timestamp>
  14. <pattern>
  15. <pattern>
  16. {
  17. "logLevel": "%level",
  18. "serviceName": "${springAppName:-}",
  19. "pid": "${PID:-}",
  20. "thread": "%thread",
  21. "class": "%logger{40}",
  22. "rest": "%message"
  23. }
  24. </pattern>
  25. </pattern>
  26. </providers>
  27. </encoder>
  28. </appender>
  29. <root level="INFO">
  30. <appender-ref ref="LOGSTASH" />
  31. <appender-ref ref="CONSOLE" />
  32. </root>
  33. </configuration>

记得将logstash的配置文件也进行修改。

  1. input {
  2. tcp {
  3. port => 5000
  4. codec => json_lines # 上传使用json_lines插件格式化
  5. }
  6. }
  7. output {
  8. elasticsearch {
  9. hosts => "elasticsearch:9200"
  10. }
  11. }

启动ELK三个应用,然后启动你的SpringBoot应用,就可以在索引管理中看到应用的日志索引了。

然后在管理中创建索引模式,将你的应用索引添加进去,就可以在仪表盘中对日志进行搜索服务了。

DevOps元素周期表—2号元素Kibana的更多相关文章

  1. DevOps元素周期表——1号元素 Gitlab

    DevOps元素周期表--1号元素 Gitlab GitLab 是由 GitLab Inc.开发,一款基于 Git 的完全集成的软件开发平台(fully integrated software dev ...

  2. 基于 HTML5 Canvas 的元素周期表展示

    前言 之前在网上看到别人写的有关元素周期表的文章,深深的勾起了一波回忆,记忆里初中时期背的“氢氦锂铍硼,碳氮氧氟氖,钠镁铝硅磷,硫氯氩钾钙”.“养(氧)龟(硅)铝铁盖(钙),哪(钠)家(钾)没(镁)青 ...

  3. 基于 webGL 的元素周期表 3D 交互展示

    前言 之前在网上看到别人写的有关元素周期表的文章,深深的勾起了一波回忆,记忆里初中时期背的“氢氦锂铍硼,碳氮氧氟氖,钠镁铝硅磷,硫氯氩钾钙”.“养(氧)龟(硅)铝铁盖(钙),哪(钠)家(钾)没(镁)青 ...

  4. 【DevOps敏捷开发动手实验】开源文档 v2015.2 stable 版发布

    Team Foundation Server 2015 Update 2版本终于在2周前的//Build 2016大会上正式发布了,借这个东风,小编也完成了[DevOps敏捷开发动手实验]开源文档的第 ...

  5. SyntaxHighlighter行号显示错误问题解决方案

    SyntaxHighlighter是根据代码中的换行符分配行号的.但是,如果一行代码或者注释比较长,在页面显示时需要分成多行显示,会出现行号对不上的问题,像这样: 通过设置CSS强制不换行,可以保证行 ...

  6. 【算法笔记】B1008 数组元素循环右移问题

    1008 数组元素循环右移问题 (20 分) 一个数组A中存有N(>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(≥0)个位置,即将A中的数据由(A​0​​A​1​​⋯A​N ...

  7. 使用Appium进行微信公众号自动化测试

    查看Android的webview视图版本:手机链接电脑后在电脑Chrome打开页面chrome://inspect/#devices查看Android的Chrome内核版本     下载与该版本相对 ...

  8. 顺序表添加与删除元素以及 php实现顺序表实例

    对顺序表的操作,添加与删除元素. 增加元素 如下图所示  对顺序列表 Li [1328,693,2529,254]  添加一个元素 111 ,有三种方式: a)尾部端插入元素,时间复杂度O(1);  ...

  9. [LeetCode]230. 二叉搜索树中第K小的元素(BST)(中序遍历)、530. 二叉搜索树的最小绝对差(BST)(中序遍历)

    题目230. 二叉搜索树中第K小的元素 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素. 题解 中序遍历BST,得到有序序列,返回有序序列的k-1号元素. 代 ...

随机推荐

  1. 如何把一个一般的git库变成“裸库”?

    语法: git clone --bare 「src」 「dest」 e.g. cd ~/Workspace/SourceRepo/ git clone --bare ./ ../Git/bareRep ...

  2. Vue中父组件使用子组件的emit事件,获取emit事件传出的值并添加父组件额外的参数进行操作

    需求是这样的,需要输入这样一个列表的数据,可以手动添加行,每一行中客户编号跟客户姓名是自动关联的,就是说选取了客户姓名之后,客户编号是自动填充的,客户姓名是一个独立的组件,每一个下拉项都是一个大的对象 ...

  3. mybatis-spring-boot-starter 1.3.0 操作实体类的SpringBoot例子

    例程下载:https://files.cnblogs.com/files/xiandedanteng/gatling20200428-02.zip 需求:使用mybatis实现对hy_emp表的CRU ...

  4. elo system

    今天了解了一下游戏中的PVP模块的实现,大多数的游戏都使用到了ELO算法,刚开始的时候并不清楚这个算法是做什么的,对此开始大量查找有关于ELO算法的资源,功夫不负有心人,总算找到一些有用的资源了. 先 ...

  5. docker中重启某个服务命令

    docker ps------查看正在运行的cotainners docker ps -a --------查看所有的containners docker restart 容器id docker lo ...

  6. 轻松上手SpringBoot Security + JWT Hello World示例

    前言 在本教程中,我们将开发一个Spring Boot应用程序,该应用程序使用JWT身份验证来保护公开的REST API.在此示例中,我们将使用硬编码的用户和密码进行用户身份验证. 在下一个教程中,我 ...

  7. 软件定义网络(SDN)第二次实验报告

    目录 实验 2 :Mininet 实验--拓扑的命令脚本生成 一.实验目的 二.实验任务 三.实验要求 四.具体实验步骤 引导实验 Part 1 引导实验 Part 2 本周实验任务完成流程 五.注意 ...

  8. 复习 | 彻底弄懂Flexbox之Demo篇

    flexbox之前有接触,写项目时也用过,但也只是简单的,对其也是似懂非懂,所以今天下定决心把这个再学一遍,因为似懂非懂就是不懂 本文主要是的demo演示,想看flexbox语法 请移步flexbox ...

  9. 现有 Vue.js 项目快速实现多语言切换的一种思路

    Web 项目多语言(i18n,即国际化)是比较常见的需求,常规的做法大概有以下几种: 每种语言单独开发页面,适用于 CMS 之类的网站 多语言文本和页面结构分离,运行时动态替换.适用于单页应用(SPA ...

  10. 告别硬编码,让你的POI导入导出拥抱变化

    GitHub地址 | 博客 | 中文 | English | 原文链接 为什么使用AutoExcel? Excel导入导出在软件开发中非常常见,只要你接触过开发,就一定会遇到.相信很多人会跟我一样选择 ...