普通视图

发现新文章,点击刷新页面。
昨天以前Gowhich

x509: certificate signed by unknown authority 这个问题也许没有你想的那么复杂

作者 Durban
2025年3月24日 14:11

x509: certificate signed by unknown authority 这个问题也许没有你想的那么复杂

起初是遇到了个问题

我就更新了个cdn的证书

结果golang的http库无法正常请求接口,php的file_get_contents也无法正常请求接口

报错信息

golang的http库反馈的是 x509: certificate signed by unknown authority

php的file_get_contents 反馈的是 error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed on line 1

经过多种的猜测,发现执行完这个命令后一切都恢复了正常

yum update ca-certificates -y

VirtualBox 7.0.10 kernel 6.5.x on ubuntu 22.04

作者 Durban
2024年1月22日 14:19

今天启动Virtualbox后启动不了报错,提示我运行

/sbin/vboxconfig

得到的结果提示如下

Kernel driver not installed (rc=-1908)

The VirtualBox Linux kernel driver is either not loaded or not set up correctly. Please try setting it up again by executing

'/sbin/vboxconfig'

as root.

If your system has EFI Secure Boot enabled you may also need to sign the kernel modules (vboxdrv, vboxnetflt, vboxnetadp, vboxpci) before you can load them. Please see your Linux system's documentation for more information.

where: suplibOsInit what: 3 VERR_VM_DRIVER_NOT_INSTALLED (-1908) - The support driver is not installed. On linux, open returned ENOENT. 

社区的解决方案https://forums.virtualbox.org/viewtopic.php?t=110128

$ sudo add-apt-repository ppa:ubuntu-toolchain-r/ppa -y
$ sudo apt update
$ sudo apt install g++-12 gcc-12

我执行上面的步骤没有问题后,重新运行还是不行

于是又执行了

/sbin/vboxconfig

mariadb使用记录

作者 Durban
2023年3月31日 13:51

前提条件

安装的版本

mariadb-5.5.68-linux-systemd-x86_64.tar.gz

由于最新的mysql不再适合我的古老项目,也没有升级,服务器上部署的是mysql5.7,本机器是ubuntu 22.04,apt安装的话的是mysql 8.0,降低版本安装暂时没有找到好的办法
而且想试试mariadb于是就搞了一个,其实把这种东西看作是软件就好了,毕竟这个报下载下来我就执行能运行了,不用再安装其他的依赖了。

安装解压后按照文件夹中INSTALL-BINARY进行安装使用就好了

配置文件修改

以前没注意,安装我看了下/etc/mysql目录,居然有这么多好东西

$ ll /etc/mysql     
总计 32K
drwxr-xr-x 2 root root 4.0K 10月 19 13:34 conf.d
-rw------- 1 root root  317  3月 14 09:35 debian.cnf
-rwxr-xr-x 1 root root  120  1月 28 22:44 debian-start
-rw-r--r-- 1 root root 1.1K  3月 15 18:15 mariadb.cnf
drwxr-xr-x 2 root root 4.0K  6月 17  2022 mariadb.conf.d
lrwxrwxrwx 1 root root   24 10月 19 13:34 my.cnf -> /etc/alternatives/my.cnf
-rw-r--r-- 1 root root  839 10月 20  2020 my.cnf.fallback
-rw-r--r-- 1 root root  682  3月 11  2021 mysql.cnf
drwxr-xr-x 2 root root 4.0K  3月 14 09:35 mysql.conf.d

然后我就改了mariadb.cnf

port = 3307
socket = /run/mysqld/mysqld.sock

主要是这两项,端口3307是为了跟mysql 8.0启动的端口做个区分


启动方式

cd /usr/local/mysql
sudo ./support-files/mysql.server start

启动后

mysql     120523       1  0 14:39 ?        00:00:05 /usr/sbin/mysqld
root      121333       1  0 14:42 ?        00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/usr/local/mysql/data --pid-file=/usr/local/mysql/data/durban-workspace.pid
mysql     121464  121333  0 14:42 ?        00:00:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/usr/local/mysql/data/durban-workspace.err --pid-file=/usr/local/mysql/data/durban-workspace.pid --socket=/run/mysqld/mysqld.sock --port=3307

奇怪的是如何用起来 mariadb.cnf 这个配置文件的 我看了./support-files/mysql.server 其实是使用的/etc/mysql/my.cnf

原来是软链接过去的

my.cnf -> /etc/alternatives/my.cnf
/etc/alternatives/my.cnf -> /etc/mysql/mariadb.cnf

这个操作没搞懂,不过不记得了,应该是在安装mariadb的时候操作的,具体细节忘记留意了

