go环境配置

下载go1.13版本,通过命令解压到/usr/local目录下,并设置环境变量:
sudo tar -zxvf go1.13.9.linux-amd64.tar.gz -C /usr/local/
Ubuntu_20200618虚拟机环境变量如下,通过gedit ~/.bashrc命令打开并编辑:

export GOROOT=/usr/local/go                               # install dictory
export GOPATH=/home/sym/go/gopath                         # work envirment
export GOBIN=$GOPATH/bin                                  # executable file
export PATH=$GOPATH:$GOBIN:$GOROOT/bin:$PATH                # PATH path

代理设置

Go1.13为环境变量GOPROXY设置了默认路径:https://proxy.golang.org,direct。但是由于某些因素,这个使用不了,你懂的。所以七牛云为中国的gopher提供了一个免费合法的代理goproxy.cn,其已经开源。只需一条简单命令就可以使用该代理:

go env -w GOPROXY=https://goproxy.cn,direct

原文链接:https://blog.csdn.net/qq_31930499/article/details/101108056

记住不要在go mod所在项目的目录中执行go get命令,否则会将其添加为该项目依赖放进go mod文件中,而不是编译二进制文件到go bin目录下。当需要使用go get命令安装响应的包时,应该在GOPATH路径下使用该命令。

GOPATH三个目录文件

$GOPATH 目录约定有三个子目录:

src 存放源代码(比如:.go .c .h .s等)
pkg 编译后生成的文件(比如:.a)
bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中)

原文链接:https://blog.csdn.net/shangsongwww/article/details/89680101

golang静态代码检查

gofmt命令格式化代码

①gofmt保存的时候自动 格式化go代码
单文件的格式化:gofmt -l -w test.go
整个项目的格式化:gofmt -l -w goprojectName

goimports命令导入包排序

②goimports 保存的时候自动导入处理包及排序
科学上网安装:go get golang.org/x/tools/cmd/goimports
使用如下命令,对代码导入包顺序进行格式化:

sym@sym-virtual-machine:~/go/gopath/src/github.com/sean/sms$
goimports ./..
sym@sym-virtual-machine:~/go/gopath/src/github.com/sean/sms$ goimports tools/network/request_ip.go

其中,goimports 具体的go代码文件包路径,是对单个go代码文件进行格式化;
goimports ./… 是对该项目下所有的go文件格式化。
注意:有时这个问题是由未格式化导致的,及时多次执行goimports ./xx.go代码文件的命令还是继续报这个错误,这时可以再执行gofmt -l -w ./xx.go命令格式化源代码文件。

golangci-lint语法检查

③golangci-lint或gometalinter 保存的时候自动检查go语法
golangci-lint代码格式检查:golangci-lint run ./…
这里…代表的就是检测目录下所有的文件
或者,在项目目录下直接运行:golangci-lint run ,该命令等价于golangci-lint run ./…
安装
安装最新版本的golint:https://github.com/golang/lint
安装命令:go get -u github.com/golangci/golangci-lint/cmd/golangci-lint,该命令会出错,在GOPATH下改用命令:

wget -O - -q https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.25.1

这里使用1.25.1版本,因为gitlab-ci.yml中指定了该版本 image: golangci/golangci-lint:v1.25.1。同时,该包的版本,需要与golang版本一致,否则可能会报错,可以参考文章https://blog.csdn.net/woailuo626/article/details/101377654

sym@sym-virtual-machine:~/go/gopath/bin$ wget -O - -q https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.25.1
golangci/golangci-lint info checking GitHub for tag 'v1.25.1'
golangci/golangci-lint info found version: 1.25.1 for v1.25.1/linux/amd64
golangci/golangci-lint info installed ./bin/golangci-lint
sym@sym-virtual-machine:~/go/gopath/bin$
sym@sym-virtual-machine:~/go/gopath/bin$ ls
dlv  fillstruct  gocode  gocode-gomod  godef  godoctor  golangci-lint  golint  gomodifytags  go-outline  gopkgs  goplay  gorename  goreturns  go-symbols  gotests  guru  impl
sym@sym-virtual-machine:~/go/gopath/bin$ golangci-lint --version
golangci-lint has version 1.25.1 built from 07374ce on 2020-04-27T18:08:03Z

运行:

golangci-lint run [目录]/[文件名]。

在vscode中配置如下:go.lintTool和go.lintFlags选项

参考:https://blog.csdn.net/benben_2015/article/details/89643090
https://blog.csdn.net/linux_Allen/article/details/90262517

