设计用户认证模块的最佳实践

设计用户认证模块的最佳实践

前言

转载自:Best Practices for Designing a User Authentication Module

摘录

设计身份验证模块的最佳实践将用户帐户和用户身份的概念分开。这种分离允许用户将多个身份验证因素链接到单个用户帐户并随意更改这些因素。

笔记

一、用户认证模块的表设计应该尽可能的将用户账户信息和用户登录的信息区分开来,将用户个人属性和用户认证用到的属性进一步原子化。
二、用户应该在系统中存在内部唯一的标识值(与用户登录信息无关),通过内部标识值去进行登录验证操作的关联,确保数据的一致性。
三、用户权限设计时,尽可能的将授权信息和用户信息表解耦,使用内部标识符进行关联,数据的准确性通过系统代码去控制。
四、应该尽量将各个模块根据实际需求、业务抽象成具体的表,将其按照功能模块建立模型关系或通过程序代码控制;不应该将大量数据属性聚合在一张表中,过于耦合的数据库结构,会降低系统的健壮性,不利于未来的拓展开发。

Demo数据库Sql

sqlserver事务日志增长过快应对策略

sqlserver事务日志增长过快应对策略

SQL Server事务日志处理方法

1. 将数据库恢复模式设置为简单模式

2. 定时收缩数据库日志文件

收缩数据库日志文件大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--需要注意的是在收缩日志文件时,数据库不能有正在活动的备份事务。

--将plat_lis数据库中的日志文件收缩到 1 MB。
USE testDB;
GO
-- 通过将数据库恢复模型更改为 SIMPLE 来截断日志。
ALTER DATABASE testDB
SET RECOVERY SIMPLE;
GO
-- Shrink the truncated log file to 1 MB.
DBCC SHRINKFILE (testDB_log, 1);
GO
-- 重置数据库恢复模式。
ALTER DATABASE testDB
SET RECOVERY FULL;
GO

3. 定时完全(full)备份数据库、截断事务日志

事务日志的截断与收缩

截断事务日志
事务日志会自动截断的操作:
1. 备份事务日志
2. 设置简单模式再设置回来
3. 使用backup log with no_log或 backup log with truncate_only
4. 从未对数据库进行过完全(full)备份
概要总结:所谓的截断(truncated)只是将可恢复状态的VLF转换到可重用状态

参考

事务日志的截断与收缩
完整数据库备份 (SQL Server)
SqlServer日志增长过快应对策略

引用截图:
文章截图
事务日志的截断与收缩

使用JMeter进行压力测试

使用JMeter进行压力测试

前言

Postman的Runner不支持并行调用,无法模拟出多用户并发的情况。所以使用JMeter进行压力测试和一些并发测试。

练习Demo

CASTest.jmx
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.5">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="CASTest" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="更新测试接口" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">70</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP请求" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="service" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">UpdateTotal</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">service</stringProp>
</elementProp>
<elementProp name="appid" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">1999042740418347</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">appid</stringProp>
</elementProp>
<elementProp name="data" elementType="HTTPArgument">
<boolProp name="HTTPArgument.always_encode">false</boolProp>
<stringProp name="Argument.value">{&quot;hoscode&quot;:&quot;${hoscode}&quot;,&quot;st&quot;:&quot;${st}&quot;,&quot;ed&quot;:&quot;${ed}&quot;}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
<boolProp name="HTTPArgument.use_equals">true</boolProp>
<stringProp name="Argument.name">data</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path">/API/PlatDict/UpdateTotal</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV 数据文件设置" enabled="true">
<stringProp name="delimiter">,</stringProp>
<stringProp name="fileEncoding">UTF-8</stringProp>
<stringProp name="filename">../workspace/JMterTest.csv</stringProp>
<boolProp name="ignoreFirstLine">true</boolProp>
<boolProp name="quotedData">false</boolProp>
<boolProp name="recycle">true</boolProp>
<stringProp name="shareMode">shareMode.group</stringProp>
<boolProp name="stopThread">false</boolProp>
<stringProp name="variableNames">st,ed,hoscode</stringProp>
</CSVDataSet>
<hashTree/>
</hashTree>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP请求默认值" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">127.0.0.1</stringProp>
<stringProp name="HTTPSampler.port">7957</stringProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.concurrentPool">6</stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</ConfigTestElement>
<hashTree/>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP信息头管理器" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Content-Type" elementType="Header">
<stringProp name="Header.name">Content-Type</stringProp>
<stringProp name="Header.value">application/x-www-form-urlencoded</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="响应断言" enabled="true">
<collectionProp name="Asserion.test_strings">
<stringProp name="49586">200</stringProp>
</collectionProp>
<stringProp name="Assertion.custom_message">test error</stringProp>
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
<boolProp name="Assertion.assume_success">false</boolProp>
<intProp name="Assertion.test_type">8</intProp>
</ResponseAssertion>
<hashTree/>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="查看结果树" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="StatVisualizer" testclass="ResultCollector" testname="聚合报告" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
JMetertest.csv
1
2
st,ed,hoscode
2022-07-01,2022-08-31,QJ00101

无UI启动

run.bat
1
G: cd Soft\apache-jmeter-5.5\bin jmeter -n -t G:\Soft\apache-jmeter-5.5\WorkSpace\CASTest.jmx -l G:\Soft\apache-jmeter-5.5\WorkSpace\result\result.txt -e -o G:\Soft\apache-jmeter-5.5\WorkSpace\webreport

参考

服务端性能测试-工具篇
使用 JMeter 进行压力测试
文章截图:
使用 JMeter 进行压力测试
截图2

使用WorkerServices和.NETCore3.1构建Windows服务

笔记

Adding the Quartz.NET hosted service
You need to do two things to register the Quartz.NET hosted service:

  • Register the Quartz.NET required services with the DI container
  • Register the hosted service

根据Quartz文档Microsoft DI Integration 中的描述,在微软的集成中,使用services.AddQuartzHostedService 将服务注册到 hosted service中 。根据源码可以看到是调用了services.AddSingleton方法,注册了一个单例生命周期的服务。

WorkerServices+Quartz+Windows服务 是否可实现?

  • 思路
    一开始以为services.AddQuartzHostedService 就已经是创建了一个服务,但是发现总是不对,问题在于没有添加 Host Service服务。而且AddQuartzHostedService 只是注册了一个单例生命周期的服务,并没有针对Windows服务做进一步的实现。没有实现OnStart 和OnStop 方法。所以一致报错。

    在后面添加上services.AddHostedService<Worker>(); 后,程序可以正常运行,本质上Quartz还是需要依托于一个正常运行的HostService 。而一个正常的Windows服务程序则必须是一个可以长时间运行的 HostService 。需要从BackgroundService派生并覆盖实现必要的部份方法