laravel^8.0使用laravel-excel^3.1方法记录

作者 Durban
2023年3月28日 14:29

laravel-excel的安装直接看官网

官网地址 https://docs.laravel-excel.com/

使用场景记录:

导出一个表格,指定表头,指定对应的数据

需求很简单,不过使用新版本还是有点不知如何下手,不过仔细看了文档还是找到了办法,所以文档是一个好东西

说说以前是如何使用的

首先数据源的获取,这个是一致的,需要什么数据不管新版本旧版本都一样的

$data = [[1,2,3,4],[1,2,3,4]];

之前的版本"maatwebsite/excel": "^2.1"

Excel::create('conversion', function($excel) use ($data) {
    $excel->sheet('信息', function($sheet) use ($data) {
        $sheet->rows($data);
        $sheet->prependRow(['header1','header2','header3','header4']);
    });
})->export('xlsx');

新版本"maatwebsite/excel": "^3.1"就比较麻烦了

配置数据源

use Maatwebsite\Excel\Facades\Excel;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Illuminate\Support\Collection;
class DataExport implements FromCollection, WithHeadings
{
    public function collection()
    {
        $data = [[1,2,3,4],[1,2,3,4]];
        return new Collection($data);
    }

    public function headings(): array
    {
        return [
            'header1',
            'header2',
            'header3',
            'header4',
        ];
    }
}

导出实现

Excel::store(new DataExport(), "invoices.xlsx");

看起来麻烦,不过梳理了之后感觉还是很方便的,应该算是增加了耦合度

 

 

composer更新时openssl证书异常处理

作者 Durban
2023年2月9日 15:39

composer install 或者composer update 时出现下面错误

OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed

说明需要ssl 证书没有配置或者配置的有问题

首先去下载证书

wget http://curl.haxx.se/ca/cacert.pem

然后将证书配置到指定目录

mv cacert.pem /usr/local/openssl/cert.pem

之后在修改php.ini文件中openssl的配置

openssl.cafile=/usr/local/openssl/cert.pem

 

Ubuntu下配置swap,增加程序运行速度

作者 Durban
2023年2月9日 15:05

前提是我用的是ubuntu

Image

通过增加swap的大小,就能提升程序运行速度,对于程序开发的我,还是被耍了一下,我居然不知道

最近运行浏览器google chrome,无意间打开了多个tab,然后就发现电脑卡住了

无意间最近观察了下进程发现swap在卡住的时候是占用满了的,然后还是2G我发现不对,于是想着这玩意以前接触过,应该可以扩容于是搞起来

sudo fallocate -l 8G /swapfile8 # 创建命令
sudo mkswap /swapfile8 # 格式化命令
sudo swapon /swapfile8 # 挂载swap

如果swap在使用中,需要先进行卸载swap操作

sudo swapoff /swapfile # 卸载swap

注意我是卸载的/swapfile不是/swapfile8

Curl模拟Get、Post、Put、Delete请求

作者 Durban
2021年8月17日 09:39

GET

curl -v www.xxx.com/xxx/xxx/xx

POST

curl -v www.aaa.com/xxx/xxxx -d 'xx=14&xxx=xxx'
curl -v -X POST www.aaa.com/xxxx -d 'xx=14&xxx=ddd'

PUT

curl -v -X PUT -d "xx=19&xx=C" www.xx.com/ss/ss

DELETE

curl -v -X DELETE www.xx.com/xx/sss

如何添加HEADER

curl -v -H 'ApiKey:xxx' -H 'Sign:xxx' -H 'RequestTime:xxx' -H 'Content-Type:application/json' -H 'User-Agent:PostmanRuntime/7.26.10' -H 'Accept:*/*' -H 'Accept-Encoding:gzip, deflate, br' -H 'Connection:keep-alive' -X POST www.xx.com/test/xxxx -d '{"xxx":"xxx","dd":"1"}'

 

Nestjs中的async/await的使用

作者 Durban
2021年1月15日 10:55

前景

最近在试驾Nestjs,感觉还不错,但是知识点满多的,嗯,暂时还没放弃

在试驾的过程中,遇到一个获取数据逻辑,现在的逻辑这样的,函数如下

