Gowhich

Durban's Blog

Nestjs中为了连接数据库,提供了@nestjs/typeorm包

在使用之前如果没有安装的话,需要安装下,安装命令如下

1
npm install --save @nestjs/typeorm typeorm mysql

这里使用MySQL,当然TypeORM也是支持其他关系型数据库的,如PostgreSQL,Oracle,Microsoft SQL Server,还有NoSQL数据库如MongoDB

如果TypeORM安装成功之后,就可以导入TypeOrmModule了

1
import { TypeOrmModule } from '@nestjs/typeorm';

如何配置MySQL的参数(比如连接的数据库地址、用户名、密码、数据库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: '127.0.0.1',
port: 3306,
username: 'root',
password: '123456',
database: 'test',
entities: [],
synchronize: true,
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

forRoot方法提供了所有的配置参数给TypeORM包的createConnection方法,除此之外,还有其他的一些配置参数的属性如下

  • retryAttempts:默认10,尝试连接数据库的次数
  • retryDelay:默认3000,尝试连接数据库延迟时间
  • autoLoadEntities:默认false,如果为true,entities将会被自动加载
  • keepConnectionAlive:默认false,如果为true,连接在应用被关闭时不会关闭

具体更多的连接配置参数,点击这里查看

数据库配置连接正常之后,就可以正常使用了

app.module.ts完整代码如下

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
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Connection } from 'typeorm';

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: '127.0.0.1',
port: 3306,
username: 'root',
password: '123456',
database: 'test',
entities: [],
synchronize: true,
logging: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private connection: Connection) {}
}

如何连接数据库进行查询和添加

创建Entity

cats/cats.entities.ts代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity({ name: 'cats' })
export class Cats {
@PrimaryGeneratedColumn()
id: number;

@Column({ name: 'first_name' })
firstName: string;

@Column({ name: 'last_name' })
lastName: string;

@Column({ name: 'is_active', default: true })
isActive: boolean;
}

创建Service

cats/cats.service.ts代码如下

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
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cats } from './cats.entity';

@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cats)
private catsRepository: Repository<Cats>,
) {}

findAll(): Promise<Cats[]> {
return this.catsRepository.find();
}

findOne(id: number): Promise<Cats> {
return this.catsRepository.findOne(id);
}

async remove(id: number): Promise<void> {
await this.catsRepository.delete(id);
}

async create(cats: Cats): Promise<void> {
await this.catsRepository.save(cats);
}
}

创建Controller

cats/cats.controller.ts

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
import { Body, Controller, Get, Post, Res } from '@nestjs/common';
import { Response } from 'express';
import { Cats } from './cats.entity';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}

@Get('/index')
index(@Res() res: Response): string {
this.catsService.findAll();

var cats: Promise<Cats[]> = this.catsService.findAll();

cats
.then((data) => {
return res.render('cats/index', {
message: 'Cats',
data: data,
});
})
.catch((error) => {
console.log(error);
});

return '';
}

@Post('/create')
async create(@Body() catsParam: { firstName: string; lastName: string }) {
let cats = new Cats();
cats.firstName = catsParam.firstName;
cats.lastName = catsParam.lastName;
cats.isActive = true;
return await this.catsService.create(cats);
}
}

创建Module

cats/cats.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsController } from './cats.controller';
import { Cats } from './cats.entity';
import { CatsService } from './cats.service';

@Module({
imports: [TypeOrmModule.forFeature([Cats])],
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}

修改AppModule

app.module.ts

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
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CatsModule } from './cats/cats.module';
import { Connection } from 'typeorm';
import { Cats } from './cats/cats.entity';

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: '127.0.0.1',
port: 3306,
username: 'root',
password: '123456',
database: 'test',
entities: [Cats],
synchronize: true,
logging: true,
}),
CatsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private connection: Connection) {}
}

运行项目

1
npm run start:dev

启动后如果表不存在的话会自动创建表cats

正常启动没有问题之后,先创建一个数据

1
curl -d 'firstName=cats&lastName=1' 'http://localhost:3000/cats/create'

然后访问http://localhost:3000/cats/index

会看到创建的数据输出

try 第一种使用方式 try do catch