参考:

为将作为服务应用程序的一部分而存在的服务提供基类。 在创建新的服务类时,必须从 ServiceBase 类 派生。
在服务应用程序中定义服务类时从ServiceBase派生。任何有用的服务都会覆盖OnStart和OnStop方法。对于附加功能,您可以使用特定行为覆盖OnPause和OnContinue,以响应服务状态的变化。

DemoFile:

program.cs
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
using Quartz;
using WorkerServiceDemoNet6;

IHost host = Host.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseWindowsService(option =>
{
option.ServiceName = "NET6.0 Test Windows Service";
})
.ConfigureAppConfiguration((hostingContext, configuration) =>
{
configuration.Sources.Clear();

IHostEnvironment env = hostingContext.HostingEnvironment;

Console.WriteLine("ENVIRONMENT: " + env == null ? "" : env.EnvironmentName ?? "");

configuration
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile(@"appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
})
.ConfigureServices(services =>
{
services.AddQuartz(q =>
{
q.SchedulerId = "Scheduler-Core";

q.UseInMemoryStore();
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 10;
});
//Use a Scoped container to create jobs. I'll touch on this later
q.UseMicrosoftDependencyInjectionJobFactory();

//q.AddJobAndTrigger<TestJob>(hostContext.Configuration);

// Create a "key" for the job
var jobKey = new JobKey("TestJob");
// Register the job with the DI container
q.AddJob<TestJob>(opts => opts.WithIdentity(jobKey));
// Create a trigger for the job
q.AddTrigger(opts => opts
.ForJob(jobKey) // link to the HelloWorldJob
.WithIdentity("HelloWorldJob-trigger") // give the trigger a unique name
.WithCronSchedule("0/5 * * * * ?")); // run every 5 seconds

});

//Add the Quartz.NET hosted service
services.AddQuartzHostedService(
q =>
{
q.WaitForJobsToComplete = true;
q.AwaitApplicationStarted = true;
});
})
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
})
.Build();

await host.RunAsync();

转载:
Create a Windows Service using BackgroundService
Building a Windows service with Worker Services and .NET Core 3.1, part 1: Introduction

Using Quartz.NET with ASP.NET Core and worker services
.NET Core Workers as Windows Services

参考:

AddTransient, AddScoped and AddSingleton Services Differences

ASP.NET Core Service Lifetimes (Infographic)

nodejs单线程?非阻塞I/O?异步?

像java、python这个可以具有多线程的语言。多线程同步模式是这样的,将cpu分成几个线程,每个线程同步运行。

而node.js采用单线程异步非阻塞模式,也就是说每一个计算独占cpu,遇到I/O请求不阻塞后面的计算,当I/O完成后,以事件的方式通知,继续执行计算2。

JavaScript是单线程执行【CPU单核】,根本不能进行同步IO操作,所以,JavaScript的这一“缺陷”导致了它只能使用异步IO。

JavaScript 的确是单线程的
异步是浏览器的 JavaScript 引擎做的工作


问题二:nodejs既然是单线程,如何实现异步、非阻塞I/O?

顺便回答标题nodejs真的是单线程吗?其实只有javascript执行是单线程,I/O显然是其它线程。

js执行线程是单线程,把需要做的I/O交给libuv,自己马上返回做别的事情,然后libuv在指定的时刻回调就行了。其实简化的流程就是这样子的!

细化一点,nodejs会先从js代码通过node-bindings调用到C/C++代码,然后通过C/C++代码封装一个叫 “请求对象” 的东西交给libuv,这个请求对象里面无非就是需要执行的功能+回调之类的东西,给libuv执行以及执行完实现回调。

总结来说,一个异步 I/O 的大致流程如下:

发起 I/O 调用
用户通过 Javascript 代码调用 Node 核心模块,将参数和回调函数传入到核心模块;
Node 核心模块会将传入的参数和回调函数封装成一个请求对象;
将这个请求对象推入到 I/O 线程池等待执行;
Javascript 发起的异步调用结束,Javascript 线程继续执行后续操作。
执行回调
I/O 操作完成后,会取出之前封装在请求对象中的回调函数,执行这个回调函数,以完成 Javascript 回调的目的。(这里回调的细节下面讲解)

从这里,我们可以看到,我们其实对 Node.js 的单线程一直有个误会。

事实上,它的单线程指的是自身 Javascript 运行环境的单线程,Node.js 并没有给 Javascript 执行时创建新线程的能力,最终的实际操作,还是通过 Libuv 以及它的事件循环来执行的。

这也就是为什么 Javascript 一个单线程的语言,能在 Node.js 里面实现异步操作的原因,两者并不冲突。

问题五、nodejs擅长什么?不擅长什么?

Node.js 通过 libuv 来处理与操作系统的交互,并且因此具备了异步、非阻塞、事件驱动的能力。因此,NodeJS能响应大量的并发请求。所以,NodeJS适合运用在高并发、I/O密集、少量业务逻辑的场景

上面提到,如果是 I/O 任务,Node.js 就把任务交给线程池来异步处理,高效简单,因此 Node.js 适合处理I/O密集型任务。但不是所有的任务都是 I/O 密集型任务,当碰到CPU密集型任务时,即只用CPU计算的操作,比如要对数据加解密(node.bcrypt.js),数据压缩和解压(node-tar),这时 Node.js 就会亲自处理,一个一个的计算,前面的任务没有执行完,后面的任务就只能干等着 。

我们看如下代码:

1
2
3
4
5
6
7
8
9
10
var start = Date.now();//获取当前时间戳
setTimeout(function () {
console.log(Date.now() - start);
for (var i = 0; i < 1000000000; i++){//执行长循环
}
}, 1000);
setTimeout(function () {
console.log(Date.now() - start);
}, 2000);

最终我们的打印结果是:(结果可能因为你的机器而不同)

1
2
1000
3738

对于我们期望2秒后执行的setTimeout函数其实经过了3738毫秒之后才执行,换而言之,因为执行了一个很长的for循环,所以我们整个Node.js主线程被阻塞了,如果在我们处理100个用户请求中,其中第一个有需要这样大量的计算,那么其余99个就都会被延迟执行。如果操作系统本身就是单核,那也就算了,但现在大部分服务器都是多 CPU 或多核的,而 Node.js 只有一个 EventLoop,也就是只占用一个 CPU 内核,当 Node.js 被CPU 密集型任务占用,导致其他任务被阻塞时,却还有 CPU 内核处于闲置状态,造成资源浪费。

