宝塔面板配置MongoDB

当前版本

1
2
3
4
5
6
{
宝塔面板: '7.5.2',
Ubuntu: '18.04.4 LTS',
MongoDB: '4.0.10'
}

本文仅适用于上述版本,其他版本中配置过程可能存在差别

本文参考 宝塔面板 mongodb设置账号,密码,数据库,并配置远程连接 并结合实际体验编写

本文不附带图片,所有过程均以文字说明为主。

安装MongoDB

登录宝塔面板,点击软件商店,找到MongoDB,点击安装,稍等片刻即可完成。

配置MongoDB

这是安装完成后默认的配置文件内容,注意 authorization 要为 disabled

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
## content
systemLog:
destination: file
logAppend: true
path: /www/server/mongodb/log/config.log

# Where and how to store data.
storage:
dbPath: /www/server/mongodb/data
directoryPerDB: true

journal:
enabled: true
# how the process runs
processManagement:
fork: true
pidFilePath: /www/server/mongodb/log/configsvr.pid

# network interfaces
net:
port: 27017
bindIp: 127.0.0.1

#operationProfiling:
#replication:
# replSetName: bt_main
security:
authorization: disabled
javascriptEnabled: false

#sharding:
# clusterRole: shardsvr

连接MongoDB及账号配置

创建完成时,数据库中应该会包含 admin 数据库如果没有,可以新建一个。

打开SSH终端,进入MongoDB目录,默认安装在 /www/server/mongodb/bin

1
> cd /www/server/mongodb/bin

进入mongoDB环境,并切换到admin数据库

1
2
3
4
5
6
> mongo

# 查看当前所有数据库
> show databases

> use admin

查看当前数据库中有哪些用户,当没有用户时不会显示任何信息

1
> show users

创建一个管理员账户

1
> db.createUser( { user: 'admin', pwd: 'password', roles:[ { role: 'userAdminAnyDatabase', db: 'admin' } ] } )

这条命令的意思是:在admin数据库中创建用户admin,并给该用户admin数据库上的userAdminAnyDatabase角色。

admin用户用于管理账号,不能进行关闭数据库等操作。

相关文档:createUser - 创建一个数据库新用户

执行命令会看到类似的信息

1
2
3
4
5
6
7
8
9
Successfully added user: {
"user": "admin",
"roles": [
{
"role": "userAdminAnyDatabase",
"db": "admin"
}
]
}

创建完成后再次执行 show users 命令,会显示刚才创建的用户信息。

接下来创建root账号

1
> db.createUser({ user: 'root', pwd: 'password', roles: [ { role: 'root', db: 'admin' } ] })

测试账号登录

1
> db.auth('user', 'pwd')

成功返回1,失败返回0

创建数据库以及账号

使用use语法创建数据库,数据库不存在时会自动创建一个新的数据库,数据库存在时会切换到指定数据库。

1
> use [database_name]

创建当前数据库管理角色

1
> db.createUser( { user: "user",pwd: "password",roles: [ { role: "dbOwner", db: "yourdatabase" } ] } )

role: 'dbOwner' 代表数据库所有者角色,它拥有当前数据库的最高权限。注意,创建该角色时请先切换到刚才创建的数据库中。

创建完成后使用 show users 查看用户。

修改配置文件,授权远程登录

1
2
3
4
5
authorization: disabled

# 更改为

authorization: enabled

需要注意,要设置放行端口,否则本地将无法连接到数据库。

Mongodb 的 BindIP 需要设置为 0.0.0.0,表示不限制ip。

当前部分常用命令

1
2
3
4
5
6
7
8
# 查看当前所有数据库
> show databases
> show dbs
# 进入指定数据库
> use [database]
# 查看所有用户
> show users

本地连接MongoDB

这里我尝试了Navicat和MongoDB Compass

其中Navicat连接时需要使用新建的一个数据库,和该数据库的用户才可以登录,使用admin账号登陆后查看新建的数据库提示未授权。

而MongoDB Compass则可以使用admin账号登录,与Navicat相反,我使用指定账户登录数据库时提示失败。

这方面可能是软件差异或是配置问题,但现阶段就不用先纠结这些了。

以下内容仅作简单说明,具体请查看MongoDB文档。

MongoDB内置角色有如下:

  1. 数据库用户角色:read、readWrite;

  2. 数据库管理角色:dbAdmin、dbOwner、userAdmin;

  3. 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;

  4. 备份恢复角色:backup、restore;

  5. 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase

  6. 超级用户角色:root
    // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)

  7. 内部角色:__system

JSON.parse解析双引号嵌套单引号格式的数据

示例:

1
const str = "['123', '456', '789']"

当存在这种数据时 JSON.parse 无法直接解析,会提示 SyntaxError: Unexpected token ' in JSON at position 1 的错误。

