JoyLau's Blog

JoyLau 的技术学习与思考

添加多页面配置

之前写过一篇 npm eject 之后的多页面配置,可以往前翻阅 , 现在不想 eject, 该怎么配置多页面?

  1. npm install react-app-rewire-multiple-entry –save-dev

  2. 在 config-overrides.js 中添加配置
    现在 public 里复制一个 html 页面, 在 src 目录下再新增一个目录,里面的文件拷贝 index 的稍微改动下,
    大致目录如下:

-serviceWorker.js
-metadata.js
-metadata.css
-logo.svg
-App.test.js
-App.js
-App.css

基本使用:

1
2
3
4
5
6
7
8
9
10
11
12
const multipleEntry = require('react-app-rewire-multiple-entry')([{
entry: 'src/metadata/metadata.js',
template: 'public/metadata.html',
outPath: '/metadata',
}]);

module.exports = {
webpack: function(config, env) {
multipleEntry.addMultiEntry(config);
return config;
}
};

在 customize-cra 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const multipleEntry = require('react-app-rewire-multiple-entry')([
{
entry: 'src/entry/landing.js',
template: 'public/landing.html',
outPath: '/landing.html'
}
]);

const {
override,
overrideDevServer
} = require('customize-cra');

module.exports = {
webpack: override(
multipleEntry.addMultiEntry
)
};

结合 ant-design 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const {override, fixBabelImports, addLessLoader} = require('customize-cra');

const multipleEntry = require('react-app-rewire-multiple-entry')([{
entry: 'src/metadata/metadata.js',
template: 'public/metadata.html',
outPath: '/metadata',
}]);


module.exports = override(
multipleEntry.addMultiEntry,
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: { '@primary-color': '#1890ff' },
}),
);

注意,这样配置的话, 请求的 uri 是 /metadata, 在 build 后会生成 metadata 文件, 将打包后的文件拷贝到服务器上运行效果不好
一般我都注释掉 template, 再将 outPath 写成 /metadata.html

打包不生成 source-map 文件

在 配置文件 config-overrides.js 添加 process.env.GENERATE_SOURCEMAP = "false";
或者

在项目更目录下创建文件 .env, 写入: GENERATE_SOURCEMAP=false 即可.

背景

Nested 类型的数据不多说了,
先看 mapping:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"metaArray": {
"type": "nested",
"properties": {
"key": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"full": {
"type": "keyword"
}
}
},
"value": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"full": {
"type": "keyword"
}
}
}
}
},

再看数据:

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
{
"_index":"category_libs_v1.x",
"_type":"category_info",
"_id":"526",
"_version":1,
"_score":1,
"_source":{
"categoryName":"投标文件",
"createTime":"2019-12-23 00:07:15",
"id":"526",
"metaArray":[
{
"value":"Joy",
"key":"作者"
},
{
"value":"txt",
"key":"文件类型"
}
],
"pathName":"企业空间导航/业务条块",
"pids":"|1|525|",
"status":0,
"updateTime":"2019-12-23 00:07:15"
}
}

目的

想查作者是 Joy 并且文件类型是 txt 的记录

方式

使用 nestedQuery + queryStringQuery

语句:

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
{
"from":0,
"size":10,
"query":{
"bool":{
"must":[
{
"nested":{
"query":{
"query_string":{
"query":"metaArray.key.full:作者 AND metaArray.value.full:Joy"
}
},
"path":"metaArray",
"score_mode":"max"
}
},
{
"nested":{
"query":{
"query_string":{
"query":"metaArray.key.full:文件类型 AND metaArray.value.full:txt"
}
},
"path":"metaArray",
"score_mode":"max"
}
}
]
}
}
}

代码:

1
2
3
String key = xxxx
String value = xxxx
nestedQuery("metaArray", queryStringQuery("metaArray.key.full:" + key + " AND metaArray.value.full:" + value), ScoreMode.Max);

背景

之前一直用我弟弟的学生证申请的 license,可惜今年毕业了,无法在续费申请了
早期已经听说 JetBrains 可以使用自己的开源项目进行申请免费的 license
正好使用我的这个博客来申请一波