其实虽然Node.js可以处理数以千记的并发,但是一个Node.js进程在某一时刻其实只是在处理一个请求。

对于非阻塞的IO可以去看Unix网络编程里面定义的几种io模型。

因此,Node.js 并不适合 CPU 密集型任务

参考:

nodejs单线程?非阻塞I/O?异步?

前端模块化开发中webpack、npm、node、nodejs之间的关系

webpack

Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

webpack是一个工具,这个工具可以帮你处理好各个包/模块之间的依赖关系(modules with dependencies),并将这些复杂依赖关系的静态文件打包成一个或很少的静态文件,提供给浏览器访问使用;除此之外,webpack因为可以提高兼容性,可以将一些浏览器尚不支持的新特性转换为可以支持格式,进而减少由新特性带来的浏览器的兼容性问题

好,我们通过介绍,我们有个概念,webpack是一个打包工具,可以帮你把你的项目这里的项目其实就是指通过模块化开发的项目 打包为简洁版的浏览器可识别的静态资源

npm

介绍了webpack,我们可能会疑问,我的JS,CSS,HTML文件分开写,挺好的呀,为什么要使用webpack工具,进行复杂的各项配置。在传统前端开发模式下,我们确实是按照JS/CSS/HTML文件分开写的模式就可以,但是随着前端的发展,社区的壮大,各种前端的库和框架层出不穷,我们项目中可能会使用很多额外的库,如何有效管理这些引入的库文件是一个大问题,而且我们知道基于在HTML中使用<script>引入的方式,有两个弊端,一个是会重复引入,二是当库文件数量很多时管理成为一个大难题。
面对这样的局面,为了简化开发的复杂度,前端社区涌现了很多实践方法。模块化就是其中一项成功实践,而npm就是这样在社区 其实就是node社区中产生的

npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。
npm 由三个独立的部分组成:

  • 网站
  • 注册表(registry)
  • 命令行工具 (CLI)

网站 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。
注册表 是一个巨大的数据库,保存了每个包(package)的信息。
CLI 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。

这是npm的官方网站给出的介绍,一般来说提起npm有两个含义,一个是说npm官方网站,一个就是说npm包管理工具。npm社区或官网是一个巨大的Node生态系统,社区成员可以随意发布和安装npm生态中的包,也就是不用在重复造轮子了,别人造好了,你直接安装到你的项目中就可以使用,但是因为前面说了,当包引入数量很多时管理就成为了一个问题,这个就是npm为开发者行了方便之处,npm已经为你做好了依赖和版本的控制,也就是说使用npm可以让你从繁杂的依赖安装和版本冲突中解脱出来,进而关注你的业务而不是库的管理。

而webpack就是将你从npm中安装的包打包成更小的浏览器可读的静态资源,这里需要注意的是,webpack只是一个前端的打包工具,打包的是静态资源,和后台没有关系,虽然webpack依赖于node环境

what is node or nodejs?

其实node和nodejs两个概念没有太大差别,我个人认为唯一的区别就是,人们说起node的时候语境更多的是再说node环境,而说nodejs时更多的是在说node是一门可以提供后端能力的技术。本质上来说,node就是nodejs,nodejs就是node

简单的说 Node.js 就是运行在服务端的 JavaScript。
Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行>Javascript的速度非常快,性能非常好。

node环境基于V8引擎提供了一种可以让JS代码跑在后端的能力,这就是node。其实这里的node本身和我们这篇讲的前端模块化没啥关系。但是因为npm是产生与node社区,node中也是通过npm来加载模块的,所以有必要说一下他们之间的关系。

npm 是 Node.js 官方提供的包管理工具,他已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制

webpack npm node之间关系?

  • webpack是npm生态中的一个模块,我们可以通过全局安装webpack来使用webpack对项目进行打包;
  • webpack的运行依赖于node的环境,没有node是不能打包的,但是webpack打包后的项目本身只是前端静态资源和后台没有关系,也就是说不依赖与node,只要有后台能力的都可以部署项目
  • npm是于Node社区中产生的,是nodejs的官方包管理工具,当你下载安装好node的时候,npm cli就自动安装好了
  • 正是因为npm的包管理,使得项目可以模块化的开发,而模块化的开发带来的这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就是webpack工具存在的意义

参考:

前端模块化开发中webpack、npm、node、nodejs之间的关系

摘录:
Node.js介绍,摘自官网

Node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
Node.js is an open-source and cross-platform JavaScript runtime environment. It is a popular tool for almost any kind of project!

Node.js runs the V8 JavaScript engine, the core of Google Chrome, outside of the browser. This allows Node.js to be very performant.

A Node.js app runs in a single process, without creating a new thread for every request. Node.js provides a set of asynchronous I/O primitives in its standard library that prevent JavaScript code from blocking and generally, libraries in Node.js are written using non-blocking paradigms, making blocking behavior the exception rather than the norm.

When Node.js performs an I/O operation, like reading from the network, accessing a database or the filesystem, instead of blocking the thread and wasting CPU cycles waiting, Node.js will resume the operations when the response comes back.

This allows Node.js to handle thousands of concurrent connections with a single server without introducing the burden of managing thread concurrency, which could be a significant source of bugs.

Node.js has a unique advantage because millions of frontend developers that write JavaScript for the browser are now able to write the server-side code in addition to the client-side code without the need to learn a completely different language.

In Node.js the new ECMAScript standards can be used without problems, as you don't have to wait for all your users to update their browsers - you are in charge of deciding which ECMAScript version to use by changing the Node.js version, and you can also enable specific experimental features by running Node.js with flags.

Introduction to npm
npm is the standard package manager for Node.js.

In January 2017 over 350000 packages were reported being listed in >the npm registry, making it the biggest single language code >repository on Earth, and you can be sure there is a package for >(almost!) everything.

It started as a way to download and manage dependencies of Node.js >packages, but it has since become a tool used also in frontend >JavaScript.

There are many things that npm does.

Yarn and pnpm are alternatives to npm cli. You can check them out as >well.

PDI【Kettle】(一)

PDI【Kettle】(一)

Kettle中JavaScript内置函数说明

转换组件-脚本-JavaScript【Spoon】

我们在使用JavaScript组件的时候,在左侧核心树对象栏中可以看到Kettle为我们提供了很多简洁强大的内置函数,帮助我们在写脚本的时候对数据、参数变量等能很轻松的做处理,体验编码的感觉.本篇将详细介绍JavaScript组件中的函数功能

脚本组件包含的函数主要包括六大类,分别是:

  • 字符串类型的函数(String Functions)
  • 浮点型的函数(Numeric Functions)
  • 日期类型函数(Date Functions)
  • 逻辑判断型函数(Logic Functions)
  • 特殊的函数(Special Functions)
  • 文件处理类函数(File Functions)

