Chobits

Chobits 查看完整档案

石家庄编辑河北北方学院  |  数学教育 编辑美国圣地亚戈国际农业集团  |  非洲支援特别行动小组 编辑 steamcommunity.com/id/chobitssp 编辑
编辑

保卫骡博 Chobits#3780

个人动态

Chobits 关注了问题 · 8月29日

node中如何用模型来接收前端传递的数据

业务背景

最近在用阿里云的serverless重构自己的一个私人应用,发现存mogodb数据库的时候,前端传过来多少个字段,就存进去多少个字段

比如用户注册只需要username password两个字段,但是前端传的时候把confirmPassword也传过来

在node层木有过滤,直接给存到数据库里面去了

我的问题

很久很久以前接触.net的时候,好像对入参可以设置一个模型,系统只会取模型中存在的字段,像刚刚这种情况,就会自动过滤掉confirmPassword

那么,在node.js中有类似的插件吗

tips

由于个人业余时间实在有限,着实不想自己实现这样一个功能,业务代码也有很多,所以不想为了自己写或者去搜索这样的插件浪费太多时间

就来当一下伸手党了

关注 3 回答 1

Chobits 赞了文章 · 7月19日

踩坑记:Go服务灵异panic

这个坑比较新鲜,周一刚填完,还冒着冷气。

- 1 -

在字节跳动,我们线上服务的所有 log 都通过统一的日志库采集到流式日志服务、落地 ES 集群,配上字节云超(sang)级(xin)强(bing)大(kuang)的监控能力,每一条 panic log 都可以触发一个打给值班同学的电话。

所以我们常常不选电话,只选飞书 ↓↓↓

lark-only.png

但毕竟是 panic,大部分 case 都会迅速被就地正法,除了少数排查费劲、又不对线上产生太大影响的,比如这一个:

Error: invalid memory address or nil pointer dereference
Traceback:
goroutine 68532877 [running]:
...
src/encoding/json/encode.go:880 +0x59
encoding/json.stringEncoder(0xcb9fead550, ...)
...
src/encoding/json/encode.go:298 +0xa5
encoding/json.Marshal(0x1ecb9a0, ...)
...
/path/to/util.SendData(0xca813cd300)

注:为了方便阅读,略有简化。

你看,它可以被 recover 兜住(不会把服务搞挂),而且出现频率很低(每天几次甚至没有),考虑到在每天数百亿请求中的占比,解决它的 ROI 实在太低,所以就耽搁了一段时间 且不用担心背 P0 的锅

这个锅我不背s.png

- 2 -

其实之前 S 同学和我都关注过这个 panic ,从上面的 Error log 可以看到,错误发生在调用 json.Marshal 的时候,调用方的代码大概长这样:

func SendData(...) {
  data := map[string]interface{} {
    "code":    ctx.ErrorCode,
    "message": ctx.Message,
    "step":    ctx.StepName,
  }
  msg, err := json.Marshal(data)
  ...
}

注:实际map有更多key/value,这里略作简化。

看这代码,第一反应是:这**也能 panic ?

奇了怪了打码s.png

找到对应的 json 库源码(encode.go第880行,对应下面第5行):

