go 工程

Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── api # 符合 OpenAPI/Swagger 规范的服务接口
├── assets # 项目中使用的其他资源,如媒体资源等
├── build # 打包和持续集成; 将容器、包(deb、rpm、pkg)、脚本等放在 build/package; ci 放在 build/ci
├── cmd # 项目主要应用程序; 如, cmd/app/main.go & cmd/tool/main.go
├── configs # 配置文件
├── deployments # Iaas,Paas,系统和容器编排部署配置和模板
├── docs # 设计和用户文档
├── examples # 样例
├── init # 系统初始化(systemd、upstart、sysv) 及进程管理(runit、supervisord)配置
├── internal # 内部代码,不希望被他人导入的代码
├── pkg # 外部应用程序可以使用的库代码
├── scripts # 用于执行构建、安装等操作的脚本
├── test # 外部测试应用程序和测试数据
├── third_party # 外部辅助工具
├── tools # 此项目支持的工具
├── vendor # 应用程序依赖关系
├── web # Web 应用程序的特定组件, 静态资源、前端模板等
├── website # github pages / 网站数据
├── Makefile # 打包
├── go.mod # 模块信息
├── LICENSE.md
└── README.md

Start

1
$ go mod init github.com/sunzhenkai/go-hello-world

添加依赖

1
$ go get github.com/*/*

Reference

go startup

[TOC]

安装

1
2
# mac
$ brew install go

编译

1
2
3
4
5
$ go build main.go
$ go build -o output-file-name main.go

# 打印 gc 信息
$ go build -gcflags="-m" main.go

静态编译

不依赖共享库,可以避免 libc 库接口不兼容的问题。

1
CGO_ENABLED=0 go build -ldflags="-s -w -extldflags=-static" main.go

示例

代码

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Println("Hello World!")
}

运行

1
2
$ go run hello.go
Hello World!

编译

1
$ go build hello.go

添加依赖库示例

1
# main.go

依赖

使用 go get 下载公开库,该命令会把依赖下载至第一个 GOPATH 下的 src 目录下。

参数 说明
-v 打印详情日志
-d 只下载不安装
-u 下载丢失的包,不更新

示例

1
2
$ go get [dep-name]				# 安装单个依赖
$ go get -d -v ./... # 递归地下载当前目录下所有文件的依赖

Package

报名尽量使用单个词。

1
package http;

私有库

terminal prompts disabled

1
2
3
4
go: gitlab.company.com/org/pkg@v0.0.1: reading https://goproxy.cn/gitlab.company.com/org/pkg/@v/v0.0.1.mod: 404 Not Found
server response:
not found: gitlab.company.com/org/pkg@v0.0.1: invalid version: git fetch -f origin refs/heads/*:refs/heads/* refs/tags/*:refs/tags/* in /tmp/gopath/pkg/mod/cache/vcs/1b2a69e43fbd284ebef999cca485d367b743c300d2970b093def252bae54d3ef: exit status 128:
fatal: could not read Username for 'http://gitlab.company.com': terminal prompts disabled

私有项目,默认走 goproxy,故找不到 pkg。

1
2
3
4
5
# 设置 pkg 路径为私有库
go env -w GOPRIVATE="gitlab.company.com/org"

# get
GIT_TERMINAL_PROMPT=1 go get

或者使用 ssh 认证。

1
git config --global --add url."git@your-repo.com:".insteadOf "https://your-repo.com/"

Env

1
2
3
4
5
6
7
8
9
10
11
# 查看 go env
$ go env
GO111MODULE=""
GOARCH="amd64"
...

# 设置
$ go env -w GOPROXY="..."

# 取消设置
$ go env -u GOPROXY

Proxy

1
2
3
4
5
# 设置 proxy
$ go env -w GOPROXY="..."

# 不使用 proxy 的仓库
$ go env -w GOPRIVATE "git.a.com,git.b.com,..."

Mod

1
2
3
4
5
6
7
8
# 下载依赖
go mod download

# 整理依赖
go mod tidy

# 清楚缓存(删除所有下载的库,谨慎操作)
go clean -modcache

参考

go 类型

布尔类型

  • bool

数字类型

数字类型

  • unit8
  • unit16
  • unit32
  • unit64
  • int8
  • int16
  • int32
  • int64

浮点型

  • float32
  • float64
  • complex64
  • complex128

其他类型

  • byte
  • rune
  • unit
  • int
  • uintptr

派生类型

  • 指针
  • 数组
  • 结构化类型
  • Channel类型
  • 函数类型
  • 切片类型
  • 接口类型
  • Map类型

tmux

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 启动
tmux
# 启动并设置 session 名称
tmux new -s {session-name}
# attach
tmux a # or at, or attach
# attach by session name
tmux a -t {session-name}
# 列出 session
tmux ls
# 清理 session
tmux kill-session -t {session-name}

# tmux 内
# 重命名 session
C-B $
# 重命名 window
C-B ,
# 关闭 window
C-B &

快捷键

TmuxCheatSheet

1
2
# active command mode
ctrl + b

Pane

1
2
3
4
5
6
7
8
9
10
11
"   splite vertical
% splite horizon
q show pane numbers
o change focued pane
z set current pane fullscreen
! 转换 pane 为 window
ctrl+o swap panes
[space] 切换布局

x close panel (or exit)
arrow change panel

Session

1
2
3
$  rename session
s list session
d detach (keep session alive)

Window

1
2
3
4
5
6
7
c		create
& close
n next
p previous
w list windows
f find windows
, name windows

拷贝模式

1
2
3
4
5
6
7
# active command mode
ctrl + b

# command
[ 开启拷贝模式, 可以滚动屏幕
q 退出拷贝模式
[Esc] 退出拷贝模式

调整大小

1
2
3
4
5
# 1. 启用 command mode 
ctrl + b

# 2. 调整大小
{滚轮} 调整上下分屏 Pane 的大小

TPM(Tmux Plugin Manager)

安装。

1
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

编辑 ~/.tmux.conf,添加如下内容。

1
2
3
4
5
6
7
8
9
10
11
12
# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'

# Other examples:
# set -g @plugin 'github_username/plugin_name'
# set -g @plugin 'github_username/plugin_name#branch'
# set -g @plugin 'git@github.com:user/plugin'
# set -g @plugin 'git@bitbucket.com:user/plugin'

# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

概念

三要素 Session、Window、Pane。prefix key,默认 ctrl + B。

update-alternatives

安装

1
$ sudo pacman -S dpkg

使用

--slave

在更新一个程序后,往往需要同步更改其他程序,比如修改 gcc 为 gcc11,则需要同步修改 g++、gcov 等,可以使用 slave 来实现。

1
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 110 --slave /usr/bin/g++ g++ /usr/bin/g++-11 --slave /usr/bin/gcov gcov /usr/bin/gcov-11 --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-11 --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-11

示例

1
2
3
# 添加一个 alt
# 链接创建路径 名称 实际文件路径 优先级
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-4.9 10
1
sudo update-alternatives --config cc  # 选择并设置默认版本

node startup

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# nvm
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash # curl
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash # nvm

# ls-remote
$ nvm ls-remote
$ nvm ls-remote --lts # 仅列长期支持版本

# install
$ nvm install v12.20.0
$ nvm install 16 # 安装最新的 16 主版本的最新稳定版

# use
$ nvm use v12.20.0

# set default
$ nvm alias default v12.20.0

# check
$ npm -v
$ node -v

常用工具

1
$ npm install nrm -g

初始化项目

1
$ npm init

安装依赖

1
$ npm install <pkg-name> -s

引入依赖

1
const name = require('pkg-name');

spring boot 常见问题

服务停止时Redis连接池断开异常

问题描述

使用Spring Boot的RedisTemplate、RedisLockRegistry,在停止服务时,由于Redis连接早于应用逻辑断开,导致打印 Redis 连接断开异常,具体异常日志如下。

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
org.springframework.data.redis.RedisSystemException: Redis exception; ness
ted exception is io.lettuce.core.RedisException: Connection is closed
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:74)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:268)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.incr(LettuceStringCommands.java:332)
at org.springframework.data.redis.connection.DefaultedRedisConnection.incr(DefaultedRedisConnection.java:323)
at org.springframework.data.redis.core.DefaultValueOperations.lambda$increment$0(DefaultValueOperations.java:87)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
at org.springframework.data.redis.core.DefaultValueOperations.increment(DefaultValueOperations.java:87)
...
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: io.lettuce.core.RedisException: Connection is closed
at io.lettuce.core.cluster.ClusterDistributionChannelWriter.write(ClusterDistributionChannelWriter.java:71)
at io.lettuce.core.RedisChannelHandler.dispatch(RedisChannelHandler.java:187)
at io.lettuce.core.cluster.StatefulRedisClusterConnectionImpl.dispatch(StatefulRedisClusterConnectionImpl.java:207)
at io.lettuce.core.AbstractRedisAsyncCommands.dispatch(AbstractRedisAsyncCommands.java:467)
at io.lettuce.core.AbstractRedisAsyncCommands.incr(AbstractRedisAsyncCommands.java:796)
at sun.reflect.GeneratedMethodAccessor284.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at io.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:114)
at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)
at com.sun.proxy.$Proxy188.incr(Unknown Source)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.incr(LettuceStringCommands.java:330)
... 13 more

解决方案

解决思路是让Redis连接断开时间晚于服务Bean实例销毁时间,这里的解决方案是使用 @DependsOn 注解,DependsOn 既可以保证依赖Bean早于当前Bean创建,同时也会保证依赖Bean晚于当前Bean销毁,在使用redisTemplate或redisLockRegistry,或二者的Bean类,添加注解,示例如下,按需选择其一或两个。

1
@DependsOn({"redisTemplate", "redisLockRegistry"})

接口返回 null 字段

1
2
3
4
# 添加如下配置
spring:
jackson:
default-property-inclusion: non_null

跨域

1
2
3
4
5
// 添加如下注解
@CrossOrigin(maxAge = 1, methods = {RequestMethod.POST, RequestMethod.GET})
@RequestMapping(value = "info", method = {
RequestMethod.POST, RequestMethod.GET
}, produces = MediaType.APPLICATION_JSON_VALUE)