字符串类型函数(String Functions)

顾名思义,字符串类型的函数肯定是针对字符串类型的参数、变量进行处理操作的函数

日期转字符串(date2str)

日期转字符串函数date2str主要有4个方法,分别是:

  • date2str(date):传入日期实例,转换成字符串类型
  • date2str(date,format):传入日期和格式化参数,进行格式化转换
  • date2str(date,format,iso):传入日期和参数格式化及ISO代码进行转换,(DE = German, EN = English, FR = France, …)
  • date2str(date,format,iso,zone):传入时区进行格式化,例如北京时区(GMT+8)

日期格式化参数format参数类型供参考:

1
2
3
4
5
6
7
yy / yyyy - 06 / 2006
MM / MMM / MMMMM - 11 / Nov / November
d / dd - 1 / 01
E / EEEE - Tue / Tuesday
hh / HH - 11 / 23
m / mm - 5 / 05
s / ss - 8 / 08

代码示例:

1
2
3
4
5
6
7
var dValue = new Date();
writeToLog(date2str(dValue,"dd.MM.yyyy"));
writeToLog(date2str(dValue,"dd.MM.yyyy HH:mm:ss"));
writeToLog(date2str(dValue,"E.MMM.yyyy","DE"));
writeToLog(date2str(dValue,"dd.MM.yyyy HH:mm:ss","EN"));
writeToLog(date2str(dValue,"dd.MM.yyyy HH:mm:ss","ZH", "GMT+8"));
writeToLog(date2str(dValue,"yyyy-MM-dd HH:mm:ss","ZH", "GMT+8"));

以上代码在控制台将会输出如下:

1
2
3
4
5
6
2019/08/19 10:12:56 - JavaScript代码.0 - 19.08.2019
2019/08/19 10:12:56 - JavaScript代码.0 - 19.08.2019 10:12:56
2019/08/19 10:12:56 - JavaScript代码.0 - Mo.Aug.2019
2019/08/19 10:12:56 - JavaScript代码.0 - 19.08.2019 10:12:56
2019/08/19 10:12:56 - JavaScript代码.0 - 19.08.2019 10:12:56
2019/08/19 10:12:56 - JavaScript代码.0 - 2019-08-19 10:12:56

转义HTMLescapeHtml(html)

代码如下:

1
2
3
var html="<h1>我是H2标题</h2>";

writeToLog(escapeHtml(html))

最终输出

1
2019/08/19 10:16:13 - JavaScript代码.0 - &lt;h1&gt;&#25105;&#26159;H2&#26631;&#39064;&lt;/h2&gt;

转义SQL(escapeSQL(var))

1
2
var str1 = "SELECT * FROM CUSTOMER WHERE NAME='" + escapeSQL("McHale's Navy") + "'";  
writeToLog(str1)

该函数会把单引号转成双引号,输出结果如下:

1
2019/08/19 10:18:59 - JavaScript代码.0 - SELECT * FROM CUSTOMER WHERE NAME='McHale''s Navy'

构造定长字符串(fillString(char,length))

代码示例如下:

1
2
writeToLog(fillString("x",10));
writeToLog(fillString("A",3));

最终会输出10个X和3个A,输出结果如下:

1
2
2019/08/19 10:24:08 - JavaScript代码.0 - xxxxxxxxxx
2019/08/19 10:24:08 - JavaScript代码.0 - AAA

需要注意的是第一个是一个char类型的单字符,不能是字符串

统计字符串出现频次(getOcuranceString(str,searchStr))

第一个参数是要搜索的完整字符串,第二个参数是要搜索统计的字符串

代码示例:

1
2
3
4
var sef='2007-09-11';

writeToLog(getOcuranceString(sef,'0'))
writeToLog(getOcuranceString(sef,'00'))

我们分别统计字符串0和00最终出现的次数,此时,日志最终打印的次数是3和1:

1
2
2019/08/19 10:28:45 - JavaScript代码.0 - 3
2019/08/19 10:28:45 - JavaScript代码.0 - 1

获取字符串下标索引(indexOf)

获取下标索引主要有2个重构函数,分别是:

  • indexOf(string,subString):获取出现字符串的索引开始位置
  • indexOf(string,subString,fromIndex);指定开始位置,获取字符串索引开始位置

代码示例:

1
2
3
4
5
6
var str1= "Hello Pentaho!";
var str2= indexOf(str1, "Pentaho");
var str3= indexOf(str1, "o", 7);
writeToLog("Input : " + str1);
writeToLog("Index of 'Pentaho' : " + str2);
writeToLog("index of 'o', search from position 7 : " + str3);

最终控制台输出:

1
2
3
2019/08/19 10:34:16 - JavaScript代码.0 - Input : Hello Pentaho!
2019/08/19 10:34:16 - JavaScript代码.0 - Index of 'Pentaho' : 6
2019/08/19 10:34:16 - JavaScript代码.0 - index of 'o', search from position 7 : 12

首字母大写(initCap)

对指定字符串首字母大写处理,来看代码示例:

1
2
3
4
var str1 = "my home";      
writeToLog(initCap(str1));
writeToLog(initCap('test a aaa cw'));
writeToLog(initCap('myhome'));

此时,最终控制台输出如下:

1
2
3
2019/08/19 10:41:27 - JavaScript代码.0 - My Home
2019/08/19 10:41:27 - JavaScript代码.0 - Test A Aaa Cw
2019/08/19 10:41:27 - JavaScript代码.0 - Myhome

字符串转小写(lower)

将传入字符串全部转小写

代码如下:

1
2
3
4
5
var str1= "Hello World!";
var str2= lower(str1);
writeToLog("Input:" + str1);
writeToLog("Converted to LowerCase:" + str2);
writeToLog(lower('DDDHelloSWxss'))

响应内容

1
2
3
2019/08/19 10:43:09 - JavaScript代码.0 - Input:Hello World!
2019/08/19 10:43:09 - JavaScript代码.0 - Converted to LowerCase:hello world!
2019/08/19 10:43:09 - JavaScript代码.0 - dddhelloswxss

字符串填充左侧(lpad(string,char,length))

用指定长度的给定字符将字符串填充到左侧

参数定义:

  • 1:传入字符串
  • 2:填充单字符
  • 3:填充单字符长度

如果length长度超过给定字符串的长度,将对填充字符串做减法,例如:

1
2
var str1= "Hello World!"; 
writeToLog("Lpad:" + lpad(str1, "x",20));

此时,最终输出结果为:

1
2019/08/19 10:46:38 - JavaScript代码.0 - Lpad:xxxxxxxxHello World!

最终的完成长度是20个字符长度,因此填充的单字符x并没有填充20次

如果length长度小于给定字符串的长度,则默认返回原字符串,不做填充,代码示例:

1
2
var str1= "Hello World!"; 
writeToLog("Lpad:" + lpad(str1, "x",5));

此时最终的输出结果为:

1
2019/08/19 10:46:38 - JavaScript代码.0 - Lpad:Hello World!

去空字符(ltrim)

从左侧开始去除空字符串

数字转字符串(num2str)

给定数字,转换为字符串,主要有3个构造函数:

  • num2str(num):转换num数字为字符串
  • num2str(num,format):格式化数字为指定字符串
  • num2str(num,format,iso):按照本地ISO编码进行格式化

代码示例如下:

1
2
3
4
5
6
var d1 = 123.40;
var d2 = -123.40;
writeToLog(num2str(d1));
writeToLog(num2str(d1, "0.00"));
writeToLog(num2str(d1, "0.00", "EN"));
writeToLog(num2str(d2, "0.00;(0.00)", "EN"));

最终控制台输出:

1
2
3
4
2019/08/19 11:00:17 - JavaScript代码.0 - 123.4
2019/08/19 11:00:17 - JavaScript代码.0 - 123.40
2019/08/19 11:00:17 - JavaScript代码.0 - 123.40
2019/08/19 11:00:17 - JavaScript代码.0 - (123.40)

XML保护标签函数转换(protectXMLCDATA)

传入给定字符串,添加标准保护,代码示例

1
2
var str1 = "my home";      
writeToLog(protectXMLCDATA(str1));

此时,将会给变量str1加上保护标签

1
2019/08/19 11:02:09 - JavaScript代码.0 - <![CDATA[my home]]>

移除字符串中CRLF字符(removeCRLF(str))

给定字符串中删除CR END LF的字符串

替换字符串(replace)

替换字符串主要包括两个构造函数:

  • replace(str,searchStr,replaceStr):从指定字符串中查询,然后替换
  • replace(str,firstSearch,firstReplace,secondSearch,SecondReplace...):无限查询替换

代码示例如下:

1
2
3
4
5
var str1 = "Hello World, this is a nice function";      
var str2 = replace(str1,"World", "Folk");
writeToLog(str2);
var str2 = replace(str1,"World", "Folk", "nice","beautifull");
writeToLog(str2);

最终输出:

1
2
2019/08/19 11:10:21 - JavaScript代码.0 - Hello Folk, this is a nice function
2019/08/19 11:10:21 - JavaScript代码.0 - Hello Folk, this is a beautifull function

字符串右侧填充(rpad(string,char,length))

使用方法同lpad,只是一个是左侧,一个是右侧

去除空字符(右侧)(rtrim)

正则切分(str2RegExp)

出入一个正则表达式,对string字符串进行Split操作.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12

var strToMatch = "info@proconis.de";
var strReg = "^(\\w+)@([a-zA-Z_]+?)\\.([a-zA-Z]{2,3})$";
var xArr = str2RegExp(strToMatch, strReg);
if ( xArr != null ) {
for(i=0;i<xArr.length;i++) {
writeToLog(xArr[i]);
}
}
else {
writeToLog("no match");
}

最终控制台输出:

1
2
3
2019/08/19 13:21:19 - JavaScript代码.0 - info
2019/08/19 13:21:19 - JavaScript代码.0 - proconis
2019/08/19 13:21:19 - JavaScript代码.0 - de

字符串截取(substr)

通过制定索引开始对字符串进行截取操作,主要有两个重构参数:

  • substr(string,from):指定from索引开始截取字符串
  • substr(string,from,to):指定开始和截止索引进行截取

代码示例:

1
2
3
4
5
6
var str1= "Hello Pentaho!";
var str2= substr(str1, 6);
var str3= substr(str1, 6, 7);
writeToLog("Input : " + str1);
writeToLog("From position 6: " + str2);
writeToLog("From position 6 for 7 long : " + str3);

控制台输出如下:

1
2
3
2019/08/19 13:31:20 - JavaScript代码.0 - Input : Hello Pentaho!
2019/08/19 13:31:20 - JavaScript代码.0 - From position 6: Pentaho!
2019/08/19 13:31:20 - JavaScript代码.0 - From position 6 for 7 long : Pentaho

去除左右空格(trim)

不转义HTML(unEscapeHtml(html))

针对以转义的HTML字符进行解密,代码如下:

1
2
3
4
5
6
7
var w='<h2>我是H2标题</h2>';

var esW=escapeHtml(w);
var unesw=unEscapeHtml(esW);

writeToLog(esW);
writeToLog(unesw);

最终控制台输出:

1
2
2019/08/19 13:38:16 - JavaScript代码.0 - &lt;h2&gt;&#25105;&#26159;H2&#26631;&#39064;&lt;/h2&gt;
2019/08/19 13:38:16 - JavaScript代码.0 - <h2>我是H2标题</h2>

解码转义XML(unEscapeXml )

字符串转大写(upper)

将传入字符串全部转大写.例如:

1
2
var str="Hello World";
writeToLog(upper(str));

浮点型的函数(Numeric Functions)

计算绝对值(abs(num))

计算一个数值的绝对值,代码示例:

1
2
3
4
var d1 = -1234.01;
var d2 = 1234.01;
writeToLog(abs(d1));
writeToLog(abs(d2));

最终控制台输出为:

1
2
2019/08/19 13:51:00 - JavaScript代码.0 - 1234.01
2019/08/19 13:51:00 - JavaScript代码.0 - 1234.01

最小双精度值(ceil(num))

返回最小的双精度值。该值将被四舍五入。代码示例:

1
2
3
4
var d1 = -1234.01;
var d2 = 1234.01;
writeToLog(ceil(d1));
writeToLog(ceil(d2));

最终控制台输出:

1
2
2019/08/19 13:52:40 - JavaScript代码.0 - -1234
2019/08/19 13:52:40 - JavaScript代码.0 - 1235

最大数值(floor(num))

返回最大数值,该值将被四舍五入,代码示例:

1
2
3
4
var d1 = -1234.01;
var d2 = 1234.01;
writeToLog(floor(d1));
writeToLog(floor(d2));

运行结果如下:

1
2
2019/08/19 13:55:13 - JavaScript代码.0 - -1235
2019/08/19 13:55:13 - JavaScript代码.0 - 1234

字符串转数值(str2num(var))

字符串转数值主要包含两个构造函数,分别是

  • str2num(str):传入数值字符串,进行格式化转换
  • str2num(str,format):通过指定格式进行数值转换