@Get('/index')
index(@Res() res: Response) {
  var cats: Promise<Cats[]> = this.catsService.findAll();

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

写完之后,发现then...catch,让我想起来,我用Reactjs开发的时候,曾经把这玩意换成了async/await于是,函数修改成如下

@Get('/index')
async index(@Res() res: Response) {
  var cats: Promise<Cats[]> = this.catsService.findAll();

  var data = await cats;

  return res.render('cats/index', {
    message: 'Cats",
    data: data,
  })
}

该写完之后,又让我想起来,我在用Nodejs写接口的时候,需要对await cats的执行做下异常捕获,不然访问的时候直接崩溃,对终端不太友好,于是该写如下

@Get('/index')
async index(@Res() res: Response) {
  var cats: Promise<Cats[]> = this.catsService.findAll();

  try {
    const data = await cats;

    return res.render('cats/index', {
      message: 'Cats",
      data: data,
    })
  } catch (err) {
    // 处理异常逻辑
    console.log(err);
  }
}

建议:使用async/await的情况下,如果遇到异常会导致接口异常,为了能够正常处理逻辑,可以使用try...catch

如何查看Linux进程运行的完整路径

作者 Durban
2021年1月14日 10:13

日常工作中查看进程的命令有ps和top,但是只能查看到相对路径,如果想看到详细的信息,如绝对路径等是比较困难的

不过可以通过如下的方法来查看进程的详细信息

 

Linux在启动一个进程的时候,系统会在/proc目录下面创建一个以PID命名的文件夹

比如以nginx为例

$ ps -ef | grep nginx
root      3882  3865  0  2020 pts/0    00:00:00 nginx: master process /usr/bin/openresty -g daemon off;
nobody    3969  3882  0  2020 pts/0    00:00:03 nginx: worker process
nobody    3970  3882  0  2020 pts/0    00:00:05 nginx: worker process
nobody    3971  3882  0  2020 pts/0    00:00:10 nginx: worker process
nobody    3972  3882  0  2020 pts/0    00:00:57 nginx: worker process

 

我们拿PID 3882查看下

$ ls /proc/3882
attr       cgroup      comm             cwd      fd       io        map_files  mountinfo   net        oom_adj        pagemap      root       sessionid  stack  status   timers
autogroup  clear_refs  coredump_filter  environ  fdinfo   limits    maps       mounts      ns         oom_score      personality  sched      setgroups  stat   syscall  uid_map
auxv       cmdline     cpuset           exe      gid_map  loginuid  mem        mountstats  numa_maps  oom_score_adj  projid_map   schedstat  smaps      statm  task     wchan

其中包括一个名为exe的文件,这个文件即记录了绝对路径,通过llls -l命令即可查看。

里面的文件的含义,如下

  • cwd 符号链接的是进程运行目录;
  • exe 符号连接就是执行程序的绝对路径;
  • cmdline 就是程序运行时输入的命令行命令;
  • environ 记录了进程运行时的环境变量;
  • fd 目录下是进程打开或使用的文件的符号连接。

其他的请自行搜索

新手入门Nest.js(十五)- 连接数据库

作者 Durban
2021年1月4日 10:55

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

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

npm install --save @nestjs/typeorm typeorm mysql

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

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

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

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

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完整代码如下

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代码如下

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代码如下

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

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

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

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) {}
}

运行项目

npm run start:dev

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

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

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

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

会看到创建的数据输出


 

Linux服务器CPU跑满或抛高及带宽跑满怎样排查分析原因

作者 Durban
2020年12月16日 18:20

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

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

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

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

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

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

CPU 跑满或跑高的问题定位

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

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 资源的总体使用情况,下方会显示各个进程的资源占用情况。

%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 流量监控工具。

yum install iftop -y

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

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

iftop -i eth1 -P

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

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

netstat -tunlp |grep 53139

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

使用 nethogs 进行排查

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

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

yum install nethogs -y

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

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

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

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

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

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

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

Laravel中如何给mongodb查询添加日志输出

作者 Durban
2020年11月23日 15:38

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

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

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

'mongodb' => [
    'driver' => 'mongodb',
    'dsn' => env('MONGODB_DSN'),
    'database' => env('MONGODB_DATABASE'),
],

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

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

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

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

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

DB::enableQueryLog();
DB::getQueryLog();

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

 

参考:参考1

webapp实现点击按钮copy(复制)内容的功能

作者 Durban
2020年11月20日 17:21

准备一个textarea(基于vuejs)

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

实现代码逻辑如下

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

作者 Durban
2020年11月16日 15:23

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

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

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

出现上面错误的原因:

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

:set ff?

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

Swift集合类型高阶函数(三)

作者 Durban
2020年11月11日 22:14

filter、reduce (swift 5.3)的使用

filter

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

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

输出结果如下

[1, 3]

reduce

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

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

输出的结果如下

Dog,Dog,Cat,Pig

 

Swift集合类型高阶函数(二)

作者 Durban
2020年11月10日 10:33