func (e *encodeState) string(s string, escapeHTML bool) {
  e.WriteByte('"')
  start := 0
  for i := 0; i < len(s); {
    if b := s[i]; b < utf8.RuneSelf {
      ...

—— 也只是从string里逐个读取字符,看着并没什么猫饼。

由于 panic 发生在官方 json 库里,不适合修改并部署到全量机器;引入第三方 json 库又涉及很多依赖问题,所以当时没再跟进。

直到最近 panic 频率逐渐升高, H 和 L 同学实在看不下去了。

- 3 -

L 同学的思路是,既然这个 panic 能被 recover 兜住,那为什么不看看 panic 时这个 map 里装了什么呢?

葫芦里装逼s.png

于是代码就变成了这样:

defer func() {
  if p := recover(); p != nil {
    log.Warnf("Error: %v, data: %v", p, data)
  }
}()
data := map[string]...

然后 panic 顺利转移到了 log.Warnf 这一行[doge]

- 4 -

不管怎么说成功地转移了问题,只要把 log.Warnf 这一行注释掉……

你懂我意思吧s.png

作为一个追求极致的 ByteDancer,L 同学抵制住了诱惑并尝试了新的思路,既然从 panic log 看到是跪在了一个 string 上,那至少先看看是哪一个string:

data := make(map[string]interface{})
defer func() {
  if p := recover(); p != nil {
    for k, v := range data {
      log.Warnf("CatchMe: k=%v", k)
      log.Warnf("CatchMe: v=%v", v)
    }
  }
}()
...

改起来倒是很简单,赶在这个 需要上班的 周日下午发了车,晚上就捉到了一个case。

通过线上 log,我们发现错误出现在 "step" 这个 key 上(log里有输出key、但没输出value),value 本应是 ctx.StepName 这个 string。

可是 string 这种看起来人畜无害的 immutable 的 type 为什么会导致 panic 呢?

我怀疑你在逗我s.png

- 5 -

通过走读代码得知,在遇到异常的时候,我们会往 ctx.StepName 写入这个异常点的名称,就像这样:

const STEP_XX = "XX"

func XX(...) {
  if err := process(); err != nil {
    ctx.StepName = STEP_XX
  }
}

一边读一边写,有那么点并发的味道了。

考虑到我们为了降低媒体感知的超时率,将整个广告的召回流程包装成一个带时间限制的任务:

finished := make(chan struct{})
timer := time.NewTimer(duration)
go recall(finished)
select {
  case <-finished:
    sendResponse()
  case <- timer.C:
    sendTimeoutResponse()
}

因此在一个请求流程中,确实可能会出现并发读写 ctx.StepName 这个 string object 的情况。

但如何实锤是这儿挖的坑呢?

- 6 -

在线上服务中直接验证这一点不太容易,但是 H 同学做了一个简单的 POC,大概像这样:

const (
  FIRST  = "WHAT THE"
  SECOND = "F*CK"
)

func main() {
  var s string
  go func() {
    i := 1
    for {
      i = 1 - i
      if i == 0 {
        s = FIRST
      } else {
        s = SECOND
      }
      time.Sleep(10)
    }
  }()

  for {
    fmt.Println(s)
    time.Sleep(10)
  }
}

代码一跑起来就有点味道了:

$ go run poc.go
WHAT THE
F*CK
...
WHAT
WHAT
WHAT
F*CKGOGC
...

黑人问号s.png

虽然没看到 panic,但是确实看到了点奇怪的东西(严正声明:不是故意要吐槽GO的GC)。

再用 go 的 race detector 瞅瞅:

$ go run -race poc.go >/dev/null    
==================
WARNING: DATA RACE
Write at 0x00c00011c1e0 by goroutine 7:
  main.main.func1()
    poc.go:19 +0x66(赋值那行)

Previous read at 0x00c00011c1e0 by main goroutine:
  main.main()
    poc.go:28 +0x9d(println那行)

这下可算是实锤了。

- 7 -

那么为什么 string 的并发读写会出现这种现象呢?

这就得从 string 底层的数据结构说起了。在 go 的 reflect 包里有一个 type StringHeader ,对应的就是 string 在 go runtime的表示:

type StringHeader struct {
    Data uintptr
    Len  int
}

可以看到, string 由一个指针(指向字符串实际内容)和一个长度组成。

比如说我们可以这么玩弄 StringHeader:

s := "hello"
p := *(*reflect.StringHeader)(unsafe.Pointer(&s))
fmt.Println(p.Len)

对于这样一个 struct ,golang 无法保证原子性地完成赋值,因此可能会出现goroutine 1 刚修改完指针(Data)、还没来得及修改长度(Len),goroutine 2 就读取了这个string 的情况。

因此我们看到了 "WHAT" 这个输出 —— 这就是将 s 从 "F*CK" 改成 "WHAT THE" 时,Data 改了、Len 还没来得及改的情况(仍然等于4)。

至于 "F*CKGOGC" 则正好相反,而且显然是出现了越界,只不过越界访问的地址仍然在进程可访问的地址空间里。

- 8 -

既然问题定位到了,解决起来就很简单了。

最直接的方法是使用 sync.Mutex:

func (ctx *Context) SetStep(step string) {
  ctx.Mutex.Lock()
  defer ctx.Mutex.Unlock()
  ctx.StepName = Step
}

Mutex 性能不够好(lock does not scale with the number of the processors),对于这种读写冲突概率很小的场景,性能更好的方案是将 ctx.StepName 类型改成 atomic.Value,然后

ctx.StepName.Store(step)

注:也可以改成 *string 然后使用 atomic.StorePointer

实际上,Golang 不保证任何单独的操作是原子性的,除非使用 atomic 包里提供的原语或加锁

- 9 -

大结局:周一下午 H 同学提交了修复代码并完成发布,这个 panic 就再没出现了。

总结一下:

  • string 没有看起来那么人畜无害
  • 并发的坑可以找 -race 帮帮忙
  • 记得使用 mutex 或 atomic

最后留下一个小问题供思考:

这说了半天并没有完全复现 panic,不过文中已经给了足够多的工具,你能想到怎么办吗?


推荐阅读:

欢迎关注

weixin1.png

查看原文

赞 12 收藏 2 评论 14

Chobits 发布了文章 · 5月28日

微信小程序 绝对路径 require

https://segmentfault.com/a/11...

之前参照上面文章 可以实现绝对路径的引入 但是在vscode中 require函数无法自动提示路径 并且require的模块也不能自动提示

为了解决这个问题


1.根目录建立 jsconfig.json 文件

{
  "compilerOptions": {
    "target": "ES6",
    "baseUrl": ".",
    "paths": {
      "/*": ["./*"]
    }
  }
}

2.在使用 require 函数的js文件 最前面加上

const app = getApp();
(function () { require = app.require; })();

3.使用

const CommonApi = require("/api/common");   //可自动提示路径
CommonApi.CallApi1(); //可以自动提示出方法 可配合d.ts食用
查看原文

赞 0 收藏 0 评论 0

Chobits 收藏了文章 · 5月20日

tsconfig常用配置解析

一、TypeScript编译工具安装

我们用TypeScript开发项目的时候,编写的源文件都是以".ts"结尾的文件,而".ts"文件是无法直接被浏览器或node环境下直接运行的,所以必须进行编译,将".ts"的文件编译成".js"文件后才能直接运行。要想编译TypeScript文件,那么必须使用tsc编译工具,需要注意的是,我们并不是直接去安装tsc而是全局安装typescript,全局安装好typescript之后,就可以在命令行工具中直接使用tsc命令了,如下图所示:

tsc.png

二、tsc编译工具的使用

首先我们可以在命令行中,输入tsc --help,即可查看tsc命令的使用帮助文档。如果直接在项目根目录下输入tsc,那么tsc会将项目下的所有".ts"文件都进行编译,编译后输出的文件与源ts文件同名、同位置

tsc--help.png

我们可以在执行tsc命令的时候传递一些参数,进行特定的配置,如:
// 编译结果采用es5,模块形式采用commonjs,编译源文件为 ts/index.ts

tsc --target es5 --module commonjs ts/index.ts

对于简单的项目,我们可以通过给tsc命令传递一些参数进行编译,但是如果是大型的复杂项目,那么通过传递命令参数的形式进行编译就会显得心有余而力不足了,也不方便。我们可以采用配置文件的方式,来指导编译。即在项目根目录下,新建一个tsconfig.json的文件。注意,这是一个json文件,当然不一定要放在根目录下,可以通过tsc --project path/to/tsconfig.json指定配置文件。

三、使用配置文件编译ts文件

① files

由于默认情况下,tsc会编译当前项目下的所有ts文件,所以如果我们可以通过files配置来指定编译的入口文件files的属性值为一个数组,可以指定要编译的具体文件,但是其不能使用通配符进行指定

{
    //"files": [ // files中不能使用通配符进行配置
      //  "ts/**/*.ts"
    //]
    "files": [
        "ts/index.ts" // 对ts目录下的index.ts文件进行编译
    ]
}
如果files中配置了通配符,那么会报错 error TS6053: File '/path/to/ts-test/ts/**/*.ts' not found.,并且files的属性值不能是空数组([]),否则也会编译报错;

② include

正是由于files配置无法使用通配符进行配置,所以添加了include配置,includes配置属性值也是一个数组,但是include中可以使用通配符,并且可以和files一起使用,最终编译的源文件包含,files和include的合集,如:

{
    "files": [
        "foo/foo.ts" // 包含foo目录下的foo.ts
    ],
    "include": [ // 可以使用通配符
        "ts/**/*.ts" // 包含ts目录下的所有ts文件
    ]
}
所以最终编译的源文件包含ts目录下的所有ts文件和foo目录下的foo.ts文件。

③ exclude

我们可以通过exclude配置来排除掉include配置中包含的源文件,需要特别注意的是,exclude只对include中包含文件起到排除的作用其无法排除files中配置的源文件。exclude的适用场景,通常为,当所有的源文件被includes进来后,而其中有一些是ts的测试文件,可以直接排除掉,如:

{
    "include": [
        "ts/**/*.ts"
    ],
    "exclude": ["node_modules", "ts/test"] // 排除对ts目录下的test目录下的测试文件的编译
}

exclude只能排除include中包含的文件,并且不是可编译文件的依赖文件,也就是说,如果include配置为可编译ts目录下的所有ts文件,那么index.ts可以编译,虽然exclude了foo.ts文件,但是index.ts依赖了foo.ts,所以foo.ts还是会被编译。

④ compilerOptions

这是一个编译选项配置,用于控制编译过程和编译结果。常用的编译选项为:

4.1 noEmitOnError

这个是用于配置,当编译源文件出现错误的时候,是否继续输出编译结果。noEmitOnError默认为false,所以即使编译的源文件中有错误,那么也会继续输出编译结果,如果noEmitOnError配置为true,那么当源文件中有错误的时候,将不再输出编译结果

{
    "compilerOptions": {
        "noEmitOnError": true // 编译的源文件中存在错误的时候不再输出编译结果文件
    }
}

4.2 outDir

这个是用于指定编译结果的输出目录的。在不指定outDir的时候,默认是将编译结果输出文件输出到源文件所在目录下,所以可以通过outDir指定一个编译结果输出目录,那么所有的编译文件都会输出到指定的目录下,如:

{
    "compilerOptions": {
        "outDir": "./dist" // 将所有编译结果输出到dist目录下
    }
}

4.3 noImplicitAny

这个是用于控制当源文件中存在隐式的any的时候是否报错,noImplicitAny默认为false,即当源文件中存在隐式的any的时候也不报错,如果设置为true则会报错,如:

// ts/index.ts
function foo(bar) { // bar参数存在隐式any
    console.log(bar);
}
{
    "compilerOptions": {
        "noImplicitAny": true, // 当源文件中存在隐式any的时候报错
    }
}

4.4 noImplicitThis

这个是用于控制当源文件中存在this的值是any的时候是否报错,noImplicitThis默认为false,即当源文件中存在this为any的情况也不报错,如果设置为true则会报错,如:

// ts/index.ts
function foo(bar: string) {
    console.log(this.str); // 这里的this为any
}
{
     "compilerOptions": {
        "noImplicitThis": true, // 当源文件中存在this为any的时候报错
     }
}
// 改正方法为,显示指定this的类型
class Foo {
    str: string;
}
function foo(this: Foo, bar: string) { // 指定this为Foo类型的实例
    console.log(this.str);
}

4.5 target

这个是用于控制编译后输出的是什么js版本。即生成的js符合什么版本的js规范,其默认值为es3,如下:

// ts/index.ts
const str = "this is a string";
{
     "compilerOptions": {
        "target": "es6"
     }
}
// dist/index.js编译输出结果
const str = "this is a string";

4.6 lib

这个是用于指定要引入的库文件,当ts文件中使用到了一些全局的类库的时候才会配置,属性值为一个数组,有es5es6es7dom四个值可选,如果不配置lib,那么其默认会引入dom库,但是如果配置了lib,那么就只会引入指定的库了。如:

// ts/index.ts
document.getElementById("#app");
{
    "compilerOptions": {
        "lib": ["es6"], // 只引入es6的库文件,不引入dom的库文件
        "target": "es6"
    }
}

由于配置了lib,那么就只会引入es6的库文件,不再引入dom相关的库文件了,所以无法使用dom相关的东西,比如document,会提示ts/index.ts:2:1 - error TS2584: Cannot find name 'document'. Do you need to change your target library? Try changing the lib compiler option to include 'dom'.

lib的配置和target也有关,target默认值为es3,所以当ts文件中使用到了Promise等全局类库的时候,就无法解析了,这个时候我们可以通过lib配置,引入es6的全局类库;当然我们也可以直接将target设置为es6,那么就可以解析Promise了。

4.7 module

这个用于指定要使用的模块标准,如果不显式配置module,那么其值与target的配置有关,其默认值为target === "es3" or "es5" ?"commonjs" : "es6",所以当target为es3或者es5的时候,module的默认值为commonjs,当target为其他的值的时候,那么module的默认值为es6。当然可以显式的指定,如:

{
    "compilerOptions": {
        "module": "commonjs", // 指定使用的模块标准
    }
}

由于模块标准与采用是es5语法还是es6语法没有关系,也就是说,模块标准可以与各种es语法相互配合,比如,es5语法也可以采用es6的模块标准,所以module专门用于配置输出结果中引入或导出模块时采用的语法标准,是采用commonjs还是node。
为了支持CommonJS和AMD的exports, TypeScript提供了export =语法。所以ts源文件使用export =语法导出的时候,module必须配置为commonjs,同时ts源文件中也必须使用import module = require("module")来导入此模块
也就是说,如果我们希望最终使用commonjs模块标准,那么我们用export =语法导出,用import module = require("module")来导入;如果我们希望最终使用ES6模块标准,那么我们就使用ES6的模块标准进行导入导出,二者不可混用。

4.8 removeComments

这个用于指定编译输出文件中是否删除源文件中的注释,默认为false,即不删除,如果设置为true,则会在输出文件中清除所有注释。

{
    "compilerOptions": {
        "removeComments": true, // 是否在输出文件中清除源文件中的注释
    }
}

4.9 alwaysStrict

这个用于控制是否始终以严格模式检查每个模块,并且在编译后的输出结果中加入"use strict";默认为false。

{
    "compilerOptions": {
        "alwaysStrict": true, // 始终以严格模式检查每个模块,并且在编译后的结果文件中加入"use strict";
    }
}

4.10 declaration

这个用于指定是否在编译完成后生成相应的*.d.ts文件,默认为false,即不生成对应的声明文件,只有当你的代码需要给其他模块引用的时候才需要生成相应的类型声明文件,如:

{
    "compilerOptions": {
        "declaration": true, // 用于指定是否在编译完成后生成相应的*.d.ts文件
    }
}

4.11 moduleResolution

这个是用于配置模块的解析规则,主要有两种,分别为classicnode。默认值为module ==="amd" or "system" or "es6" or "es2015"?"classic" : "node",所以其默认值和module的配置有关联,由于module的默认值和target有关,而target默认值为es3,所以module的默认值commonjs,所以moduleResolution的默认值为node。这里解释一下classic和node两种解析规则的不同:


假设用户主目录下有一个ts-test的项目,里面有一个src目录,src目录下有一个a.ts文件,即/Users/**/ts-test/src/a.ts

  • classic模块解析规则:
    ① 对于相对路径模块: 只会在当前相对路径下查找是否存在该文件(.ts文件),不会作进一步的解析,如"./src/a.ts"文件中,有一行import { b } from "./b",那么其只会检测是否存在"./src/b.ts",没有就算找不到。
    ② 对于非相对路径模块: 编译器则会从包含导入文件的目录开始依次向上级目录遍历尝试定位匹配的ts文件或者d.ts类型声明文件,如果/Users/**/ts-test/src/a.ts文件中有一行import { b } from "b",那么其查找过程如下:
/Users/**/ts-test/src/b.ts
/Users/**/ts-test/src/b.d.ts
/Users/**/ts-test/b.ts
/Users/**/ts-test/b.d.ts
/Users/**/b.ts
/Users/**/b.d.ts
/Users/b.ts
/Users/b.d.ts
/b.ts
/b.d.ts
  • node模块解析规则:
    ① 对于相对路径模块:除了会在当前相对路径下查找是否存在该文件(.ts文件)外,还会作进一步的解析,如果在相对目录下没有找到对应的.ts文件,那么就会看一下是否存在同名的目录,如果有,那么再看一下里面是否有package.json文件,然后看里面有没有配置,main属性,如果配置了,则加载main所指向的文件(.ts或者.d.ts),如果没有配置main属性,那么就会看一下目录里有没有index.ts或者index.d.ts,有则加载。
    ② 对于非相对路径模块: 对于非相对路径模块,那么会直接到a.ts所在目录下的node_modules目录下去查找,也是遵循逐层遍历的规则,查找规则同上,同上node模块解析规则查找如下:
/Users/**/ts-test/src/node_modules/b.ts
/Users/**/ts-test/src/node_modules/b.d.ts
/Users/**/ts-test/src/node_modules/b/package.json(如果指定了main)
/Users/**/ts-test/src/node_modules/b/index.ts
/Users/**/ts-test/src/node_modules/b/index.d.ts

/Users/**/ts-test/node_modules/b.ts
/Users/**/ts-test/node_modules/b.d.ts
/Users/**/ts-test/node_modules/b/package.json(如果指定了main)
/Users/**/ts-test/node_modules/index.ts
/Users/**/ts-test/node_modules/index.d.ts

/Users/**/node_modules/b.ts
/Users/**/node_modules/b.d.ts
/Users/**/node_modules/b/package.json(如果指定了main)
/Users/**/node_modules/index.ts
/Users/**/node_modules/index.d.ts

/Users/node_modules/b.ts
/Users/node_modules/b.d.ts
/Users/node_modules/b/package.json(如果指定了main)
/Users/node_modules/index.ts
/Users/node_modules/index.d.ts

/node_modules/b.ts
/node_modules/b.d.ts
/node_modules/b/package.json(如果指定了main)
/node_modules/index.ts
/node_modules/index.d.ts

4.12 baseUrl

这个是用于拓宽引入非相对模块时的查找路径的。其默认值就是"./",比如当moduleResolution属性值为node的时候,如果我们引入了一个非相对模块,那么编译器只会到node_modules目录下去查找,但是如果配置了baseUrl,那么编译器在node_modules中没有找到的情况下,还会到baseUrl中指定的目录下查找;同样moduleResolution属性值为classic的时候也是一样,除了到当前目录下找之外(逐层),如果没有找到还会到baseUrl中指定的目录下查找;就是相当于拓宽了非相对模块的查找路径范围。

{
    "compilerOptions": {
        "moduleResolution": "node",
        "baseUrl": "./typings" // 配合moduleResolution解析方式,如果没有找到,则会再到当前配置所在目录下的typings目录下查找
    }
}

需要注意的时候,如果把baseUrl配置成默认值"./",那么将不会起作用,相当于没有配置,将按node和classic的解析规则进行解析此时必须配合下面的paths配置才会生效,并且既可以找.ts文件,也可以找.d.ts类型声明文件

4.13 paths

这个是配合baseUrl一起使用的,因为其是相对于baseUrl所在的路径的,主要用于到baseUrl所在目录下查找的时候进行的路径映射。如:

// projectRoot/src/index.ts
import foo from "foo";
{
    "compilerOptions": {
        "baseUrl": "./typings",
        "paths": { // 路径映射,相对于baseUrl
            "foo": ["node_modules/foo"]
        }
    }
}
如果没有配置paths(没有配置paths属性或者paths属性值为{}),那么当引入的非相对模块找不到的情况下,这里以classic模块解析规则为例,编译器会到./typings目录下去查找有没有foo.ts或者foo.d.ts,但是如果配置了paths(至少配置了一项),那么编译器就不会到./typings目录下去查找有没有foo.ts或foo.d.ts了,即使./typings目录下有foo.ts或foo.d.ts也无法找到,因为其只会到baseUrl/映射路径下查找,即./typings/node_modules/foo.ts或者./typings/node_modules/foo.d.ts如果是node的解析规则,那么foo就可以是文件夹了,主要取决于模块解析规则。

4.14 typeRoots

这个用于指定类型声明文件的查找路径。默认值为node_modules/@types,即在node_modules下的@types里面查找。需要注意的是这里仅仅是d.ts文件的查找路径。同样,这个也是相当于在引入非相对模块的时候拓宽了类型声明文件的查找范围,其实就是配置类型声明文件的查找目录,如:

// projectRoot/src/index.ts
import foo from "foo";
{
    "compilerOptions": {
        "typeRoots": [
            "node_modules/@types", // 默认值
            "./typings"
        ]
    }
}

在其他情况都找不到foo模块的时候,编译器还会到项目根目录下的typings目录下去查找有没有foo目录里面是否有一个index.d.ts类型声明文件,并且只能识别目录下的.d.ts文件,不能识别.ts文件


不管typeRoots怎么配置,编译器都会到node_modules/@types下查找类型配置文件,并且不管是classic解析还是node解析,都会到node_modules/@types目录下查找类型声明文件,即typeRoots和types的配置与模块的解析规则无关

4.15 types

这个需要配合typeRoots来使用,用于指定需要包含的模块只有在这里列出的模块的声明文件才会被加载进来,其属性值为一个数组,如果将types设置为一个空的数组,那么typeRoots配置的目录里的声明文件都将不会被加载进来,比如此时如果源文件中使用到了node的内置模块,将会编译失败,如:

// ts/index.ts
import http = require("http"); // 引入了node里的http模块
console.log(http);
{
    "compilerOptions": {
        "typeRoots": [
            "node_modules/@types" // 默认值
        ],
        "types": ["node"], // 将@types/node里的类型声明文件引入进来
    }
}

// 加载自己的类型声明文件

{
     "compilerOptions": {
         "typeRoots": [
            "./typings"
         ],
         "types": ["foo"]
     }
}
可以到./typings目录下查找是否有foo目录,并且foo目录下是否有index.d.ts

再比如,源码中存在import foo from "foo",因为编译器在node解析规则的情况下,既会到node_modules下面找也会到node_modules/@types下面找,但是如果这两个目录下都没有找到foo模块的定义,那么编译器会看一下typeRoots和types的配置,如果node_modules/@types目录下存在一个bar目录,并且bar目录里面有一个index.d.ts和foo.d.ts,具体配置如下:
// node_modules/@types/bar/foo.d.ts
declare class Foo {

}
declare module "foo" {
    export default Foo;
}
// node_modules/@types/bar/index.d.ts
/// <reference path="foo.d.ts" />
{
    "types": ["bar"] // 引入bar模块的定义
}
由于源码中引入的是foo模块,但是node_modules/@types目录下并没有foo模块的定义,但是有bar模块的定义,但是bar模块中有foo模块的定义,那么就可以通过"types"配置,引入整个bar模块的定义,因为bar模块中引入了foo模块的定义,所以foo模块的定义也会被加载进来,当然不配置types,那么node_modules/@types目录下的所有模块的定义都会加载进来,所以types的配置主要为了一次性加载某个包含了很多类型声明的模块

如果node_modules和node_modules/@type里找不到foo模块的定义,通过typeRoots和types的配置也找不到,那么编译器就会根据baseUrl的配置,进一步查找。所以非相对模块的查找顺序为,根据moduleResolution的配置,确定是使用node还是classic模块进行基础解析,如果找不到,则查看typeRoots和types的配置,如果还是找不到,则查看baseUrl和paths的配置,需要注意的是typeRoots和types的配置只能是查找.d.ts类型声明文件,如果还是找不到,那么就在编译入口所在目录下查找有没有对应模块的定义了,后面会具体讲解。

四、其他配置项查询

其他配置项查询
.d.ts文件的查找范围为,只要该.d.ts文件在编译入口范围之内,那么其中的定义都可以被识别,并且类型声明文件的名称可自定义。

{
    "includes": ["./**/*.ts"],
     "exclude": ["./node_modules", "test", "hack"]
}

如上配置,那么项目根目录下的类型声明文件,除了在test和hack目录下的都能被识别。因为其编译入口范围为,项目根目录下的所有.ts文件,但是排除了test和hack目录。

五、总结

tsconfig的配置主要分两块,编译入口编译选项。编译入口主要通过files、includes、excludes进行控制,files只能配置具体的文件,不能使用通配符,includes和excludes可以使用通配符,并且excludes只能排除includes中的文件。
编译选项这一块,主要需要理清引入非相对模块时的查找方式和顺序,通过moduleResolution确定模块的解析规则,引入相对先按是classic还是node解析规则进行查找,如果查找不到,那么编译器提供了两种方式来拓宽查找范围,一种是typeRoots和types,另一种是baseUrl和paths,首先到typeRoot和types包含的目录中查找(只能查找类型声明文件),typeRoot和types的配置和模块的解析规则无关只有node_modules目录下查找才和模块的解析规则有关,如果还是找不到,再到baseUrl和paths包含的目录中查找。

查看原文

Chobits 关注了标签 · 5月16日

blazor

Build client web apps with C#

关注 2

Chobits 回答了问题 · 4月27日

解决go 如何优化下面的代码,方便其他的地方调用

关注 5 回答 4

Chobits 收藏了文章 · 4月15日

全栈后台管理系统脚手架 gin-vue-admin 2.0版本介绍

项目文档

在线文档
项目地址

1. 基本介绍

1.1 项目介绍

在线预览

Gin-vue-admin是一个基于vue和gin开发的全栈前后端分离的后台管理系统,集成jwt鉴权,动态路由,动态菜单,casbin鉴权,表单生成器,代码生成器等功能,提供多种示例文件,让您把更多时间专注在业务开发上。

1.2 贡献指南

Hi! 首先感谢你使用 gin-vue-admin。

Gin-vue-admin 是一套为后台管理平台准备的一整套前后端分离架构式的开源框架,旨在快速搭建后台管理系统。

Gin-vue-admin 的成长离不开大家的支持,如果你愿意为 gin-vue-admin 贡献代码或提供建议,请阅读以下内容。

1.2.1 Issue 规范

  • issue 仅用于提交 Bug 或 Feature 以及设计相关的内容,其它内容可能会被直接关闭。如果你在使用时产生了疑问,请到 Slack 或 Gitter 里咨询。
  • 在提交 issue 之前,请搜索相关内容是否已被提出。

1.2.2 Pull Request 规范

  • 请先 fork 一份到自己的项目下,不要直接在仓库下建分支。
  • commit 信息要以[文件名]: 描述信息 的形式填写,例如 README.md: fix xxx bug
  • <font color=red>确保 PR 是提交到 develop 分支,而不是 master 分支。</font>
  • 如果是修复 bug,请在 PR 中给出描述信息。
  • 合并代码需要两名维护人员参与:一人进行 review 后 approve,另一人再次 review,通过后即可合并。

1.3 版本列表

2. 使用说明

- node版本 > v8.6.0
- golang版本 >= v1.11
- IDE推荐:Golang
- 各位在clone项目以后,把db文件导入自己创建的库后,最好前往七牛云申请自己的空间地址。
- 替换掉项目中的七牛云公钥,私钥,仓名和默认url地址,以免发生测试文件数据错乱

2.1 web端

# clone the project
git clone https://github.com/piexlmax/gin-vue-admin.git

# enter the project directory
cd web

# install dependency
npm install

# develop
npm run serve

2.2 server端

# 使用 go.mod

# 安装go依赖包
go list (go mod tidy)

# 编译
go build

2.3 生成swagger自动化API文档

2.3.1 安装 swagger

(1)可以翻墙
go get -u github.com/swaggo/swag/cmd/swag
(2)无法翻墙

由于国内没法安装 go.org/x 包下面的东西,需要先安装gopm

# 下载gopm包
go get -v -u github.com/gpmgo/gopm

# 执行
gopm get -g -v github.com/swaggo/swag/cmd/swag

# 到GOPATH的/src/github.com/swaggo/swag/cmd/swag路径下执行
go install

2.3.2 生成API文档

cd server
swag init

执行上面的命令后,server目录下会出现docs文件夹,登录http://localhost:8888/swagger/index.html,即可查看swagger文档

2.4 docker镜像

感谢 @chenlinzhong提供的docker镜像.

# 启动容器
docker run -itd --net=host --name=go_container shareclz/go_node /bin/bash;

# 进入容器
docker exec -it go_container /bin/bash;
git clone https://github.com/piexlmax/gin-vue-admin.git /data1/www/htdocs/go/admin;

# 启动前端
cd /data1/www/htdocs/go/admin/QMPlusVuePage;
cnpm i ;
npm run serve;

# 修改数据库配置
vi /data1/www/htdocs/go/admin/QMPlusServer/static/dbconfig/config.json;

# 启动后端
cd /data1/www/htdocs/go/admin/QMPlusServer;z
go run main.go;

3. 技术选型

  • 前端:用基于vueElement-UI构建基础页面。
  • 后端:用Gin快速搭建基础restful风格API,Gin是一个go语言编写的Web框架。
  • 数据库:采用MySql(5.6.44)版本,使用gorm实现对数据库的基本操作,已添加对sqlite数据库的支持。
  • 缓存:使用Redis实现记录当前活跃用户的jwt令牌并实现多点登录限制。
  • API文档:使用Swagger构建自动化文档。
  • 配置文件:使用fsnotifyviper实现yaml格式的配置文件。
  • 日志:使用go-logging实现日志记录。

4. 项目目录

    ├─server           (后端文件夹)
    │  ├─api            (API)
    │  ├─config         (配置包)
    │  ├─core              (內核)
    │  ├─db             (数据库脚本)
    │  ├─docs              (swagger文档目录)
    │  ├─global         (全局对象)
    │  ├─initialiaze    (初始化)
    │  ├─middleware     (中间件)
    │  ├─model          (结构体层)
    │  ├─resource       (资源)
    │  ├─router         (路由)
    │  └─utils            (公共功能)
    └─web            (前端文件)
        ├─public        (发布模板)
        └─src           (源码包)
            ├─api       (向后台发送ajax的封装层)
            ├─assets    (静态文件)
            ├─components(组件)
            ├─router    (前端路由)
            ├─store     (vuex 状态管理仓)
            ├─style     (通用样式文件)
            ├─utils     (前端工具库)
            └─view      (前端页面)

5. 主要功能

  • 权限管理:基于jwtcasbin实现的权限管理
  • 文件上传下载:实现基于七牛云的文件上传操作(为了方便大家测试,我公开了自己的七牛测试号的各种重要token,恳请大家不要乱传东西)
  • 分页封装:前端使用mixins封装分页,分页方法调用mixins即可
  • 用户管理:系统管理员分配用户角色和角色权限。
  • 角色管理:创建权限控制的主要对象,可以给角色分配不同api权限和菜单权限。
  • 菜单管理:实现用户动态菜单配置,实现不同角色不同菜单。
  • api管理:不同用户可调用的api接口的权限不同。
  • 配置管理:配置文件可前台修改(测试环境不开放此功能)。
  • 富文本编辑器:MarkDown编辑器功能嵌入。
  • 条件搜索:增加条件搜索示例。
  • restful示例:可以参考用户管理模块中的示例API。
前端文件参考: src\view\superAdmin\api\api.vue 
后台文件参考: model\dnModel\api.go 
  • 多点登录限制:需要在config.yaml中把system中的useMultipoint修改为true(需要自行配置Redis和Config中的Redis参数,测试阶段,有bug请及时反馈)。
  • 分片长传:提供文件分片上传和大文件分片上传功能示例。
  • 表单生成器:表单生成器借助 @form-generator
  • 代码生成器:后台基础逻辑以及简单curd的代码生成器。

6. 计划任务

  • [ ] 导入,导出Excel
  • [ ] Echart图表支持
  • [ ] 工作流,任务交接功能开发
  • [ ] 单独前端使用模式以及数据模拟

7. 更新日志

日期日志
2020/01/07角色增加数据资源功能 增加数据资源关联返回 演示环境代码已同步 开启了多点登录拦截 可能会被其他人挤掉
2020/01/13增加了配置管理功能 此功能不发表至测试环境 待保护机制以及服务重启机制发开完成后才会发表值测试环境 请自行clone且导入sql体验
2020/02/21修改了casbin的自定义鉴权方法,使其完全支持RESTFUL的/:params以及?query= 的接口模式
2020/03/17增加了验证码功能 使用了 @dchest/captcha
2020/03/30代码生成器开发完成 表单生成器开发完成 使用了@form-generator
2020/04/01增加前端历史页签功能,增加(修改)条件查询示例,前端背景色调修改为白色。(如不需要此功能可以在web/src/view/layout/index.vue中屏蔽HistoryComponent背景色调,为本页260行&.el-main中的background属性)
2020/04/04启动2.x版本,项目文档规范化,日志功能改造,方法增加英文注释

8. 团队博客

https://blog.henrongyi.top

内有前端框架教学视频。如果觉得项目对您有所帮助可以添加我的个人微信:shouzi_1994,欢迎您提出宝贵的需求。

9. 教学视频

9.1 环境搭建

Bilibili:https://www.bilibili.com/vide... (v1.0版本视频,v2.0操作相同目录不同)

9.2 模板使用

Bilibili:https://www.bilibili.com/vide... (v1.0版本视频,v2.0操作相同目录不同)

9.3 golang基础教学视频录制中...

地址:https://space.bilibili.com/32...

- QQ交流群:622360840

- 微信交流群:可以添加上面任意一位开发者,备注"加入gin-vue-admin交流群"

10. 开发者列表

昵称项目职务
@piexlmax项目发起者
@granty1后台开发
@Ruio9244全栈开发
@1319612909前端UI开发
@krank666前端开发
@chen-chen-up新手开发

11. 捐赠

如果你觉得这个项目对你有帮助,你可以请作者喝饮料

查看原文

Chobits 关注了标签 · 4月10日

golang

Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。Go是谷歌2009发布的第二款编程语言。

七牛云存储CEO许式伟出版《Go语言编程
go语言翻译项目 http://code.google.com/p/gola...
《go编程导读》 http://code.google.com/p/ac-m...
golang的官方文档 http://golang.org/doc/docs.html
golang windows上安装 http://code.google.com/p/gomi...

关注 25954

Chobits 关注了问题 · 2月2日

vue cli3 如何使用jsx?

项目开始

vue create .
# 默认配置

代码:

export default {
  render(h) {
    return (<div>11</div>);
  }
}

尝试过的:

方案1

https://github.com/vuejs/jsx#...

Install the preset with:

npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props

Then add the preset to.babelrc:

{
  "presets": \["@vue/babel-preset-jsx"\]
}

报错

Module parse failed: Unexpected token (62:12)
File was processed with these loaders:
 * ./node_modules/eslint-loader/index.js
You may need an additional loader to handle the result of these loaders.
|   },
|   render(h) {
>     return (<div>11</div>);
|   }

方案二

https://github.com/vuejs/babe...

npm install babel-plugin-syntax-jsx babel-plugin-transform-vue-jsx babel-helper-vue-jsx-merge-props babel-preset-es2015 --save-dev

In your.babelrc:

{
  "presets": \["env"\],
  "plugins": \["transform-vue-jsx"\]
}

报错 同上

方案三:

https://segmentfault.com/q/10... @zangeci 的问答

报错 同上

关注 2 回答 0

Chobits 关注了标签 · 1月16日

graphql

GraphQL 是一个由Facebook提出的 应用层查询语言. 使用 GraphQL, 你可以基于图模式定义你的后端. 然后客户端就可以请求所需要的数据集。

关注 116

认证与成就

  • 获得 384 次点赞
  • 获得 17 枚徽章 获得 1 枚金徽章, 获得 5 枚银徽章, 获得 11 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2014-06-18
个人主页被 5.7k 人浏览