代码示例如下:

1
2
3
4
var str1 = "1.234,56";
var str2 = "12";
writeToLog((str2num(str1,"#,##0.00")));
writeToLog((str2num(str2)));

最终控制台输出:

1
2
2019/08/19 14:02:19 - JavaScript代码.0 - 1.234
2019/08/19 14:02:19 - JavaScript代码.0 - 12

截取数值(trunc)

1
trunc(1234.9); // 返回 1234

日期类型函数(Date Functions)

日期相加(dateAdd)

针对日期变量进行相应的添加时间,添加频率包括年、月、日、时、分、秒 等等

函数定义:dateAdd(date,format,plusNum)

  • date:日期对象
  • format:要加的类型
  • plusNum:加的数值

相加类型主要包括:

  • y:年
  • m:月
  • d:日
  • w:周
  • wd:工作日
  • hh:小时
  • mi:分钟
  • ss:秒

代码示例如下:

1
2
3
4
5
6
7
var d1 = new Date();

var fmt='yyyy-MM-dd HH:mm:ss';
writeToLog("当前时间:"+date2str(d1,fmt));
var py=dateAdd(d1,'y',1);
var fy=date2str(py,fmt);
writeToLog("加1年:"+fy);

最终控制台输出:

1
2
2019/08/19 14:17:41 - JavaScript代码.0 - 当前时间:2019-08-19 14:17:41
2019/08/19 14:17:41 - JavaScript代码.0 - 加1年:2020-08-19 14:17:41

日期比较(dateDiff)

两个日期相互比较

函数定义:dateDiff(startDate,endDate,type)

  • startDate:开始日期
  • endDate:截止日期
  • type:返回相差数值类型

类型主要包括:

  • y:年
  • m:月
  • d:日
  • w:周
  • wd:工作日
  • hh:小时
  • mi:分钟
  • ss:秒

获取指定日期数值(getDayNumber)

根据类型获取指定日期的数值

函数定义:getDayNumber(date,type)

  • date:当前日期实例
  • type:类别

类别主要分四类

  • y:获取当年的天数
  • m:获取当月的天数
  • w:获取本周的天数
  • wm:获取当月中本周的天数

代码示例:

1
2
3
4
5
var d1 = new Date();
writeToLog(getDayNumber(d1, "y"));
writeToLog(getDayNumber(d1, "m"));
writeToLog(getDayNumber(d1, "w"));
writeToLog(getDayNumber(d1, "wm"));

getFiscalDate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Returns the fiscal Date from the date value,
// based on a given offset.
//
// Usage:
// getFiscalDate(var);
// 1: Date - The Variable with the Date.
// 2: String - The Date/Month which represents
// the fiscal Start Offset. Format allways "dd.MM.".
//
// 2006-11-15
//
var d1 = new Date();
var str1 = "01.07.";
var str2 = "10.12.";
Alert(getFiscalDate(d1, str1));
Alert(getFiscalDate(d1, str2));

获取下一个工作日日期(getNextWorkingDay)

传入当前日期,获取该日期后面一个工作日日期

函数定义getNextWorkingDay(date)

代码示例如下:

1
2
3
4
5
6
7
8
9
10
var d1 = new Date();

// 周1
var d2=str2date('2019-08-19 16:36:00',fmt);
//周 6
var d3=str2date('2019-08-17 16:36:00',fmt);

writeToLog(date2str(getNextWorkingDay(d1),fmt));
writeToLog(date2str(getNextWorkingDay(d2),fmt));
writeToLog(date2str(getNextWorkingDay(d3),fmt));

我们这d2和d3变量中定义了不同的日期实例,分别是周1和周6,最终通过getNextWorkingDay能获取得到下一个工作日日期,控制台输出如下:

1
2
3
2019/08/19 16:37:38 - JavaScript代码.0 - 2019-08-20 16:37:38
2019/08/19 16:37:38 - JavaScript代码.0 - 2019-08-20 16:36:00
2019/08/19 16:37:38 - JavaScript代码.0 - 2019-08-19 16:36:00

获取当前月份数值(month(date))

获取当前日期的月份数值,需要注意的是,该值的月份是从0开始的,因此我们最终得到的结果应该+1才是我们的真实月份数值,代码示例:

1
2
var d1 = new Date();//2019/08/19
writeToLog(month(d1)); //最终输出为7

获取当前时间的季度值(quarter(date))

根据指定日期获取当前季度数值

1
2
var d1 = new Date();//2019/08/19
writeToLog(quarter(d1));//最终输出为3(代表第三季度)

字符串转日期(str2date)

字符串转日期和日期转字符串有点类似,只不过主体对换了一下,但是传入的格式参数都是一样的,主要有4个重载函数:

  • str2date(str):默认转换
  • str2date(str,format):传入format格式化参数
  • str2date(str,format,iso):根据iso编码及格式化参数进行转换
  • str2date(str,format,iso,timezone):根据不同时区的iso编码进行格式化转换

代码示例如下:

1
2
3
4
5
writeToLog(str2date("01.12.2006","dd.MM.yyyy"));
writeToLog(str2date("01.12.2006 23:23:01","dd.MM.yyyy HH:mm:ss"));
writeToLog(str2date("Tue.May.2006","E.MMM.yyyy","EN"));
writeToLog(str2date("22.02.2008 23:23:01","dd.MM.yyyy HH:mm:ss","DE"));
writeToLog(str2date("22.02.2008 23:23:01","dd.MM.yyyy HH:mm:ss","DE", "EST"));

截取日期(truncDate(date,type))

指定截取不同的日期部分,函数定义truncDate(date,type)

  • date:当前日期实例
  • type:截取类型

类型主要有6中,分别是整型,从0-5:

  • 5:截取月份
  • 4:截取天数
  • 3:截取小时
  • 2:截取分钟
  • 1:截取秒
  • 0:截取毫秒

代码示例 如下:

1
2
3
4
5
6
7
var dateTime = new Date();
var date0 = truncDate(dateTime, 0); // gives back today at yyyy/MM/dd HH:mm:ss.000
var date1 = truncDate(dateTime, 1); // gives back today at yyyy/MM/dd HH:mm:00.000
var date2 = truncDate(dateTime, 2); // gives back today at yyyy/MM/dd HH:00:00.000
var date3 = truncDate(dateTime, 3); // gives back today at yyyy/MM/dd 00:00:00.000
var date4 = truncDate(dateTime, 4); // gives back today at yyyy/MM/01 00:00:00.000
var date5 = truncDate(dateTime, 5); // gives back today at yyyy/01/01 00:00:00.000

获取当年的周数(week)