1
2
3
4
5
6
7
8
let data: Data = Data()

do {
let responseJSON = try JSONSerialization.jsonObject(with: data, options: []) as! [[String: Any]]
print(responseJSON)
} catch {
print("something is wrong here. Try connecting to the wifi.")
}

try 第二种使用方式 try?

1
2
3
4
5
6
7
8
9
let data: Data = Data()

let responseJSON = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]

if responseJSON != nil {
print("Yeah, We have just unwrapped responseJSON!")
} else {
print(responseJSON ?? "")
}

try 第三种使用方式 guard try?

1
2
3
4
5
6
7
8
9
let data: Data = Data()

func getResponseJSON() {
guard let responseJSON = try? (JSONSerialization.jsonObject(with: data, options: []) as! [[String: Any]]) else {
return
}

print(responseJSON)
}

try 第四种使用方式 try!

1
2
3
4
let data: Data = Data()

let responseJSON = try! JSONSerialization.jsonObject(with: data, options: []) as! [[String: Any]]
print(responseJSON)

总结

try 需要与 do…catch配合使用,这种方式可以使用更详细的错误处理方法

try? 忽略我们的错误,如果碰巧发生,会将它们设置为nil

try! 打开您的代码,并保证我们的函数永远不会遇到错误。在我们的函数确实抛出错误的情况下,我们的应用只会崩溃,这种方式用的时候一定要小心

如果您使用阿里云云服务器 ECS 时,若出现服务的速度变慢,或 ECS 实例突然断开,可以考虑服务器带宽和 CPU 是否有跑满或跑高的问题。Linux 系统下,您可以按如下步骤进行排查:

定位问题。找到影响带宽和 CPU 跑满或跑高的具体进程。

分析处理。排查影响带宽和 CPU 跑满或跑高的进程是否正常,并分类进行处理。

对于 正常进程:您需要对程序进行优化或者升级服务器配置。

对于 异常进程:您可以手动对进程进行查杀,也可以使用第三方安全工具去查杀。

当然,若您预先创建报警任务,当带宽和 CPU 跑满或跑高时,系统将自动进行报警提醒。如果云服务器 ECS Linux 系统的 CPU 持续跑高,则会对系统稳定性和业务运行造成影响。本文相关配置及说明已在 CentOS 6.5 64 位操作系统中进行过测试。其它类型及版本操作系统配置可能有所差异。

CPU 跑满或跑高的问题定位

若云服务器 ECS 的 CPU 持续跑高,会对系统的稳定性和业务运行造成影响。Linux 系统下,查看进程的常用命令如下:

1
ps -aux ps -ef top

Linux 系统中,通常使用 top 命令来查看系统的负载问题,并定位耗用较多 CPU 资源的进程。 通过 top 命令查看系统当前的运行情况。 针对负载问题,您只需关注回显的第一行和第三行信息,详细说明如下。

top 命令的第一行显示的内容 17:27:13 up 27 days, 3:13, 1 user, load average: 0.02, 0.03, 0.05 依次为 系统当前时间 、系统到目前为止已运行的时间、当前登录系统的用户数量、系统负载, 这与直接执行 uptime 命令查询结果一致。

top 命令的第三行会显示当前 CPU 资源的总体使用情况,下方会显示各个进程的资源占用情况。

1
%Cpu(s): 0.3 us, 0.1 sy, 0.0 ni, 99.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.1 st

通过字母键 P,可以对 CPU 使用率进行倒序排列,进而定位系统中占用 CPU 较高的进程。

说明:通过字母键 M, 您可以对系统内存使用情况进行排序。如果有多核 CPU,数字键 1 可以显示每核 CPU 的负载状况。

通过ll /proc/PID/exe可以查看每个进程 ID 对应的程序文件。

CPU 跑满或跑高的分析处理

CPU 的跑满或跑高,在确认具体的进程结果后,针对异常的进程,您需要通过 top 命令将其终止;而对于 kswapd0 进程导致的内存不足等问题,您需要对系统进行规格的升级或程序的优化。

使用 top 直接终止 CPU 消耗较大的进程