步骤

  1. 前往 JetBrains 官方提供的申请链接 (https://www.jetbrains.com/shop/eform/opensource?product=ALL)
  2. 填写资料,其中注意,需要在项目的根目录下创建 License 文件,类型没有差别,我使用的是 MIT LICENSE,还有就是邮箱地址和 github profile 页面的邮箱地址一致
  3. 等待了 2 天,收到了 JetBrains 工作人员的回复
  4. 点击邮件中的 Take me to my license(s)
  5. 使用申请的邮箱地址登录已有的账号或者创建新的账号
  6. 登录成功后点击 license tab 页面,会看到你填的项目名
  7. 点击 Active subscriptions, 激活,在点击 Assign分配使用
  8. 看到一些提示成功信息,就说明没有问题了,直接在 IDEA 的激活页面登录账户使用即可

小插曲

我这里因为之前使用 QQ 邮箱创建过 JetBrains 的账号,这次不想使用新的邮箱再次创建账号
于是在第四步点击 Take me to my license(s) 后,我选择授权其他邮箱,填写邮箱地址,之后你的邮箱会收到邮件,点击接受授权的链接
之后的操作都一致了,成功使用我原来的邮箱获取到了 license.

区别

个人理解:
${} : 用于加载外部文件中指定key的值
#{} : 功能更强大的SpEl表达式,将内容赋值给属性
#{…}${…} 可以混合使用,但是必须#{}外面,${}在里面,#{ ‘${}’ } ,注意单引号,注意不能反过来

#{} 功能

  1. 直接量表达式: “#{‘Hello World’}”
  2. 使用java代码new/instance of: 此方法只能是java.lang 下的类才可以省略包名 #{“new Spring(‘Hello World’)”}
  3. 使用T(Type): 使用“T(Type)”来表示java.lang.Class实例,同样,只有java.lang 下的类才可以省略包名。此方法一般用来引用常量或静态方法 ,#{“T(Integer).MAX_VALUE”}
  4. 变量: 使用“#bean_id”来获取,#{“beanId.field”}
  5. 方法调用: #{“#abc.substring(0,1)”}
  6. 运算符表达式: 算数表达式,比较表达式,逻辑表达式,赋值表达式,三目表达式,正则表达式
  7. 判断空: #{“name?:’other’”}

实例

springboot 和 elasticsearch 的整合包里有一个注解
@Document(indexName = “”, type = “”)
indexName 和 type 都是字符串
这个注解写在实体类上,代表该实体类是一个索引
现在, indexName 和 type 不能为固定写死,需要从配置文件读取,
于是想到了 spring 的 el 表达式
使用
@Document(indexName = “${xxxx}”, type = “${xxxx}”)
启动后
无效,spring 直接将其解析成了字符串
于是,查看 @Document 这个注解实现的源码
在这个包中 org.springframework.data.elasticsearch.core.mapping 找到了实现类 SimpleElasticsearchPersistentEntity
其中

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
public SimpleElasticsearchPersistentEntity(TypeInformation<T> typeInformation) {
super(typeInformation);
this.context = new StandardEvaluationContext();
this.parser = new SpelExpressionParser();

Class<T> clazz = typeInformation.getType();
if (clazz.isAnnotationPresent(Document.class)) {
Document document = clazz.getAnnotation(Document.class);
Assert.hasText(document.indexName(),
" Unknown indexName. Make sure the indexName is defined. e.g @Document(indexName=\"foo\")");
this.indexName = document.indexName();
this.indexType = hasText(document.type()) ? document.type() : clazz.getSimpleName().toLowerCase(Locale.ENGLISH);
this.useServerConfiguration = document.useServerConfiguration();
this.shards = document.shards();
this.replicas = document.replicas();
this.refreshInterval = document.refreshInterval();
this.indexStoreType = document.indexStoreType();
this.createIndexAndMapping = document.createIndex();
}
if (clazz.isAnnotationPresent(Setting.class)) {
this.settingPath = typeInformation.getType().getAnnotation(Setting.class).settingPath();
}
}

@Override
public String getIndexName() {
Expression expression = parser.parseExpression(indexName, ParserContext.TEMPLATE_EXPRESSION);
return expression.getValue(context, String.class);
}

@Override
public String getIndexType() {
Expression expression = parser.parseExpression(indexType, ParserContext.TEMPLATE_EXPRESSION);
return expression.getValue(context, String.class);
}

我们看到了 SpelExpressionParserParserContext.TEMPLATE_EXPRESSION
那么这里就很肯定 indexName 和 type 是支持 spel 的写法了,只是怎么写,暂时不知道
再看
ParserContext.TEMPLATE_EXPRESSION 的源码是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* The default ParserContext implementation that enables template expression
* parsing mode. The expression prefix is "#{" and the expression suffix is "}".
* @see #isTemplate()
*/
ParserContext TEMPLATE_EXPRESSION = new ParserContext() {

@Override
public boolean isTemplate() {
return true;
}

@Override
public String getExpressionPrefix() {
return "#{";
}

@Override
public String getExpressionSuffix() {
return "}";
}
};

看到上面的注释,知道是使用 #{}
接着
新建一个类,使用 @Configuration 和 @ConfigurationProperties(prefix = “xxx”) 注册一个 bean
再在实体类上加上注解 @Component 也注册一个bean
之后就可以使用 #{bean.indexName} 来读取到配置属性了

Spring Boot 中手动解析表达式的值

有时候我们会在注解中使用 SPEL 表达式来读取配置文件中的指定值, 一般会使用类似于 【${xxx.xxx.xxx}】这样来使用
如果在代码中手动解析该表达式的值,可以使用 Environment 的以下方法

environment.resolvePlaceholders(cidSpel) 或者 environment.resolveRequiredPlaceholders(cidSpel)

背景

docker 容器启动, 通过 docker logs -f container 可以实时查看日志

但是控制台输出的日志太多,会怎么样,容器里控制台输出的日志在宿主机什么位置?

有时容器输出太多,运行时间长了后,会把磁盘撑满…

解释

docker 里容器的日志都属于标准输出(stdout)
每个 container 都是一个特殊的进程,由 docker daemon 创建并启动,docker daemon 来守护和管理

docker daemon 有一个默认的日志驱动程序,默认为json-file
json-file 会把所有容器的标准输出和标准错误以json格式写入文件中,这个文件每行记录一个标准输出或标准错误并用时间戳注释

修改配置

  1. vim /etc/docker/daemon.json

  2. 增加一条:{“log-driver”: “none”} (也可以添加{“log-opts”: {“max-size”: “10m” }} 来控制log文件的大小)

  3. 重新加载配置文件并重启docker服务: systemctl daemon-reload

docker-compose 配置

1
2
3
4
    logging: 
# driver: "json-file"
options:
max-size: "1g"

这样就不需要修改 daemon.json 配置文件了

查看日志位置

  1. docker inspect container_id | grep log
  2. 进入上述目录
  3. du -sh *

解决

在 idea 以前的版本里,在 Preferences | Build, Execution, Deployment | Gradle 去掉勾选 Offline work 即可

但是在最新版 2019.2 里,需要点击 gradle 面板里最上面一排小扳手左边一个图标,取消离线模式

  1. fork 模式下
  • 使用命令参数 pm2 start app.js --node-args="--harmony"
  • json 文件添加配置: "node_args" : "--harmony"
  1. cluster 模式下
    使用上一篇的方法 require("babel-register");
    在更改配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"apps": [
{
"name": "my_name",
"cwd": "./",
"script": "bin/start",
"instances" : "max",
"exec_mode" : "cluster",
"log_date_format": "YYYY-MM-DD HH:mm Z",
"error_file": "./logs/error.log",
"watch": ["routes"]
}
]
}

这里需要注意:

  1. exec_mode 要改为 cluster, instances 为实例数, max 为 CPU 的核心数,
  2. script 里配置的直接就是 js 文件,不需要加 node 命令(如 “script”: “node bin/start”) ,否则启动会报错,我踩过这个坑

  1. package.json 添加
1
2
3
4
5
6
7
8
9
10
"babel": {
"presets": [
"es2015"
]
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-es2015": "^6.24.1",
"babel-register": "^6.26.0"
}
  1. npm install

  2. 有 2 种方法可配置

  • 第一种: 启动命令改为: ./node_modules/.bin/babel-node app.js
  • 第二种: 在 app.js 头部里添加 require("babel-register");
0%