获取指定日期的周数,代码示例:

1
2
3
var d1 = new Date(); //2019/08/19

writeToLog(week(d1));// 返回34

获取年份(year)

获取传入日期的年份,代码示例:

1
2
3
var d1 = new Date(); //2019/08/19

writeToLog(year(d1));// 返回2019

逻辑判断型函数(Logic Functions)

isCodepage

判断字符串的codepage项,代码示例:

1
2
3
var xStr = "Réal";
writeToLog(isCodepage(xStr, "UTF-8"));// true
writeToLog(isCodepage(xStr, "windows-1250"));// true

是否日期(isDate(str))

判断当前字符串是否日期

1
2
3
4
var d1 = "Hello World";      
var d2 = new Date();
writeToLog(isDate(d1));//false
writeToLog(isDate(d2));//true

是否为空(isEmpty(str))

判断字符串是否为空

1
2
var d = "Hello World";      
Alert(isEmpty(d));//false

判断字符串是否为邮箱标准格式(isMailValid(str))

判断一个字符串是否是邮箱

判断是否是数值(isNum(str))

判断一个字符串是否是数值

1
2
3
4
var str1 = "Hello World";      
var str2 = 123456;
Alert(isNum(str1));//false
Alert(isNum(str2));//true

是否正则匹配(isRegExp)

判断给定的正则表达式是否匹配当前的字符串,主要有2个函数定义:

  • isRegExp(str,reg):给定正则判断字符串是否匹配
  • isRegExp(str,reg1,reg2,reg3…);可以递归判断正则匹配

最终返回的是匹配的次数数值,如果不匹配,返回-1

代码示例如下:

1
2
3
4
5
6
7
8
9
10
var email1 ="info@proconis.de";
var email2= "support@proconis.co.uk";
var email3= "HelloWorld@x";

var reg1="^\\w+@[a-zA-Z_]+?\\.[a-zA-Z]{2,3}$";
var reg2="^[\\w-\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";

writeToLog(isRegExp(email1, reg1,reg2) + " Matches"); //1
writeToLog(isRegExp(email2, reg1,reg2) + " Matches"); //2
writeToLog(isRegExp(email3, reg1,reg2) + " Matches");// 1

是否工作日(isWorkingDay(date))

判断某日期是否是工作日,代码示例:

1
2
3
4
var d1 = new Date();//周1
var d2=str2date('2019-08-17','yyyy-MM-dd') //周六
writeToLog(isWorkingDay(d1));//true
writeToLog(isWorkingDay(d2));//false

特殊的函数(Special Functions)

弹框信息(Alert(msg))

在屏幕前弹出一个信息框

加载JavaScript文件(LoadScriptFile)

将一个javascript文件加载到实际的运行上下文中。应该从定义的StartScript调用此函数,否则,每次处理都会加载javascript文件行。

代码示例如下:

1
2
var xPfad = "F:/bak/Hello.js";
LoadScriptFile(xPfad);

此时,我们的外部JS文件仅仅是包含一句简单的输出,如下:

1
writeToLog("Hello LoadScriptFile,outSide JS File ");

最终运行时,控制台会打印出我们在外部JS中的输出行

从当前Tab栏加载JS并运行(LoadScriptFromTab)

如果我们在当前的JavaScript组件中通过模块化的方式编写了很多脚本代码,我们可以通过LoadScriptFromTab函数进行相互调用,这对于开发抽象来说是很好的,代码示例如下:

1
2
writeToLog("外部Tab加载JS-------------------------")
LoadScriptFromTab('Item_1');

有效卡号判断(LuhnCheck)

如果给定的是一个有效的卡号,则返回true

1
2
3
4
5
6
var str1 = "4444333322221111";      
writeToLog(str1 + ": " + LuhnCheck(str1)); //true

var str2 = "4444333322221110";
writeToLog(str2 + ": " + LuhnCheck(str2));//false

向文件中追加数据(appendToFile)

向指定文件中追加数据,如果文件不存在则创建文件

1
2
3
4
5
6
7

var file = "F:/bak/log.txt";

for(var i=0;i<100;i++){
appendToFile(file,'TEST'+i+"\r\n");
}

此时,该代码会向log.txt文件输出100条数据行

decode函数

decode函数有点类似于IF-THEN-ELSE语句,即表示通过给定查询的字符串是否存在,如果存在,即替换,否则返回默认值

代码示例:

1
2
3
4
5
6
var str1 = "Hallo";
writeToLog(decode(str1, "Hallo", "Hello"));
writeToLog(decode(str1, "1", "Mr", "2", "Mrs", "N/A"));
writeToLog(decode(str1, "1", "Mr", "2", "Mrs"));
str1 = "Mrs";
writeToLog(decode(str1, "1", "Mr", "2", "Mrs"));

控制台输出:

1
2
3
4
2019/08/19 17:39:01 - JavaScript代码.0 - Hello
2019/08/19 17:39:01 - JavaScript代码.0 - N/A
2019/08/19 17:39:01 - JavaScript代码.0 - Hallo
2019/08/19 17:39:01 - JavaScript代码.0 - Mrs

执行命令(execProcess)

代码如下:

1
2
var t=execProcess('ping www.baidu.com');
writeToLog(t)

调用命令行,ping百度的网址,最终输出返回数据

执行SQL语句(fireToDB)

通过获取数据库连接名称,传递SQL语句,以返回SQL查询的值,函数定义:

  • fireToDB(connectionName,SQL);第一个参数为数据库连接名称,我们在JNDI中定义的名称,第二个参数为SQL语句
1
2
3
var strConn = "MY Connection";
var strSQL = "SELECT COUNT(*) FROM ...";
var xArr = fireToDB(strConn, strSQL);

仅仅获取数值(getDigitsOnly)

在给定的字符串中仅仅筛选过滤得到数值,代码如下:

1
2
var str1 = "abc123cde";      
writeToLog(getDigitsOnly(str1));//返回123

获取Kettle环境变量的值(getEnvironmentVar)

获取在Kettle中的环境变量的值

1
2
writeToLog(getEnvironmentVar("user.dir"));
writeToLog(getEnvironmentVar("user.name"));

获取当前进程的受影响行数(getProcessCount(type))

根据类型获取当前进程的受影响行数,类型如下:

  • u:更新行数
  • i:插入行数
  • w:写入行数
  • r:读取行数
  • o:输出行数
1
2
writeToLog(getProcessCount("u"));
writeToLog(getProcessCount("r"));

获取当前转换名称(getTransformationName)

获取当前的转换名称

1
2
var xTranName = getTransformationName();
writeToLog(xTranName);

获取Kettle环境中的变量值(getVariable)