您可以直接在 top 运行界面快速终止相应的异常进程。操作步骤如下: 若您想要终止某个进程,只需按下小写的 k 键。 输入想要终止的进程 PID (top 输出结果的第一列)。例如,若您想要终止 PID 为 86 的进程,输入 86 后按回车即可。 操作成功后,界面会出现类似 Send pid 86 signal [15/sigterm] 的提示信息。按回车确认即可。

kswapd0 进程占用导致 CPU 较高

操作系统都用分页机制来管理物理内存,系统会把一部分硬盘空间虚拟成内存使用。由于内存的速度要比磁盘快得多,所以系统要按照某种换页机制将不需要的页面换到磁盘中,将需要的页面调到内存中。

kswapd0 是虚拟内存管理中负责换页的进程,当服务器内存不足的时候 kswapd0 会执行换页操作,这个换页操作是十分消耗主机 CPU 资源的。操作步骤如下:

通过 top 命令查看 kswapd0 进程。

检查该进程是否持续处于非睡眠状态,且运行时间较长。若是,可以初步判定系统在持续地进行换页操作,kswapd0 进程占用了系统大量 CPU 资源。

您可以通过 free 、ps 等指令进一步查询系统及系统内进程的内存占用情况,做进一步排查分析。 针对系统当前内存不足的问题,您可以重启 Apache,释放内存。 说明:从长远的角度来看,您需要对内存进行升级。

带宽跑满或跑高的分析处理

对于正常进程导致的带宽跑满或跑高的问题,需要对服务器的带宽进行升级。对于异常进程,有可能是由于恶意程序问题,或者是部分 IP 恶意访问导致,也可能是服务遭到了 CC 攻击。 通常情况下,您可以使用 iftop 工具或 nethogs 查看流量的占用情况,进而定位到具体的进程。

使用 iftop 工具排查

在服务器内部安装 iftop 流量监控工具。

1
yum install iftop -y

服务器外网带宽被占满时,如果通过远程无法登陆,可通过阿里云终端管理进入到服务器内部,运行下面命令查看流量占用情况:

注意:-P 参数将会显示请求端口。执行 iftop -i eth0 -P 命令,可以查看通过服务器哪个端口建立的连接,以及内网流量。举例如下:

1
iftop -i eth1 -P

在上图中,您可以查看到流量高耗的是服务器上 53139 端口和 115.205.150.235 地址建立的连接。

执行 netstat 命令反查 53139 端口对应的进程。

1
netstat -tunlp |grep 53139

经反查,服务器上 vsftpd 服务产生大量流量,您可以通过停止服务或使用 iptables 服务来对指定地址进行处理,如屏蔽 IP 地址或限速,以保证服务器带宽能够正常使用。

使用 nethogs 进行排查

在服务器内部安装 nethogs 流量监控工具。

通过 nethogs 工具来查看网卡上进程级的流量信息,若未安装可以通过 yum、apt-get 等方式安装。举例如下:

1
yum install nethogs -y

若 eth1 网卡跑满,执行命令 nethogs eth1。 查看每个进程的网络带宽情况以及进程对应的 PID 确定导致带宽跑满或跑高的具体进程。

若进程确定是恶意程序,可以通过执行 kill -TERM 来终止程序。

说明: 如果是 Web 服务程序,您可以使用 iftop 等工具来查询具体 IP 来源,然后分析 Web 访问日志是否为正常流量。日志分析可以使用 logwatch 或 awstats 等工具进行。

使用 Web 应用防火墙防御 CC 攻击

若您的服务遭受了 CC 攻击,请在 Web 应用防火墙控制台尽快开启 CC 安全防护。

登录 Web应用防火墙 控制台。

在 CC 安全防护中,启动状态按钮,并在模式中选择 正常。

MongoDB Sort : how to fix maximum RAM exceeded error

针对这个问题具体的报错信息如下

Error: error: {
“ok” : 0,
“errmsg” : “Executor error during find command :: caused by :: errmsg: "Sort operation used more than the maximum 33554432 bytes of RAM. Add an index, or specify a smaller limit."“,
“code” : 96,
“codeName” : “OperationFailed”
}

解决办法:

1、加索引(这个是比较常用的方式)

2、增加RAM大小