使用golangci-lint run后,不规范内容优化
①a blank import should be only in a main or test package, or have a comment justifying it (golint)应该为使用_导入的包添加注释

       _ "github.com/jinzhu/gorm/dialects/mysql"

应该修改为:

// mysql driver
_ "github.com/jinzhu/gorm/dialects/mysql"

②可导出变量或常量需要添加注释

exported const XXX should have comment or be unexportedexported var XXX should have comment or be unexported

可导出变量或常量XXX需要添加注释,(比如// XXX .),XXX后面要带一个空格+至少一个字符,这里为了简便使用空格+.
可导出变量就是首字符为大写的变量。
③可导出的函数XXX返回了一个未导出的类型(结构)

exported func XXX returns unexported type *cclua.tagLuaInfo, which can be annoying to use

可导出的函数XXX返回了一个未导出的类型(结构),将XXX首字符改为小写,或者将XXX的返回类型改为可导出(首字符大写)。

④error字符串首字母不应该大写,或以标点符号/换行符结尾

error strings should not be capitalized or end with punctuation or a newline

错误的字符串没有初始化或以标点或换行符结尾,比如errors.New(“hey man,wtf!”),可以将最后的!号去掉即可消除该隐患。

⑤使用fmt.Errorf(…)替换errors.New(fmt.Sprintf(…))

should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...)

应该替换errors.New(fmt.Sprintf(…))为fmt.Errorf(…)

⑥ if语句块以return语句作为结尾,应该删除其else语句块部分并且反缩进该语句块

if block ends with a return statement, so drop this else and outdent its block

if语句块以return语句作为结尾,应该删除其else语句块部分并且反缩进该语句块,e.g:

arr := strings.Split(funcName, ".")
if len(arr) < 2 {arr = strings.Split(funcName, ":")if len(arr) < 2 {return nil, fmt.Errorf("func[%v].no-exists", funcName)} else {result := make([]lua.LValue, len(args)+1)result[0] = lua.LString("self")for i := 0; i < len(args); i++ {result[i+1] = args[i]}args = result}
}
应该修改为:
arr := strings.Split(funcName, ".")if len(arr) < 2 {arr = strings.Split(funcName, ":")if len(arr) >= 2 {result := make([]lua.LValue, len(args)+1)result[0] = lua.LString("self")for i := 0; i < len(args); i++ {result[i+1] = args[i]}args = result      } else {return nil, fmt.Errorf("func[%v].no-exists", funcName)}}

⑦包的导入顺序不合规范

file is not goimports-ed (goimports)
"github.com/sean/sms/configs/consts"
"github.com/sirupsen/logrus"
"net"
"net/http"

应该改为:

"net"
"net/http""github.com/sean/sms/configs/consts"
"github.com/sirupsen/logrus"

包的导入顺序,一般为基础包,再者是开源的包或自己的(项目的其他)包,且两者之间有一个换行符,并且安装字典顺序排列。

⑧Error return value of ctx.JSON is not checked (errcheck)
按理说ctx.JSON不应该做err != nil的异常检测的,而golangci-lint也存在异常位置误报的情况,这里就需要看看上下文有没有换行符或者其他不符合规范的格式,如:

err := ctx.ReadJSON(&req)
if err != nil {logrus.Errorf("parse request body error:%v", err)ctx.JSON(common.NormalRes{consts.ErrRequestParams, err.Error(), nil})return}fmt.Printf("[GetSendByUserIDHistory] send history request data:%v\n", req)

改为:

if err := ctx.ReadJSON(&req); err != nil{logrus.Errorf("parse request body error:%v", err)ctx.JSON(common.NormalRes{consts.ErrRequestParams, err.Error(), nil})return
}
fmt.Printf("send history request data:%v", req)

上面主要的问题,是err := ctx.ReadJSON(&req)和err != nil 应该合并为一句
if err := ctx.ReadJSON(&req); err != nil

特别是,对于非空、判零、判等等字段内容的检查,应该放在services层,而不应该放在controllers的handle层;同时,在ctx.ReadJSON等errcheck语句里,不要有fmt.Error或者logrus.Errorf等语句,否则也会在该语句上线出现ctx.JSON is not checked(errcheck)类型的异常提示,如:

controllers/handler/contacts/contact.go:27:11: Error return value of `ctx.JSON` is not checked (errcheck)ctx.JSON(common.NormalRes{consts.ErrRequestParams, err.Error(), nil})^
controllers/handler/contacts/contact.go:32:11: Error return value of `ctx.JSON` is not checked (errcheck)ctx.JSON(common.NormalRes{consts.ErrRequestParams, errors.New("用户编号不能为空").Error(), nil})^

在controllers层源代码有如下语句:

if req.UserID == 0 { // 添加时不传联系人对应的用户user_id也返回错误ctx.JSON(common.NormalRes{consts.ErrRequestParams, errors.New("用户编号不能为空").Error(), nil})return
}

进行如下修改即可:

if err := contactServ.CheckUserID(req.UserID); err != nil { // 添加时不传联系人对应的用户user_id也返回错误ctx.JSON(common.NormalRes{consts.ErrRequestParams, errors.New("用户编号不能为空").Error(), nil})return
}

// 同时将CheckUserID,即判断req.UserID是否等于0的errcheck处理逻辑放在service层,如下:

func CheckUserID(userID int) (err error) {if userID == 0 {err = fmt.Errorf("用户编号不能为空")}return
}

另外,对应返回结构的组装,参考就简原则,也不要在ctx.JSON前,使用如下先赋值给结构体变量,再把结构体变量传递给ctx.JSON的形式,否则也会出现上面的errcheck提示:

res = ContactRespose{Code: consts.StatusOK,Msg:  "OK",Data: ct,
}
ctx.JSON(res)

可以在ctx.JSON中直接使用结构体进行传参,结构体中的字段要使用带key的显示赋值,如:

ctx.JSON(ContactRespose{Code: consts.StatusOK, Msg: "OK", Data: ct})

注意:函数头部的注释是否与函数名一致,并且请求变量要使用结构体的指针形式,并使用短变量赋值符的形式:=进行赋值,否则也可能出现ctx.JSON errcheck的异常提示。

func GetSendHistoryByUser(ctx iris.Context) {var req RequstSendHistoryif err := ctx.ReadJSON(&req); err != nil {ctx.JSON(common.NormalRes{consts.ErrRequestParams, err.Error(), nil})return}......
}

改为:

func GetSendHistoryByUser(ctx iris.Context) {req := &RequstSendHistory{}if err := ctx.ReadJSON(req); err != nil {ctx.JSON(common.NormalRes{consts.ErrRequestParams, err.Error(), nil})return}......
}

⑨composite literal uses unkeyed fields结构体返回时,字段不带key

func CommonRes(code int, msg string, data interface{}) (res NormalRes) {res = NormalRes{Code: code,Msg:  msg,Data: data,}return res
}func FindHandler(ctx iris.Context) {addrs, code, err := addrServ.Find()if err != nil {ctx.JSON(common.NormalRes{code, err.Error(), nil})return}ctx.JSON(common.NormalRes{Code: consts.StatusOK, Msg: "OK", Data: addrs})
}

使用上面无键字段初始化,就会警告:
controllers/address/address.go:24:12: composites: git.xxx.cn/ff/zhouyi/controllers/common.NormalRes composite literal uses unkeyed fields (govet)
ctx.JSON(common.NormalRes{code, err.Error(), nil})

在自己看来NormalRes结构体中的字段就应该是与Code、Msg、Data对应的,但是使用golangci-lint进行静态代码检查时,编译器却不认账。
正确初始化方法是带上相应字段的key:

 ctx.JSON(common.NormalRes{Code:code, Msg:err.Error(), Data:nil})

You can disable it with the -composites=false flag,可以使用下面的命令禁止composite检查,参考这篇文档:

go vet -composites=false

gosec安全分析

④Gosec:Go语言源码安全分析工具
安装:

$ go get github.com/securego/gosec/cmd/gosec/...

使用:
我们可以将Gosec配置为仅运行某个规则子集,如排除某些文件路径,生成不同格式的报告等。在默认情况下,Gosec将对提供的输入文件运行所有规则。要从当前目录递归扫描,你可以提供’./…’ 作为输入参数。

选择规则:
默认情况下,gosec将针对提供的文件路径运行所有规则。但如果你要指定运行某个规则,则可以使用 ‘-include=’ 参数,或者你也可以使用 ‘-exclude=’来排除那些你不想运行的规则。

可用规则:

G101:查找硬编码凭证
G102:绑定到所有接口
G103:审计不安全区块的使用
G104:审计错误未检查
G105:审计math/big.Int.Exp的使用
G106:审计ssh.InsecureIgnoreHostKey的使用
G201:SQL查询构造使用格式字符串
G202:SQL查询构造使用字符串连接
G203:在HTML模板中使用未转义的数据
G204:审计命令执行情况
G301:创建目录时文件权限分配不合理
G302:chmod文件权限分配不合理
G303:使用可预测的路径创建临时文件
G304:作为污点输入提供的文件路径
G305:提取zip存档时遍历文件
G401:检测DES,RC4或MD5的使用情况
G402:查找错误的TLS连接设置
G403:确保最小RSA密钥长度为2048位
G404:不安全的随机数源(rand)
G501:导入黑名单列表:crypto/md5
G502:导入黑名单列表:crypto/des
G503:导入黑名单列表:crypto/rc4
G504:导入黑名单列表:net/http/cgi

Run a specific set of rules:

$ gosec -include=G101,G203,G401 ./...

Run everything except for rule G303:

$ gosec -exclude=G303 ./...

注释代码:

与所有自动检测工具一样,gosec也会出现误报的情况。如果gosec报告已手动验证为安全的,则可以使用“#nosec”来注释代码。

注释将导致gosec停止处理AST中的任何其他节点,因此可以应用于整个块或应用于单个表达式中。

import "md5" // #nosec 不要忘记//注释符号func main(){/* #nosec */if x > y {h := md5.New() // this will also be ignored}}

在某些情况下,你可能还需要重新访问已使用#nosec注释的位置。那么你可以执行以下命令来运行扫描程序以及忽略#nosec注释:

$ gosec -nosec=true ./...

govet

⑤govet

Go vet composite literal uses unkeyed fields

在使用go vet进行语法检查时, 报了这么个错composite literal uses unkeyed fields
对于刚开始看Golang的我一脸懵逼, 明明是可以编译通过且跑通的…
struct 是这样定义的

type CallRequest struct {AccessToken stringAPIName     stringAPIVersion  stringAPIParams   map[string]string
}

代码里是这样用的
// … 省略 …

request := CallRequest{accessToken, apiName, apiVersion, params}

然后 go vet ./… 就报错了… composite literal uses unkeyed fields
看了些资料后知道了, 这样写更严谨一些:

request := CallRequest{AccessToken: accessToken, APIName: apiName, APIVersion: apiVersion, APIPara

或者,直接将golangci.yml配置文件中的govet注释掉。

这里将errcheck、gosec、govet注释掉。

gocritic检查

⑥gocritic
golint代码检查异常信息:

tools/utils/reflect.go:45:2: typeSwitchVar: case 0 can benefit from type switch with assignment (gocritic)switch value.(type) {

对应的代码如下:

func InterfaceType2String(value interface{}) string {var key stringif value == nil {return ""}switch value.(type) {case float64:ft := value.(float64)key = strconv.FormatFloat(ft, 'f', -1, 64)case float32:ft := value.(float32)key = strconv.FormatFloat(float64(ft), 'f', -1, 64)case int:it := value.(int)key = strconv.Itoa(it)case uint:it := value.(uint)key = strconv.Itoa(int(it))case int8:it := value.(int8)key = strconv.Itoa(int(it))case uint8:it := value.(uint8)key = strconv.Itoa(int(it))case int16:it := value.(int16)key = strconv.Itoa(int(it))case uint16:it := value.(uint16)key = strconv.Itoa(int(it))case int32:it := value.(int32)key = strconv.Itoa(int(it))case uint32:it := value.(uint32)key = strconv.Itoa(int(it))case int64:it := value.(int64)key = strconv.FormatInt(it, 10)case uint64:it := value.(uint64)key = strconv.FormatUint(it, 10)case string:key = value.(string)case []byte:key = string(value.([]byte))default:newValue, err := json.Marshal(value)if err != nil {logrus.Errorf("value:%v, Marshal failed, error:%v", value, err)}key = string(newValue)}return key
}

对于type switch,应该使用一个变量接收该value的类型推断,如t := value.(type),并且第一个case应该为nil,对类型推断为nil的进行逻辑处理。
应该修改为:

func InterfaceType2String(value interface{}) (key string) {switch t := value.(type) {case nil:fmt.Printf("type:%T, value:%v\n", t, t)key = ""case float64:ft := value.(float64)key = strconv.FormatFloat(ft, 'f', -1, 64)case float32:ft := value.(float32)key = strconv.FormatFloat(float64(ft), 'f', -1, 64)case int:it := value.(int)key = strconv.Itoa(it)case uint:it := value.(uint)key = strconv.Itoa(int(it))case int8:it := value.(int8)key = strconv.Itoa(int(it))case uint8:it := value.(uint8)key = strconv.Itoa(int(it))case int16:it := value.(int16)key = strconv.Itoa(int(it))case uint16:it := value.(uint16)key = strconv.Itoa(int(it))case int32:it := value.(int32)key = strconv.Itoa(int(it))case uint32:it := value.(uint32)key = strconv.Itoa(int(it))case int64:it := value.(int64)key = strconv.FormatInt(it, 10)case uint64:it := value.(uint64)key = strconv.FormatUint(it, 10)case string:key = value.(string)case []byte:key = string(value.([]byte))default:newValue, err := json.Marshal(value)if err != nil {logrus.Errorf("value:%v, Marshal failed, error:%v", value, err)}key = string(newValue)}return key
}

结构体内存对其

⑦struct of size maligned内存对齐
参考:Golang 是否有必要内存对齐?
有些同学可能不知道,struct 中的字段顺序不同,内存占用也有可能会相差很大。比如:

type T1 struct {a int8b int64c int16
}type T2 struct {a int8c int16b int64
}

在 64 bit 平台上,T1 占用 24 bytes,T2 占用 16 bytes 大小;而在 32 bit 平台上,T1 占用 16 bytes,T2 占用 12 bytes 大小。可见不同的字段顺序,最终决定 struct 的内存大小,所以有时候合理的字段顺序可以减少内存的开销。
这是为什么呢?因为有内存对齐的存在,编译器使用了内存对齐,那么最后的大小结果就会不一样。至于为什么要做对齐,主要考虑下面两个原因:

  • 平台(移植性)
    不是所有的硬件平台都能够访问任意地址上的任意数据。例如:特定的硬件平台只允许在特定地址获取特定类型的数据,否则会导致异常情况
  • 性能
    若访问未对齐的内存,将会导致 CPU 进行两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。而本身就对齐的内存仅需要一次访问就可以完成读取动作,这显然高效很多,是标准的空间换时间做法。
    有的小伙伴可能会认为内存读取,就是一个简单的字节数组摆放。但实际上 CPU 并不会以一个一个字节去读取和写入内存,相反 CPU 读取内存是一块一块读取的,块的大小可以为 2、4、6、8、16 字节等大小,块大小我们称其为内存访问粒度。假设访问粒度为 4,那么 CPU 就会以每 4 个字节大小的访问粒度去读取和写入内存。
    在不同平台上的编译器都有自己默认的 “对齐系数”。一般来讲,我们常用的 x86 平台的系数为 4;x86_64 平台系数为 8。需要注意的是,除了这个默认的对齐系数外,还有不同数据类型的对齐系数。数据类型的对齐系数在不同平台上可能会不一致。例如,在 x86_64 平台上,int64 的对齐系数为 8,而在 x86 平台上其对齐系数就是 4。
models/orders/orders.go:15:12: struct of size 544 bytes could be of size 536 bytes (maligned)
type Order struct {^
models/stars/stars.go:15:11: struct of size 256 bytes could be of size 248 bytes (maligned)
type Star struct {^

可以将结构体中的int32、float64等类型统一,如都统一为64位。

仔细看,T1 存在许多 padding,显然它占据了不少空间。那么也就不难理解,为什么调整结构体内成员变量的字段顺序就能达到缩小结构体占用大小的疑问了,是因为巧妙地减少了 padding 的存在。让它们更 “紧凑” 了。
其实内存对齐除了可以降低内存占用之外,还有一种情况是必须要手动对齐的:在 x86 平台上原子操作 64bit 指针。之所以要强制对齐,是因为在 32bit 平台下进行 64bit 原子操作要求必须 8 字节对齐,否则程序会 panic。详情可以参考 atomic 官方文档(:

Bugs

On x86-32, the 64-bit functions use instructions unavailable before the Pentium MMX. On non-Linux ARM, the 64-bit functions use instructions unavailable before the ARMv6k core. On ARM, x86-32, and 32-bit MIPS, it is the caller’s responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.

golang静态代码检查配置与常见格式异常相关推荐

  1. CppCheck静态代码检查配置(命令行方式或在VS中使用)

    目录 CppCheck静态代码检查 1.1 安装cppcheck 1.2 直接使用Cppcheck 1.2.1 命令行方式 1.2.2 UI方式 1.3 在VS2017中使用 1.3.1 在VS201 ...

  2. Ubuntu vscode 配置c/c++环境 ---- 静态代码检查

    我曾一度因为vscode中c语言的静态代码检查问题而困扰,想想还是太懒了,不愿意折腾,今天搞一下. 首先在ubuntu中装vscode,,, 然后装clang apt install llvm -y ...

  3. Android 静态代码检查

    文章目录 背景 项目当前代码质量问题例子 重复类问题 Java 代码问题 Kotlin 代码问题 预期收益 技术方案 技术调研 技术实施 总体流程 技术细节 CPD 重复代码检查 PMD Java 代 ...

  4. 静态代码检查工具 FindBugs

    静态代码检查工具 FindBugs 使用 FindBugs的原因和方法 静态分析工具承诺无需开发人员费劲就能找出代码中已有的缺陷.当然,如果有多年的编写经验,就会知道这些承诺并不是一定能兑现.尽管如此 ...

  5. jenkins+findbugs+checkstyle+PMD静态代码检查(二)

    可以根据自己的需求选中对应的插件进行配置(不一定非要同时配置三个插件) jenkins:持续集成的工具 fundbugs:检测代码静态错误的插件  例如:定义了没有用到的对象,string类型的比较使 ...

  6. 静态代码检查工具简介

    静态代码检查工具简介 在 Java 软件开发过程中,开发团队往往要花费大量的时间和精力发现并修改代码缺陷.传统的代码复审.同行评审,通过人工方式来检查缺陷仍然是一件耗时耗力的事情.Java 静态代码分 ...

  7. 静态代码检查工具-PMD

    静态代码检查工具-PMD 分类: 网络安全/ 工具使用/ 文章 提高代码的质量,除了要提高逻辑上的控制以及业务流程的理解外,代码本身也存在提高的空间,例如一些潜在的问题可以很早的就避免.类似于编码规范 ...

  8. Python静态代码检查工具Flake8

    简介 Flake8 是由Python官方发布的一款辅助检测Python代码是否规范的工具,相对于目前热度比较高的Pylint来说,Flake8检查规则灵活,支持集成额外插件,扩展性强.Flake8是对 ...

  9. java lint_Android静态代码检查-Lint

    参考文章: Improving Your Code with lint lint 使用 lint 增强你的代码 Android Lint简介 gradle lint gradle中有lint任务,可以 ...

最新文章

  1. 整理:warning LNK4098: 默认库“LIBCMT”与其他库的使用冲突;请使用 /NODEFAULTLIB:library
  2. es获取最大时间的记录_大屏幕大智慧,腕上私教+生理周期,荣耀手表ES评测
  3. 经典C语言程序100例之六四
  4. 5分钟盗走你的隐私照片,这个全球性漏洞到底有多可怕
  5. 注入器 过检测_连云港管道检测服务
  6. ORA-00907:missing right parenthesis缺少右括号
  7. Android RecyclerView快速上手
  8. React 第十章 状态提升
  9. 浅析NVR主流芯片方案
  10. Android 应用开发(9)---内联复杂的XML资源
  11. 不足300的游戏蓝牙耳机靠谱吗?五款高人气蓝牙耳机测评
  12. java 物联网 云计算_传智播客Java JavaEE+物联网云计算 就业班
  13. 去马赛克的频域方法(可抗混叠)
  14. 一堂难忘的计算机课作文,难忘的一节微机课_800字
  15. Golang面试问题汇总
  16. 冒泡排序--咕噜咕噜
  17. 固定利率,会是下一个异军突起的DeFi热点吗?
  18. Xcode--下载地址大全
  19. 模仿淘宝手机号码输入框
  20. XZ_iOS 之企业版APP安装和强制更新

热门文章

  1. 2013 China Hadoop Summit杂记
  2. 养宠受追捧,国内宠物食品电商为何始终萎靡不振?
  3. 天猫店群是什么意思?天猫店群真赚钱吗?揭秘传言月入十万真假?
  4. 交友项目的思路与逻辑
  5. MVP+OKHttp+Recyclerview+Springview下拉刷新上拉加载
  6. Android图片的手动放大缩小
  7. 两个numpy的向量相乘并生成矩阵
  8. 4376. 数圈圈(DAY 13)
  9. android开发 听筒模式,Android开发【06-29视频贴】切换听筒模式部分手机失效,怎么解决?...
  10. 微信小程序 下载文件到本地 (解决文件名乱码问题)