月盾的博客

firefox火狐新标签中打开书签

月盾

火狐浏览器新建标签总是在当前打开标签之后,而不是在最后一个标签后新建。 1.about:config 2.browser.tabs.insertAfterCurrent设为false。

新标签中打开书签 browser.tabs.loadBookmarksInTabs设置true。

新标签中打开搜索 browser.search.openintab设置true.

nestjs中使用携程Apollo配置中心

月盾

nest框架官方文档中使用的是本地文件配置,也就是@nestjs/config包。本地配置文件的好处是使用简单,但是对于一些更新较快的项目,难免会增加配置数据,曾经吃过不少配置文件的亏,在发布的时候很容易因为缺少配置文件直接把服务发挂了,或者需要在服务器上修改配置很容易修改错误导致服务发布失败。

集中的配置中心可以解决上面问题,本文以apollo配置中心为例来说明。 在使用的过程中需要注意以下问题:从配置中心获取数据库连接信息,再去连接会连接失败,因为在连接的时候还没有获取到配置信息。 先看代码再解释。

// main.ts
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter, NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { MyLogger } from './libs/mylog.service';
import { join } from 'path';
const Apollo = require('node-apollo');
const dotenv = require('dotenv');

async function bootstrap() {
    try {
        const root = join(__dirname, '../');
        let envFile = join(root, '.env')
        dotenv.config({ "path": envFile })
        const {
            APOLLO_APPID,
            APOLLO_ENV,
            APOLLO_HOST,
            APOLLO_NAMESPACE,
            APOLLO_PORT,
            APOLLO_TOKEN,
            APOLLO_ClUSTER
        } = process.env;
        let apolloEnv = {
            configServerUrl: `http://${APOLLO_HOST}:${APOLLO_PORT}`,
            appId: `${APOLLO_APPID}`,
            clusterName: `${APOLLO_ClUSTER}`,
            apolloEnv: `${APOLLO_ENV}`,
            token: `${APOLLO_TOKEN}`,
            namespaceName: [`${APOLLO_NAMESPACE}`]
        };
        // 获取到的配置信息
        let zmConf = await Apollo.remoteConfigService(apolloEnv);
        console.log(">>>>>>>main.config", zmConf);
        process.env = Object.assign(process.env, zmConf);
    } catch (err) {
        console.log(`获取环境变量异常:${err}`)
    }

    const app = await NestFactory.create<NestExpressApplication>(AppModule, new ExpressAdapter());
    app.useLogger(app.get(MyLogger));

    await app.listen(3434, () => {
        const logger = new MyLogger('main.ts');
        logger.debug(process.env.NODE_ENV, 'main.ts');
        logger.log('server start on http://localhost:3434');
    });
}
bootstrap();
// app.module.ts
@Module({
    imports: [
        // MongooseModule.forRoot(`mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DATABASE}`),
        MongooseModule.forRootAsync({
            useFactory: () => ({
                uri: `mongodb://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DATABASE}`
            }),
        }),
        //  Nest can't resolve dependencies of the AppService (?). Please make sure that the argument ArticleService at index [0] is available in the AppModule context.
        // 在article.service中exports:[ArticleService]
        ArticleModule,
        LoggerModule,
        // HttpModule,
    ], // 导入模块所需的导入模块列表
    controllers: [AppController], // 必须创建的一组控制器
    providers: [AppService, MyLogger], // 由 Nest 注入器实例化的提供者,并且可以在整个模块中共享
})
export class AppModule implements NestModule {
    // 中间件模块在此处添加,可以给某一部分增加中间件,如果要全局增加则在main.ts中使用app.use添加
    configure(consumer: MiddlewareConsumer) {
        consumer
            .apply()
            // .with('中间件参数')
            .forRoutes('/*');
    }
}

使用MongooseModule.forRoot连接数据库肯定是不行的,需要改成异步的:

svelte history路由刷新后404

月盾

npms.io上搜索到svelte的route包其实也不算少,使用比较广泛的svelte-spa-router路由包却不支持history模式。有些支持history模式的使用上也不是很方便,试用过五六个支持history的路由后最终@spaceavocado/svelte-router算是满足了要求。

  • 使用简单
  • 功能丰富
  • 支持history和hash 我也是够难伺候的。

在试用了多个支持history的路由过程中,都遇到了一个问题:切换路由后刷新404。这也算是单页应用的通病了。不过像vue这种是在部署到服务器上刷新404,而svelte却在开发过程中也出现了,又想放弃了… 好在我也是试用过五六七八个路由的人了,中间不知道尝试过多少种方法来实现history路由。最后使用@spaceavocado/svelte-router包实现了history路由的时候我还是满心欢喜。

使用方法:

//App.svelte
<script>
	import RouterView from '@spaceavocado/svelte-router/component/view';
	import { routes } from './router.js'
</script>

<RouterView />
//router.js
import createRouter from '@spaceavocado/svelte-router';
import index from './index/index.svelte';
import a from './a/a.svelte';
import b from './b/b.svelte';

export const routes = createRouter({
    mode: "HISTORY",
    routes: [
        {
            path: '/',
            name: 'HOME',
            component: index,
        },
        {
            path: '/a',
            name: 'a',
            component: a,
        },
        {
            path: '/b',
            name: 'b',
            component: b,
        },
        {
            path: '*',
            component: index,
        },
    ],
});
// index.svelte
<script>
	import RouterLink from '@spaceavocado/svelte-router/component/link';
</script>

<main>
	<h1>Hello {name}!</h1>
	<RouterLink to="/a">a页面</RouterLink>
	<RouterLink to="/b">b页面</RouterLink>
</main>

可是一刷新就嗝屁了,依旧出现404.无意中就试了一下之前使用其他路由包使用的一种rollup配置方法。 package.json中的start命令: sirv public -s加了-s参数就好了。

基于sapper开发svelte项目配置本地代理

基于sapper开发svelte项目配置本地代理

月盾

sapper可能被放弃更新,如果您要继续使用svelte,可以考虑使用sveltekit

最近使用svelte开发一个项目,说实在的,开发过程中遇到不少问题。 每次遇到问题的时候都有种想放弃的冲动,这生态也太差了,查个啥啥问题都查不到,找个啥啥插件也没有。 不过,到最后,遇到的问题又都解决了。 这不,今天又遇到了本地代理的设置问题。 在说遇到的问题之前先介绍一些项目架构。该项目是基于sapper框架开发,这是一个使用svelte开发的框架,具备以下特点:

  • 服务端渲染
  • 路由
  • 代码分割
  • 默认支持渐进式web应用(PWA)
  • 预取路由
  • 单独的头标签(meta,link等)
  • 作为静态站点弹出
  • Cypress测试(免费,简单,端到端的测试)

可以看到,sapper基本是集合了目前前端开发所有需求,双向数据绑定,渐进式开发,SSR,静态化,高性能。

遇到的新问题:对于前端项目,调用接口时容易遇到跨域问题,一般是使用本地代理解决。自然也想这样来做,可是sapper没有vue项目那样的生态,用的打包工具也不是webpack,而是rollup。 那么就使用sapper自带的服务端来做代理好了。sapper的服务端用的是polka,而不是express,不过没关系,其实可以相互替换。 最关键的是增加了http-proxy-middleware中间件,却对中间件位置很敏感,不是想随便在哪添加一下就行。需要添加在第一个中间件位置,否则就会优先使用静态服务中间件,导致接口找不到。 完整代码:

import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
import { createProxyMiddleware } from 'http-proxy-middleware';

const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';

polka()
	// 需要放在最前面,否则接口404,secure参数解决调用https问题
	.use('/api', createProxyMiddleware({ target: 'https://example.com', pathRewrite: { '^/api': '' }, secure: false, changeOrigin: true, }))
	.use(
		compression({ threshold: 0 }),
		sirv('static', { dev }),
		sapper.middleware()
	)
	.listen(PORT, err => {
		if (err) console.log('error', err);
	});

总结: 总体来说,使用svelte开发的确没有vue顺畅,但遇到的问题都有解决方案,对于一些非核心项目还是可以使用的,粗略的对比老项目,在性能上有50%的提升。如果你不想使用回最原始的开发方式来提升性能,那么svelte是个不错的备选方案。

svelte项目rollup配置px2rem

月盾

使用svelte开发项目时遇到需要将px转换成rem的需求,有试过postcss-px2rempostcss-pxtorem,等postcss插件,都没成成功,最后找到了postcss-units插件成功实现。 完整rollup配置文件如下: converts px to rem 该配置是sapper项目配置

import path from 'path';
import resolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import commonjs from '@rollup/plugin-commonjs';
import url from '@rollup/plugin-url';
import svelte from 'rollup-plugin-svelte';
import babel from '@rollup/plugin-babel';
import { terser } from 'rollup-plugin-terser';
import config from 'sapper/config/rollup.js';
import pkg from './package.json';
import sveltePreprocess from 'svelte-preprocess';
// import { less } from 'svelte-preprocess';
const postcssUnits = require('postcss-units');

const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;

const onwarn = (warning, onwarn) =>
	(warning.code === 'MISSING_EXPORT' && /'preload'/.test(warning.message)) ||
	(warning.code === 'CIRCULAR_DEPENDENCY' && /[/\]@sapper[/\]/.test(warning.message)) ||
	onwarn(warning);