1
db.adminCommand({setParameter: 1, internalQueryExecMaxBlockingSortBytes: 335544320})

具体的大小可以根据具体的情况来设置

查看RAM大小的话,使用下面的命令

1
db.runCommand( { getParameter : 1, "internalQueryExecMaxBlockingSortBytes" : 1 } )

3、使用aggregate 命令

如果用aggregate的话,通过设置选项allowDiskUse的值为true,允许临时将数据写到文件中,具体使用参考如下

1
2
3
4
5
db.getCollection('users').aggregate( [
{ $sort : { age : 1} }
],
{ allowDiskUse: true }
)

如果在排序的时候想要得到一个好的性能,最好是创建索引

1
db.users.createIndex( { age: 1 } )

参考文章点击这里

nginx部署服务器

遇到点小问题

第一个域名解析:想把某个域名解析到指定服务器,*.xxx.com -> xxx.xxx.xxx.xxx,好处是,省去了在同一台服务器按需添加域名了,想使用啥二级域名直接配置

第二个不想未配置的域名访问服务器:配置了a.xxx.com,但是不想b.xxx.com也能访问,按照nginx的server_name寻找配置规则,访问b.xxx.com会访问到a.xxx.com

第一个问题是个好问题,这样做没问题

第二个问题的解决办法其实也很简单

如果是80端口的话

1
2
3
4
5
server {
listen 80 default_server;
server_name _;
return 500;
}

如果是443端口的话

1
2
3
4
5
6
7
8
server {
listen 443 default_server;
server_name _;
ssl on;
ssl_certificate cert/x.xxx.xxx.pem;
ssl_certificate_key cert/x.xxx.xxxkey;
return 500;
}

这里强调下443端口配置的时候一定要配置证书,不然需要使用https的域名会无法访问

同时注意下nginx的版本,不同nginx的版本针对ssl的配置也会有不同的语法

给mongodb添加查询日志输出方法非常简单,可以像下面这样添加

1
2
DB::connection('mongodb')->enableQueryLog();
DB::connection('mongodb')->getQueryLog();

mongodb”的配置是在database.php中配置的

1
2
3
4
5
'mongodb' => [
'driver' => 'mongodb',
'dsn' => env('MONGODB_DSN'),
'database' => env('MONGODB_DATABASE'),
],

为什么要加这段代码之后才会有日志输出呢

原因是因为默认的情况下,laravel只针对默认的数据库配置进行日志输出

可以尝试打开config/database.php文件,找到类似下面这行

1
'default' => env('DB_CONNECTION', 'mysql'),

默认情况下执行的是下面的代码

1
2
DB::enableQueryLog();
DB::getQueryLog();

既然是默认获取的也是默认的数据库配置查询

参考:参考1

准备一个textarea(基于vuejs)

1
<textarea v-model='videoUrl' id='copy' style="position:absolute;top:-99999999px"></textarea>

实现代码逻辑如下

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
function copy() {
if (navigator.userAgent.match(/(iPhone|iPod|iPad);?/i)) { //区分iPhone设备
console.log('ios')

window.getSelection().removeAllRanges(); //这段代码必须放在前面否则无效

var copyNode = document.getElementById("copy"); //要复制文字的节点
var editable = copyNode.contentEditable;
var readOnly = copyNode.readOnly;
copyNode.contentEditable = true;
copyNode.readOnly = true;

var range = document.createRange();
// 选中需要复制的节点
range.selectNodeContents(copyNode);

// 执行选中元素
var selection = window.getSelection();
if (selection.rangeCount > 0) {
selection.removeAllRanges();
}

selection.addRange(range);
copyNode.setSelectionRange(0, 999999);
copyNode.contentEditable = editable;
copyNode.readOnly = readOnly;

// 执行 copy 操作
var successful = document.execCommand('copy');

console.log('successful = ', successful);
// alert(successful)
if (successful) {
layer.open({
content: '复制成功^_^',
btn: ['好的'],
yes: function (index) {
layer.closeAll();
}
})
}
// 移除选中的元素
window.getSelection().removeAllRanges();
} else {
var text = document.getElementById("copy").value;
const textarea = document.createElement("textarea");
textarea.style.position = 'fixed';
textarea.style.top = 0;
textarea.style.left = 0;
textarea.style.border = 'none';
textarea.style.outline = 'none';
textarea.style.resize = 'none';
textarea.style.fontSize = '12pt';
textarea.style.background = 'transparent';
textarea.style.color = 'transparent';
textarea.value = text; // 修改文本框的内容
document.body.appendChild(textarea);
textarea.select() // 选中文本
try {
const msg = document.execCommand('copy') ?
'successful' : 'unsuccessful';
// alert(msg)
if (msg == 'successful') {
layer.open({
content: '复制成功^_^',
btn: ['好的'],
yes: function (index) {
layer.closeAll();
}
})
}
} catch (err) { alert('unable to copy', err) }
document.body.removeChild(textarea)
}

}