解决方案:

  • eval() eval语法可以简单方便的解决 JSON.parse 无法解析的问题,但这个语法不推荐使用。它会将传入的字符串当做 JavaScript 代码进行执行。这有可能会导致被运行恶意代码,而且它必须调用JS解释器会比其他替代方法更慢。
  • str.replace(/'/g, '"') 推荐使用 replace() 将单引号替换为双引号,然后再使用 JSON.parse

示例:

1
2
3
4
5
6
7
const str = "['123', '456', '789']"

console.log(JSON.parse(str.replace(/'/g, '"')))

// 输出

[ '123', '456', '789' ]

MaterialUI封装全局Snackbar组件

最近在写点小项目,使用的是Material UI,这个UI库中没有类似Ant里可以全局使用的message组件,只有Snackbar消息条组件,但这种使用方式显然不够灵活,本来想试试自己造个轮子,但在翻看Material UI的文档时发现Snackbar组件文档最后有一个notistack方案来以更灵活的方式使用Snackbar组件。

本文章中使用依赖的信息

1
2
3
4
5
6
7
{
"notistack": "^1.0.6",
"@material-ui/core": "^4.11.3",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-scripts": "4.0.0"
}

相关文档

Material-UI Snackbar组件文档

notistack文档

开始使用

官方文档对于使用的说明很详细,这里就简单写写。

如果希望直接查看在线代码,请直接跳转至文末本篇代码

使用 notistack 时必须在外部包裹 SnackbarProvider 组件。

如果使用了Material UI 的 ThemeProvider 组件,则 SnackbarProvider 组件必须位于 ThemeProvider 组件内部。

1
2
3
4
5
import { SnackbarProvider } from 'notistack';

<SnackbarProvider>
<MyApp />
</SnackbarProvider>

SnackbarProvider 组件会传入两个方法:enqueueSnackbarcloseSnackbar

enqueueSnackbar 用于添加一个消息到队列中。官方文档说明

closeSnackbar 用于关闭消息。官方文档说明

官方Api文档

在React Class Component中使用

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
import React from "react";
import { withSnackbar, ProviderContext } from "notistack";
import { Button } from "@material-ui/core";

interface ClassDemoPropsType extends ProviderContext {}

class ClassDemo extends React.Component<ClassDemoPropsType> {
constructor(props: ClassDemoPropsType) {
super(props);
}
handleButtonClick = () => {
const { enqueueSnackbar } = this.props
// 使用enqueueSnackbar创建一条消息
enqueueSnackbar(
`类组件,点击按钮`,
{
variant: 'success'
});
};

render() {
return (
<Button
variant="contained"
color="primary"
onClick={this.handleButtonClick}
>
类组件中触发
</Button>
);
}
}

// 注入snackbar
export default withSnackbar(ClassDemo);

在React Function Component中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Button } from "@material-ui/core";
import { useSnackbar } from "notistack";

const FunctionDemo = () => {
const { enqueueSnackbar } = useSnackbar();

const buttonClick = () => {
enqueueSnackbar(`函数式组件,点击按钮`, {
variant: 'success'
});
};
return (
<Button variant="contained" color="primary" onClick={buttonClick}>
函数式组件中触发
</Button>
);
};

export default FunctionDemo;

封装 SnackbarProvider 组件

可以将 SnackbarProvider 组件封装一次,免去后续直接修改根组件文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { SnackbarProvider } from "notistack";
import { SnackbarOrigin } from "@material-ui/core";

// 最大数量
const MAX_SNACK = 3;
// 自动隐藏时间,单位毫秒
const AUTO_HIDE_DURATION = 3000;
// 消息条位置
const POSITION: SnackbarOrigin = {
vertical: "top",
horizontal: "right"
};

export default function NotistackWrapper({ children }) {
return (
<SnackbarProvider
maxSnack={MAX_SNACK}
autoHideDuration={AUTO_HIDE_DURATION}
anchorOrigin={POSITION}
>
{children}
</SnackbarProvider>
);
}

在根组件使用 NotistackWrapper 组件替代之前未封装的 SnackbarProvider 组件。

本篇代码

hexo多级分类和并列分类设置

hexo文章分类设置

hexo中提供了 categories 分类设置,但官方文档对于此处的写法说明比较模糊

普通分类设置:

1
categories: category

多层分类设置:

1
categories: [parent, children]

并列分类设置:

1
2
3
categories:
- parent
- parent

并列分类及多层分类同时使用:

1
2
3
categories:
- [parent, children]
- [parent, children]

也可以这样:

1
2
3
categories:
- category
- [parent, children]

以上的写法基本上可以覆盖日常写作需求

还可以这样写:

1
categories: [[parent, children], [parent, children]]

这种写法和并列+多层分类效果相同

MaterialUI结合react-hook-form 1 (从一个简单的表单开始)

相关依赖及版本

1
2
3
4
5
6
7
{
"@material-ui.core": "4.11.3",
"react-hook-form": "7.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-scripts": "4.0.0"
}

本系列所有文章示例中的依赖均以上述为标准,或者查看对应示例代码CodeSandbox中的版本。

react-hook-form 简单介绍

React Hook Form 是一个高性能、灵活。抑郁校验的表单库。可以用更少的代码获得更好的性能。

详细信息请查看React Hook Form官方文档

需求分析

实现一个带有输入名称、网址来添加书签的表单

编写代码

基础代码

引入需要的模块

1
2
3
import React from "react";
import {Controller, useForm} from "react-hook-forn";
import {Botton, TextEield} from "@material-ui/core";

定义接口

1
2
3
4
interface FormType {
name: string;
url: string;
}

编写form组件

1
2
3
4
5
6
7
8
9
10
11
export default function App() {
return (
<div className="App">
<form>
<TextField label="名称" size="small" variant="outlined" />
<TextField label="网址" size="small" variant="outlined" />
<Button color="primary">提交</Button>
</form>
</div>
)
}

这是一个最基础的表单,不包含事件。

Materal UI 对于表单部分的描述简单,仅有按钮、输入框等组件。从这点上对比Ant Design React的form组件显得过于简单。但同时简单意味着可以根据需求做二次封装,相比Antd的拿来就可以用,Material UI则需要一些额外的东西才可以实现效果。

因此使用React Hook Form显然是一个方便的选择。但它的文档中案例均以原生为基础,没有涉及到UI库的案例,这就导致使用UI库结合React Hook Form上手时往往会出现很多意外的问题。

使用 useFormController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default function App() {
const {control, handleSubmit} = useForm<FormType>();
const [value, setValue] = React.useState({} as FormType);

const onSubmit = (val: FormType) => {
setValue(val);
}

return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
{/* ... */}
</form>
</div>
)
}

useForm() 参数请查看:useForm Api,这里我们需要 controlhandleSubmit 这两个部分。

control 用于将组件注册到React Hook Form

handlSubmit 则是表单校验通过时返回数据的事件

使用 Controller 组件控制输入框

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
export default function App() {
const { control, handleSubmit } = useForm<formType>();
const [value, setValue] = React.useState({} as formType);

const onSubmit = (val: formType) => {
setValue(val);
};

return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="name"
render={({ field: { onChange, ref }) => {
return (
<div style={{ display: "block", margin: "10px 0" }}>
<TextField
label="名称"
size="small"
variant="outlined"
onChange={onChange}
inputRef={ref}
/>
</div>
);
}}
/>
<Controller
control={control}
name="url"
render={({field: { onChange, ref }}) => {
return (
<div style={{ display: "block", margin: "10px 0" }}>
<TextField
label="网址"
size="small"
variant="outlined"
onChange={onChange}
inputRef={ref}
/>
</div>
);
}}
/>
<Button type="submit" color="primary" variant="contained">
提交
</Button>
</form>
数据:{JSON.stringify(value)}
</div>
);
}

主要使用的属性:control、name、render具体请看Controller Api

添加校验

现在表单就可以正常使用了,接下来还需要添加校验

Controller 中有一个名为 rules 的属性具体参数参考 useForm()register 属性。

1
2
3
4
<Controller
{...}
rules={{resuired: true}} // 设置必填
/>

现在最基本的使用方式就是这样了,最后是完整的例子:

本文案例(CodeSandbox)

配置post存放位置

hexo配置新建post的位置

使用 hexo new post <post_name> 新建文章时,会默认在 _post 文件夹下生成。这种做法虽然简单,但文章过多的话将不利于查看
因此需要修改默认的 new_post_name 实现功能

打开 _config.yml 文件

1
new_post_name: :title.md

这是一个默认的配置,现在要实现以年月为路径存放文章,修改上面的配置

1
new_post_name: :year/:month/:title.md

这样新建文章将会是这样的路径:/_post/<year>/<month>/<title>.md

当然 new_post_name 还可以配置生成的文章名,例如

1
new_post_name: :year-:month-:day-:title.md

这样的话生成的文章名为 <year>-<month>-<day>-<title>.md 格式,例如 2021-04-15-test.md

官方文档对new_post_name说明

hexo主题及配置GithubPage

hexo主题、简单配置及Github Page配置

主题

这里我选用Fluid主题

1
yarn add hexo-theme-fluid

更多配置详细参考Fluid官方文档

Github Page

首先在github新建一个仓库,我新建的名称为blog

安装hexo-deployer-git

1
yarn add hexo-deployer-git

在_config.yml中添加以下配置

1
2
3
4
deploy: 
type: git
repo: https://github.com/<username>/<project>
branch: gh-pages

主要需要关注repo和branch配置

repo为 https://github.com/virzs/virzs.github.io 时branch需要指定为master分支,配置完成后地址为 https://virzs.github.io/

repo为 https://github.com/virzs/blog 时branch需要指定为gh-pages分支,配置完成后地址为 https://virzs.github.io/blog/

此处virzs为我的用户名,virzs.github.io和blog为仓库名

执行 hexo clean && hexo deploy

hexo clean命令会清除缓存文件和已生成的静态文件

hexo deploy命令会部署网站

打开github仓库settings,找到pages设置,设置source,如果仓库名为 <username>.github.io 类型,选择master分支 /(root) 文件夹。如果仓库名为 <repo> 类型,则选择gh-pages分支,文件夹同样设置为 /(root) 。点击save按钮 github会提示github page的链接。点击进入即可访问博客。

从wordpress迁移到hexo

从wordpress迁移到hexo

最近阿里云数据库到期,考虑到费用问题及日常使用频率,决定将博客换到hexo。

此文章先测试下发布文章

其实很早听说过hexo,而没有使用是因为hexo给我的感觉不像是一款产品,对使用有一定学习成本

而放弃wordpress一方面因为数据库到期,另一方面是因为受到骚扰评论困扰,而且wordpress使用起来感觉过重

3月5日面试反思

3月5日下午1:30分第一场面试:

首先是笔试题,此部分偏向于对基础的考察,对该部分的结果我认为在50%左右。完全没有想到这么快就会有面试,而且过于复习框架部分知识,导致该部分结果较差。
第二部分技术面,面试官是一位很厉害的人,无论是对基础还是框架以及扩展方面都很厉害。提出的问题偏向基础及底层原理,例如:promise原理、async原理。
底层原理对我来说还比较困难,虽然在日常编写代码时有时确实会遇到这些问题,但毕竟工作上并没有时间去研究这些,而其他时间也没有仔细研究过。这部分真的是一问三不知,深深的体会到差距。而后提到css,sass部分,该公司有移动端场景,因此还是很重视兼容性问题,sass部分用的部分比较基础,面试官问到了sass函数,这部分真的没有考虑过,无论是工作中还是日常。可见该公司比较重视代码质量。css部分问了几种布局,例如flex。再然后是float用法。我提出了float目前很少场景使用,反而被面试官以兼容性难到了,可以看出,自己对样式的理解还不够深。基本上就是这些,总体下来可真的失败。
最后是人事面,简单的介绍了下公司的项目,加班情况,补贴,最后询问如果录用大概多久到岗,并且告知结果将在下周一给出。
当然我觉得这次面试糟透了,暴露出了很多我忽略的问题,技术面试官也给我提出了很多宝贵的建议,虽然很糟但还是收获不小。当然面试结果个人认为是凉了。

3月5日下午3点第二场面试:

当然一上来还是面试题主要问道了vue部分及一些原理,另有一道手写promise的题,当然没有答上来。11题大概答下来5题。
之后是boss面,boss是一个懂技术的人,看了我的答题结果表示比预想的要差,但比应届要强很多,该公司也比较重视技术。相比之下前公司则更重视业务的完整性,对代码质量几乎没有要求。这导致我在面对这些公司时没法回答,虽然一部分原因在于前公司的环境,但更多的错误则在我自身。明明下班几乎每天都会敲点代码却很少重视以上的问题。感觉就像白白的浪费了时间。
该公司加班严重且没有补贴,面试结果依旧是周一给出。但我觉得这场多半也是凉了。

3月5日下午4点第三场面试:

该场面试是临时加出来的,恰好该公司在附近,于是我提出干脆面一下吧,进去之后来的是个技术。很出乎意料地没有问道这些原理性问题,谈的内容基本上是简历中写的,该公司给我的感觉比较放松,开发人员也不多,因此他们想招到一个合适的人选。面试官给我的感觉应该不是专门负责前端的,所以没有问基础性问题,针对框架提出了一些使用层面的问题。
而后告知我改天由人事来面,不过没有给出时间。多半也是凉了。

最近一年中过分注重于框架部分,反而忽略了对基础知识的理解。

导致面试中框架部分使用基本没有问题,但涉及到深层次的原理上就无法回答。

很多东西写是一种结果,说又是另一种结果。

过分地关注框架,导致对基础没有一个完整的认知。大量基础知识混杂导致在真正需要描述的时候反而没有办法概括。

日后的计划:把重点从框架转到基础层面,系统总结一个大致详细的知识体系。

不过现在嘛,还是以找工作为主,只能去被面试题了。

突然感觉面试好难,问的很深而工作却用的少。