export default {
	client: {
		input: config.client.input(),
		output: config.client.output(),
		plugins: [
			replace({
				'process.browser': true,
				'process.env.NODE_ENV': JSON.stringify(mode)
			}),
			svelte({
				dev,
				hydratable: true,
				emitCss: true,
				// preprocess: [
				// 	less(),
				// ]
				preprocess: sveltePreprocess({
					postcss: {
						plugins: [
							postcssUnits({
								size: 75
							}),
						]
					},
				}),
			}),
			url({
				sourceDir: path.resolve(__dirname, 'src/node_modules/images'),
				publicPath: '/client/'
			}),
			resolve({
				browser: true,
				dedupe: ['svelte']
			}),
			commonjs(),

			legacy && babel({
				extensions: ['.js', '.mjs', '.html', '.svelte'],
				babelHelpers: 'runtime',
				exclude: ['node_modules/@babel/**'],
				presets: [
					['@babel/preset-env', {
						targets: '> 0.25%, not dead'
					}]
				],
				plugins: [
					'@babel/plugin-syntax-dynamic-import',
					['@babel/plugin-transform-runtime', {
						useESModules: true
					}]
				]
			}),

			!dev && terser({
				module: true
			}),
		],

		preserveEntrySignatures: false,
		onwarn,
	},

	server: {
		input: config.server.input(),
		output: config.server.output(),
		plugins: [
			replace({
				'process.browser': false,
				'process.env.NODE_ENV': JSON.stringify(mode)
			}),
			svelte({
				generate: 'ssr',
				hydratable: true,
				dev,
				// preprocess: [
				// 	less(),
				// ]
				preprocess: sveltePreprocess({
					postcss: {
						plugins: [
							postcssUnits({
								size: 75
							}),
						]
					},
				}),
			}),
			url({
				sourceDir: path.resolve(__dirname, 'src/node_modules/images'),
				publicPath: '/client/',
				emitFiles: false // already emitted by client build
			}),
			resolve({
				dedupe: ['svelte']
			}),
			commonjs(),
		],
		external: Object.keys(pkg.dependencies).concat(require('module').builtinModules),

		preserveEntrySignatures: 'strict',
		onwarn,
	},

	serviceworker: {
		input: config.serviceworker.input(),
		output: config.serviceworker.output(),
		plugins: [
			resolve(),
			replace({
				'process.browser': true,
				'process.env.NODE_ENV': JSON.stringify(mode)
			}),
			commonjs(),
			!dev && terser()
		],

		preserveEntrySignatures: false,
		onwarn,
	}
};

配置完成后同时需要将css进行修改:

svelte函数传参

月盾

svelte给dom对象绑定事件和vue框架类似。 定义函数:

function handler(index){
	alert("hello", index);
}

绑定事件:

<button on:click={handler}>点击</button>

但是带参函数的使用就略有不同了,函数handler的参数index需要传入的时候,不能直接这样使用<button on:click={handler(123)}>点击</button>,这样的写法会在页面打开时直接执行,而不是在点击按钮的时候执行。

这是初学svelte的时候比较郁闷的事,官方文档中也没有明显的文档说明如何传参。 正确的传参方式是这样的: <button on:click={() => handler(123)}>点击</button>on:click的内容改写为匿名函数,在函数中调用。

go单元测试初始化

月盾

go单元测试会遇到这样的场景: 写好了service层函数getUser()。然后测试测试getUser函数。有个问题是,函数中使用了数据库连接,如果直接测试的话会报错误,比如空指针错误。

panic: runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal 0xc0000005 code=0x0 addr=0xb0 pc=0x167680d]

如果遇到这种情况很有可能就是数据库连接未初始化。但是单元测试并不会主动去初始化数据库连接。不用担心,有办法。 go test提供了用于初始化的方法:TestMain函数。只需要在这个函数中进行数据库初始化,后面需要用的的数据库连接可直接使用,不需要重复初始化。

func TestMain(m *testing.M) {
	fmt.Println("begin")
	dba, err := gorm.Open("sqlite3", "../../website.db")
	db.SQLLite = dba
	if err != nil {
		panic(err)
	}
	m.Run()
	fmt.Println("end")
}

func TestProjectUsers(t *testing.T) {
	userService := user.NewService(db.SQLLite)
	users, err := userService.GetProjectUsers(25)
	if err != nil {
		t.Error(err)
	}
	t.Log("返回结果:", users)
}

Svelte3路由

月盾

svelte目前没有提供官网路由组件,不过可以在社区中找到。本文介绍的是svelte-spa-router的使用方法。

npm i svelte-spa-router 参考以下目录结构创建文件(不是必须)

router.js:

import index from './index/index.svelte';
import a from './a/a.svelte';
import b from './b/b.svelte';

export const routes = {
    '/': index,
    '/a': a,
    '/b': b
}