flatMap/compactMap (swift 5.3)的使用

- 不返回nil

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

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

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)

结果是

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

flatMap/compactMap实现方式如下

- flatMap

let colorsOfFlatMap = colors.flatMap { item ->Int? in
    let length = item.count
    guard length > 0 else {
        return nil
    }

    return length
}

print(colorsOfFlatMap)

- compactMap

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(_:)

结果是

[3, 6, 5]

- 打开数组

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

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], [4, 5, 6], [7, 8, 9]]

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

- 合并数组

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

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)

结果是

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

 

Swift集合类型高阶函数(一)

作者 Durban
2020年11月8日 23:06

Map(swift 5.3)的使用

对集合类型中的每一个元素做一次处理,转换为新数组

数组系列

- 案例1 - 遍历每个元素

let colors = ["red", "yellow", "green", "blue"]
let counts = colors.map { (color: String) -> Int in
    return color.count
}

print(counts)

结果是 [3,6,5,4]

- 案例2 - 更加简单的方法

let counts1 = colors.map { $0.count }
print(counts1)

结果也是 [3,6,5,4]

- 案例3 - 转换为对象数组(请问下转换为对象数组干啥用)

class Color {
    var name: String
    init(name: String) {
        self.name = name
    }
}

let colorsObj = colors.map { return Color(name: $0) }

for obj in colorsObj {
    print(obj.name)
}

结果是

red
yellow
green
blue

集合系列

let ColorsSet: Set = ["red", "yellow", "green", "blue"]
let colorsCount = ColorsSet.map { $0.count }
print(colorsCount)

结果是[3, 6, 4, 5]

字典系列

let dict = [2: "red", 4: "yellow", 6: "green", 8: "blue"]
let keys = dict.map { $0.key }
print(keys)
let values = dict.map { $0.value }
print(values)

结果分别是

[2, 8, 6, 4]

["red", "blue", "green", "yellow"]

iOS与JavaScript交互: MessageHandler

作者 Durban
2020年11月7日 23:30

WKWebView有一个内容交互控制器,该对象提供了通过JS向WKWebView发送消息的途径。需要设置MessageHandler,我把这个功能简称为MessageHandler。

首先了解下iOS开发中需要调用的方法

- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

- (void)removeScriptMessageHandlerForName:(NSString *)name;

下面看下如何调用

添加MessageHandler

[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"showAlert"];

移除MessageHandler

[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"showAlert"];

JS调用

window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

这个name就是设置MessageHandler的第二个参数

window.webkit.messageHandlers.getString.postMessage(null);

注意点

在JS中写起来简单,不用再用创建URL的方式那么麻烦了。

JS传递参数更方便。使用拦截URL的方式传递参数,只能把参数拼接在后面,如果遇到要传递的参数中有特殊字符,如&、=、?等,必须得转换,否则参数解析肯定会出错。

示例演示

html代码部分

四个按钮分别演示JS传NULL给WKWebView,然后WKWebView调用JS方法;JS传字符串、JS传数组、JS传字典改变导航背景。

<!DOCTYPE html>
<html lang="zh-cn">

<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script>
  function loadURL(url) {
    var iFrame;
    iFrame = document.createElement("iframe");
    iFrame.setAttribute("src", url);
    iFrame.setAttribute("style", "display:none;");
    iFrame.setAttribute("height", "0px");
    iFrame.setAttribute("width", "0px");
    iFrame.setAttribute("frameborder", "0");
    document.body.appendChild(iFrame);
    // 发起请求后这个iFrame就没用了,所以把它从dom上移除掉
    iFrame.parentNode.removeChild(iFrame);
    iFrame = null;
  }

  function asyncAlert(content) {
    setTimeout(function(){
      alert(content);
    },1);
  }
  
  function showAlert() {
    window.webkit.messageHandlers.showAlert.postMessage(null);
  }
  
  function alertWithMessage(content) {
    asyncAlert(content);
    document.getElementById("returnValue").value = content;
  }
  
  function postString() {
    window.webkit.messageHandlers.postString.
    postMessage('r=10,g=170,b=250,a=0.5');
  }

  function postArray() {
    window.webkit.messageHandlers.postArray.
    postMessage([Math.floor(Math.random()*255),
           Math.floor(Math.random()*255),
           Math.floor(Math.random()*255),0.5]);
  }

  function postDictionary() {
    window.webkit.messageHandlers.postDictionary.
    postMessage({red: Math.floor(Math.random()*255), 
           green: Math.floor(Math.random()*255),
           blue: Math.floor(Math.random()*255),
           alpha: 0.5});
  }

  </script>