知识点鸡肋 - /bin/bash^M: bad interpreter: No such file or directory

执行一个脚本start.sh 时, 一直是提示我:

1
-bash: ./start.sh: /bin/bash^M: bad interpreter: No such file or directory

出现上面错误的原因:

脚本文件是DOS格式的, 即每一行的行尾以\r\n来标识, 使用vim编辑器打开脚本, 运行:

1
:set ff?

可以看到DOS或UNIX的字样. 使用set ff=unixset fileformat=unix把它强制为unix格式的, 然后存盘(:wq)退出, 即可。

filter、reduce (swift 5.3)的使用

filter

过滤,可以对数组中的元素按照某种规则进行一次过滤。

1
2
3
let numbers = [1, 3, 5, 7, 9]
let filterNumbers = numbers.filter { $0 < 5 }
print(filterNumbers)

输出结果如下

1
[1, 3]

reduce

计算,可以对数组的元素进行计算

1
2
3
4
5
6
let animals1 = ["Dog", "Cat", "Pig"]
let string = animals1.reduce("Dog", {
// $0: result, $1: 数组的值
return $0 == "Cat" ? $1 : $0 + "," + $1
})
print(string)

输出的结果如下

1
Dog,Dog,Cat,Pig

flatMap/compactMap (swift 5.3)的使用

- 不返回nil

flatMap/compactMap处理返回后的数组不存在nil,同时它会把Optional解包

看下map和flatMap/compactMap实现方式如下

1
2
3
4
5
6
7
8
9
10
11
let colors = ["red", "yellow", "green", ""]
let colorsOfMap = colors.map { item -> Int? in
let length = item.count
guard length > 0 else {
return nil
}

return length
}

print(colorsOfMap)

结果是

1
[Optional(3), Optional(6), Optional(5), nil]

flatMap/compactMap实现方式如下

  • flatMap
1
2
3
4
5
6
7
8
9
10
let colorsOfFlatMap = colors.flatMap { item ->Int? in
let length = item.count
guard length > 0 else {
return nil
}

return length
}

print(colorsOfFlatMap)
  • compactMap
1
2
3
4
5
6
7
8
9
10
let colorsOfFlatMap = colors.compactMap { item ->Int? in
let length = item.count
guard length > 0 else {
return nil
}

return length
}

print(colorsOfFlatMap)

这里之所以用compactMap,是因为'flatMap' is deprecated Please use compactMap(_:)

结果是

1
[3, 6, 5]

- 打开数组

compactMap能把(二维、N维)数组一同打开变成一个新的数组

1
2
3
4
5
6
7
8
let array = [[1,2,3],[4,5,6],[7,8,9]]

// 对比
let arr1 = array.map { $0 }
print(arr1)

let arr2 = array.flatMap { $0 }
print(arr2)

结果分别是

1
2
3
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

[1, 2, 3, 4, 5, 6, 7, 8, 9]

- 合并数组

compactMap能把不同的数组合并为一个数组,合并后的数组的个数是要合并两个数组个数的乘积

1
2
3
4
5
6
7
8
9
10
let animals = ["cat", "dog", "pig"]
let counts = [1,2,3]

let newArray = counts.flatMap { count in
animals.map({ animal in
return animal + "\(count)"
})
}

print(newArray)

结果是

1
["cat1", "dog1", "pig1", "cat2", "dog2", "pig2", "cat3", "dog3", "pig3"]
0%