动态导入组件和代码分割

import { wrap } from 'svelte-spa-router/wrap'
import index from './index/index.svelte';

export const routes = {
    '/': index,
    '/a': wrap({
        asyncComponent: () => import('./a/a.svelte')
    }),
    // '/b': b
	// 动态加载
    '/b': wrap({
        asyncComponent: () => import('./b/b.svelte')
    }),
}

动态导入组件的优点是组件不会一起打包,而是单独的组件文件,在打开对应的页面时才会请求,可以有效减少包文件大小。 App.svelte:

<script>
	import Router from 'svelte-spa-router'
	import { routes } from './router.js'
</script>

<Router {routes} />

a.svelte:

<script>
import { link } from "svelte-spa-router";
</script>

<a href="/a" use:link>a</a>
<a href="/b" use:link>b</a>

注意:svelte-spa-router是基于hash实现,作者认为这是静态单页应用的理想实现方式,history方式的路由需要增加服务端,这增加了复杂性。

vscode远程开发应用场景

月盾

vscode远程开发

vscode-remote

vscode远程开发功能在2019年5月份发布,到现在已经有一年半的时间了,但是周围的人很少提及此功能,并不是没有人使用vscode,而是对此没有强烈需求。

那么远程开发还有什么用呢?下面我来举些列子。

关于vscode远程环境搭建本文不重复说明,网上有大量教程,大家只需要安装remote development插件基本都可以使用起来。

远程开发,顾名思义就是连接远端服务器进行开发,这样的场景确实不是很常见,但是有时候却是很有用,能够解决燃眉之急。

本地主机性能差

有一些大型项目对电脑的要求也较高,编译耗时,跑起来吃内存,我们的常规解决方案是升级电脑内存,升级硬盘,总之就是换高配电脑,如果是换一台也倒罢了,如果是一个团队都要使用更高配的电脑,那这样的成本还是挺高的。

此时利用远程开发功能,所有人共用一台8核16G服务器就足够了。能够解决编译耗时,吃内存等问题。

开发环境统一

现代大多数应用服务是跑在Linux上的,而开发环境则有Windows,Mac,有些服务在开发环境下无法顺利跑起来,这种情况就可以利用远程开发,不再需要虚拟机或WSL了,在Linux上部署应用,在Windows上开发。

远程调试

远程调试我想很多人都有过这样的需求,但是却从来没有过真正的实践,原因是太难搞了。线上出问题,本地无法复现,真希望能够直接调试线上代码,但是无奈无法实现。现在好了,有了vscode远程开发,调试就变得容易了。

远程编辑文件

在Linux上编辑文件时使用的vim编辑器对大多数人来说有些头大,能在vscode中编辑文本就舒服多了。只需要使用vscode连接远程服务可以很方便打开文件并编辑。

在家临时远程开发

作为开发的我们,每天下班都要背着电脑回家,主要原因就是防止线上有问题,能够打开电脑调试代码。一般来说并不是家里没有电脑,而是没有能够正常运行的开发环境。

而远程开发正好能解决该问题,我们需要的仅仅是一台装有vscode的普通电脑即可,不需要再操心开发环境,各种SDK,C++,Python,Nodejs等等八辈子不用的软件装了一大堆,还需要经常更新才行。

节省成本

大多数开发人员是不需要经常开发大型项目和远程调试代码的,但是不是完全没有,这样就需要为了不时之需而配置笔记本电脑,而笔记本电脑一般相对台式机是又贵性能还低,公司要为了应对偶发情况而给开发人员配备笔记本电脑,会造成资源浪费,成本升高。

如果有了远程开发环境,则能应对临时性工作,大大降低成本。

用iPad写代码

如果你觉得vscode远程开发还不够酷?那在iPad中写代码呢?

vscode远程开发虽好,但还有有局限性,毕竟还是需要一台可以安装vscode的电脑,还是不能随时随地的写代码。现在我要告诉你如何在iPad中写代码!

code-server是一个可以运行在服务器上的web项目,这下我们可以在浏览器中使用vscode了,可以在浏览器中打开vscode的,自然就可以使用iPad来写代码了。 Eclipse Theia https://gitpod.io/#https://github.com/eclipse-theia/theia

额,希望苹果能给我广告费!

再或者使用使用codespaces也可以在线编辑代码,https://mp.weixin.qq.com/s/Eutjgbx_nofmuhU2yBGhxg

最后

贴一篇带图的环境搭建教程真香!使用 VSCode 远程开发调试

通用的数据库GUI工具

月盾

MySQL常用的客户端是Navicat,SQLyog等,本文推荐另一款通用的客户端:DBeaver

连接

DBeaver不能创建表?那是因为选错了视图。可以重新编辑链接

DBeaver视图