从当前的Kettle环境中获取指定的变量值,目前函数有2个重载:

  • getVariable(varName);根据变量名称获取变量值
  • getVariable(varName,defaultValue):根据变量名获取值,如果不存在则使用默认值
1
2
3
4
5
6
7
8
var strVarName="getVariableTest";
var strVarValue="123456";
Alert(getVariable(strVarName, ""));
setVariable(strVarName,strVarValue, "r");
Alert(getVariable(strVarName, ""));
strVarValue="654321";
setVariable(strVarName,strVarValue, "r");
Alert(getVariable(strVarName, ""));

控制台打印(println)

1
2
3
var str = "Hello World!";
print(str);

移除数值(removeDigits)

移除给定字符串中的数值,代码示例:

1
2
3
var str1 = "abc123cde"; 

writeToLog(removeDigits(str1));//返回abccde

发送邮件

设置环境变量(setEnvironmentVar)

通过在Script脚本组件中调用函数重新设置Kettle的环境变量

1
2
3
4
5
6
7
8
var strVarName="setEnvTest";
var strVarValue="123456";
Alert(getEnvironmentVar(strVarName));
setEnvironmentVar(strVarName,strVarValue);
Alert(getEnvironmentVar(strVarName));
strVarValue="654321";
setEnvironmentVar(strVarName,strVarValue);
Alert(getEnvironmentVar(strVarName));

设置变量(setVariable)

通过setVariable函数设置环境变量,该用途可以用于重新赋值Kettle环境中已经存在的变量值或者重新生成变量值

函数定义setVariable(key,value,level)

  • key:变量名称
  • value:变量值
  • level:级别,主要包括s(system)、r(root)、p(parent)、g(grandparent)四种类别

代码示例如下:

1
2
3
4
5
6
7
8
var strVarName="setEnvTest";
var strVarValue="123456";
Alert(getVariable(strVarName, ""));
setVariable(strVarName,strVarValue, "r");
Alert(getVariable(strVarName, ""));
strVarValue="654321";
setVariable(strVarName,strVarValue, "r");
Alert(getVariable(strVarName, ""));

写入日志(writeToLog)

打印并写入日志信息,该函数可能是我们用到的最多的函数,可以帮助我们调试信息,主要有两个重载:

  • writeToLog(msg):写入msg日志信息
  • writeToLog(level,msg):根据level基本写入msg信息

关于日志的级别,这里主要是简写的方式,主要如下:

  • d(Debug):调试模式
  • l(Detailed):详细
  • e(Error):错误
  • m(Minimal):最小日志
  • r(RowLevel):行级日志
1
2
writeToLog("Hello World!");
writeToLog("r", "Hello World!");

文件处理类函数(File Functions)

复制文件(copyFile)

复制一个文件到目标目录,函数定义如下:

copyFile(sourceFile,targetFile,overwrite)

  • sourceFile:源文件
  • targetFile:目标文件
  • overWrite:是否覆盖,如果目标文件存在的话,布尔类型
1
2
3
4
5
var file1 = "F:/bak/log.txt";

var targetFile="F:/bak/logTarget.txt";

copyFile(file1,targetFile,false)

创建文件夹(createFolder)

创建一个文件夹,代码示例如下:

1
2
var strFolder = "F:/bak/createFolder";
createFolder(strFolder);

删除文件(deleteFile)

删除一个文件(不能删除文件夹)

1
2
3
var targetFile="F:/bak/logTarget.txt";

deleteFile(targetFile);

判断文件是否存在(fileExists())

判断文件是否存在

1
2
3
var targetFile="F:/bak/logTarget.txt";

fileExists(targetFile);

获取文件扩展名(getFileExtension)

如果文件不存在,则返回null,代码示例

1
2
3
4
var file1 = "F:/bak/log.txt";

var ext=getFileExtension(file1);
writeToLog("扩展名:"+ext)

获取文件大小(getFileSize)

获取文件大小,结果是一个long类型的长整型数值

1
2
3
4
var file1 = "F:/bak/log.txt";

var ext=getFileSize(file1);
writeToLog("大小:"+ext)

获取文件最后修改日期(getLastModifiedTime)

获取文件最后修改日期,函数定义:

getLastModifiedTime(filePath,format)

  • filePath:文件路径
  • format:日期格式化
1
2
3
var file1 = "F:/bak/log.txt";

var ext=getLastModifiedTime(file1,"yyyy-MM-dd HH:mm:ss");

获取文件的父文件夹名称(getParentFoldername)

获取文件的父文件夹名称

1
2
var file1 = "F:/bak/log.txt";
var parentFolder=getParentFoldername(file1);

获取文件简称(getShortName)

获取文件简称

1
2
3
4
5

var file1 = "F:/bak/log.txt";
var shortName=getShortFilename(file1);

writeToLog("简单名称:"+shortName)//返回log.txt

判断是否是一个文件(isFile)

判断是否是一个文件

1
2
var file1 = "F:/bak/log.txt";
var flag=isFile(file1) //true

判断是否是一个文件夹(isFolder)

判断是否是一个文件夹

1
2
var file1 = "F:/bak/log.txt";
var flag=isFolder(file1) //false

加载一个文件的内容(loadFileContent)

从指定文件中加载内容,主要有两个重载函数:

  • loadFileContent(filePath):默认加载文件
  • loadFileContent(filePath,encoding):指定编码加载文件内容

代码示例:

1
2
3
4
var file1 = "F:/bak/log.txt";
var content=loadFileContent(file1);
var c1=loadFileContent(file1,"UTF-8")
writeToLog(content)

移动文件(moveFile)

移动指定文件,函数定义moveFile(source,target,overWrite)

  • source:源文件
  • target:目标文件
  • overWrite;是否覆盖,如果目标文件存在,布尔类型值
1
2
3
4
5
var file1 = "F:/bak/log.txt";
var targetFile="F:/bak/logTarget.txt";

moveFile(file1,targetFile,false)

创建一个空文件(touch)

创建一个空文件

1
2
var strFile = "F:/bak/log.txt";
touch(strFile);

总结

以上就是Kettle 8.3版本中的内置函数方法,方法很多,写这篇博客也是很累,算是全部都学习了一遍,脑子里已经记忆了一遍,但是我们也不需要死记硬背,就和我们学些Linux命令一样,如果你知道man命令,对某个命令不是很了解的话直接通过man命令学习即可.

Kettle也是如此,对于某个函数不是很了解的话,右键点击该函数,会出现sample字样菜单,点击该菜单即可弹出该函数的介绍和使用信息,里面包含了该函数的调用示例和函数详细介绍,也是很人性化的.

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×