</head>

<body>
  <input type="button" value="OC调用JS方法" onclick="showAlert()">
  <input type="button" value="JS传字符串" onclick="postString()">
  <input type="button" value="JS传数组" onclick="postArray()">
  <input type="button" value="JS传字典" onclick="postDictionary()">
</body>

</html>

object-c代码部分

- 加载WKWebView

- (void)loadWebView {
    // 偏好配置
    WKWebViewConfiguration *config = [WKWebViewConfiguration new];
    config.preferences = [WKPreferences new];
    config.preferences.minimumFontSize = 30.0f;
    config.preferences.javaScriptCanOpenWindowsAutomatically = YES;
    
    self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
    self.webView.UIDelegate = self; // 设置交互代理
    [self.view addSubview:self.webView];
    
    // 加载HTML
    NSString *path = [[NSBundle mainBundle] pathForResource:@"index2" ofType:@"html"];
    NSString *htmlStr = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    [self.webView loadHTMLString:htmlStr baseURL:nil];
}

- 添加和移除处理

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"showAlert"];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"postString"];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"postArray"];
    [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"postDictionary"];
}

- (void)dealloc {
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"showAlert"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"postString"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"postArray"];
    [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"postDictionary"];
}

- 实现协议

我这里实现了两个协议<WKUIDelegate,WKScriptMessageHandler,WKUIDelegate是因为我在JS中弹出了alert。WKScriptMessageHandler是因为我们要处理JS调用OC方法的请求。

#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"showAlert"]) {
        [self alert];
    }
    else if ([message.name isEqualToString:@"postString"]) {
        [self changeColorWithString:message.body];
    }
    else if ([message.name isEqualToString:@"postArray"]) {
        [self changeColorWithArray:message.body];
    }
    else if ([message.name isEqualToString:@"postDictionary"]) {
        [self changeColorWithDictionary:message.body];
    }
}

- (void)alert {
    // OC调用JS
    NSString *jsStr = [NSString stringWithFormat:@"alertWithMessage('%@')", @"OC调用JS的方法"];
    [self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable result, NSError * _Nullable error) {
        NSLog(@"%@----%@",result, error);
    }];
}

- (void)changeColorWithString:(NSString *)string {
    NSArray *params =[string componentsSeparatedByString:@","];
    
    NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
    for (NSString *paramStr in params) {
        NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
        if (dicArray.count > 1) {
            NSString *decodeValue = [dicArray[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
            [tempDic setObject:decodeValue forKey:dicArray[0]];
        }
    }
    CGFloat r = [[tempDic objectForKey:@"r"] floatValue];
    CGFloat g = [[tempDic objectForKey:@"g"] floatValue];
    CGFloat b = [[tempDic objectForKey:@"b"] floatValue];
    CGFloat a = [[tempDic objectForKey:@"a"] floatValue];
    
    self.navigationController.navigationBar.backgroundColor = [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a];
}

- (void)changeColorWithArray:(NSArray *)array {
    CGFloat r = [array[0] floatValue]/255.0;
    CGFloat g = [array[1] floatValue]/255.0;
    CGFloat b = [array[2] floatValue]/255.0;
    CGFloat alpha = [array[3] floatValue];
    self.navigationController.navigationBar.backgroundColor = [UIColor colorWithRed:r green:g blue:b alpha:alpha];
}

- (void)changeColorWithDictionary:(NSDictionary *)dict {
    CGFloat r = [dict[@"red"] floatValue]/255.0;
    CGFloat g = [dict[@"green"] floatValue]/255.0;
    CGFloat b = [dict[@"blue"] floatValue]/255.0;
    CGFloat alpha = [dict[@"alpha"] floatValue];
    self.navigationController.navigationBar.backgroundColor = [UIColor colorWithRed:r green:g blue:b alpha:alpha];
}

- WKWebView中使用弹窗

#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController *alertCrontroller = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alertCrontroller addAction:[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    [self presentViewController:alertCrontroller animated:YES completion:nil];
}

文章参考:https://www.jianshu.com/p/160f529e16fa

如何使用Property Wrappers - State和Binding

作者 Durban
2020年11月6日 16:47

State Property Wrappers 的用法

示例如下

struct MyView: View {
    @State var myString: String = "Hello"
    var body: some View {
        OtherView(shareText: $myString)
    }
}

Binding Property Wrappers 的用法

示例如下

struct OtherView: View {
    @Binding var shareText: String
    
    var body: some View {
        Text(shareText)
    }
}

最后调用下MyView,如下(建议Playground中运行)

MyView(myString: "Hello world")

 

❌
❌