0%

安装部署

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
# http://nginx.org/en/linux_packages.html#RHEL-CentOS
echo "[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true" > /etc/yum.repos.d/nginx.repo

yum-config-manager --enable nginx-mainline
yum install nginx

systemctl start nginx #启动
systemctl restart nginx #重启
systemctl stop nginx #终止
systemctl enable nginx #开机自启
systemctl disable nginx #禁止开机自启

配置

附加站点配置目录

1
2
3
4
# 备份并修改配置文件
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
mkdir -p /data/workspace/settings/vhost
ln -s /data/workspace/settings/vhost /etc/nginx
1
2
3
4
...
include /etc/nginx/conf.d/*.conf;
+ include /etc/nginx/vhost/*.conf;
...

安全加固

编辑默认页面

1
2
3
4
5
6
7
8
9
echo "<html>
<head><title>Welcome</title></head>
<body>Welcome</body>
</html>" > /usr/share/nginx/html/index.html

echo "<html>
<head><title>50x Error</title></head>
<body>50x Error</body>
</html>" > /usr/share/nginx/html/50x.html

删除如下配置信息

1
2
3
4
5
6
7
8
9
10
11
location /doc {
root /usr/share;
autoindex on;
allow 127.0.0.1;
deny all;
}

location /images {
root /usr/share;
autoindex off;
}

隐藏 nginx 版本信息

打开配置文件 隐藏版本设置 Server_tokens off;

禁用非必要的请求方法

trace 请求用于网络诊断,会暴露信息,只允许 GET、HEAD、POST 请求,其他请求直接返回 444 状态码 (444 是 nginx 定义的响应状态码,会立即断开连接,没有响应正文,TRACE 请求 nginx 内置 405 拒绝)

1
if ($request_method !~ ^(GET|HEAD|POST)$ ) { return 444; }

隐藏 X-powered-by 信息

1
2
proxy_hide_header X-Powered-By;
fastcgi_hide_header X-Powered-By;

禁止 SSLv3 协议

1
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

其他配置

1
2
3
-   #gzip  on;
+ gzip on;
+ autoindex off

解决 systemd-journald 服务占用内存过高的问题

问题排查

生产环境机器发现内存占用很高:

1
2
3
4
[root@node132 /run/log/journal]# free -h
total used free shared buff/cache available
Mem: 125G 87G 10G 323M 27G 36G
Swap: 0B 0B 0B

排查发现,是 systemd-journal 占用了过高的内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@node173 /run/log/journal]# top -o %MEM -n 1 -b | head -n17
top - 16:29:45 up 167 days, 15:01, 1 user, load average: 0.15, 0.20, 0.29
Tasks: 623 total, 2 running, 621 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.4 us, 0.2 sy, 0.0 ni, 99.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 13138539+total, 2088144 free, 93968128 used, 35329120 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 36300876 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6764 root 20 0 81.8g 81.7g 5580 S 0.0 65.2 693:59.06 systemd-journal
18038 root 20 0 34.9g 976.9m 14360 S 0.0 0.8 43:07.76 java
8386 root 20 0 8070328 806540 17436 S 0.0 0.6 5347:53 dockerd-current
8442 root 20 0 5189372 540292 4600 S 0.0 0.4 537:15.83 docker-containe
5792 root 20 0 2302960 374132 8976 S 0.0 0.3 709:40.18 java
20023 root 20 0 2173620 339624 8976 S 0.0 0.3 63:39.12 java
41196 root 20 0 7651664 323728 9880 S 0.0 0.2 37077:57 cadvisor
11116 root 20 0 5099056 317780 15268 S 0.0 0.2 19:47.50 java
35339 root 20 0 2149764 311520 9008 S 0.0 0.2 56:52.01 java
18662 root 20 0 4730832 190692 36380 S 0.0 0.1 8338:20 kubelet

查看相关配置发现,systemd-journal 服务没有启用持久化,日志保留在内存,因此只需要启用持久化即可解决该问题。

问题解决

1. 首先将日志目录移动到 /data 路径下

1
2
3
4
5
mkdir /data/log
mv /var/log/* /data/log/
rm -r /var/log
# 重定向整个日志目录到 /data/log
ln -s /data/log /var

2. 修改服务配置,重启服务

1
2
3
4
5
6
7
8
9
10
11
vim /etc/systemd/journald.conf

# 配置并取消注释以下几项
Storage=persistent
SystemMaxUse=4G
SystemMaxFileSize=1G
RuntimeMaxUse=2G
RuntimeMaxFileSize=1G

# 重启服务
systemctl restart systemd-journald

重启服务后,日志将会被直接持久化到磁盘,不再占用过多内存。

参考

系统安全和系统保护设计

要保证数据安全和系统稳定可用,我们应当全方位地对系统进行保护,这里主要分为两个层面。

一是系统的安全方面,这主要是面向非法入侵、非法请求的。我们需要阻止和屏蔽不信任的请求源访问,我们要保证数据的安全可靠,不被人窃取。

二是系统的健壮性方面,面向合法、信任的请求源,为了保证系统的可用性,我们需要在请求并发高于系统设计容量时,拦截和丢弃超载的请求(有损服务),以避免因为请求过大发生雪崩效应,导致整个系统都不可用。

第一部分:系统安全设计

对于系统的安全防范,需要从全方位、多角度做工作,以确保整个业务链路、整个体系范围都能保证安全。以下就从多个角度来说明如何进行系统安全设计。

网络安全

  • 传输加密

    我们一般开放 Web 访问,走 HTTP 协议。要对外公开接口或页面时,面向外网应当以 HTTPS 的形式公开。

  • 网络隔离

    通过网络互不连通来防止入侵,这是最严格也是最暴力的安全解决方案。在我们的生产实践中,主要体现在如下几个方面:

    • 不公开服务的机器,不分配外网 IP
    • 办公网络和生产网络相互不可以直接通信
  • 网关

    在我们需要对外公开 HTTP/HTTPS 接口时,需要使用网关收敛对外公开的接触面。设计独立的接入层专门负责网络请求的接入,不得将非接入层的机器公开出去。

  • 容器

    我们的应用目前主要基于容器部署。在启动容器时,我们应当为每个不同的项目创建和指定不同的网络,从而隔离不同的项目服务。

认证安全

对于使用应用的实体,无论是人还是系统程序,都应当做到对每个请求都应当能找到对应的责任实体。因此,面向人我们需要做到严格的登录鉴权,面向应用我们应当做到严格的接口签名和数据校验。我们通常将这一层称之为 ACL(Access Control Layer)。

登录鉴权

目前能够落地实践的方案,有如下几个值得推荐:

群体 外网 内网
员工 接入 OA 登录 接入 OA 登录
外部用户 接入微信、企业微信、QQ 登录 不允许外部用户直接登录/访问内网(*)

TIP: 不允许外部用户直接登录内网,必需在外网部署登录验证服务,验证通过之后,才能由外网的服务通过应用网关发起对 OA 服务的请求。所有外部请求均只落到外网区域,不允许通过反向代理将请求直接转发到应用网关继而将未经过登录验证的请求路由到 OA 服务器上。

如果要自建登录验证模块,那么需要注意如下几个方面:

  • 登录页面/登录接口应当有防刷机制(比如验证码),防止机器暴力破解。
  • 应当具备随机登录因子(比如 token、短信验证码),降低密码泄露的后果影响。
  • 多次登录尝试失败之后,应当锁定账户或 IP,不再允许登录。
  • 采用增强的密码管理策略,如拒绝弱密码、密码最小长度要求、字符集要求、定期更改密码要求等等。

登录态管理

我们的服务大多需要集群部署,因此需要是无状态的。虽然智能网关和大多数反向代理一样都支持有状态服务,但为了管理方便和更高的可扩展性,我们的最佳实践是保证开发部署的所有业务服务都是无状态的。而登录态本身是有状态的,因此登录态本身需要从业务服务中剥离出来,以避免导致有状态服务的出现。此时我们通常会将登录凭据保存到 Redis 中。因此,我们需要要求:

  • 登录态应当独立存储
  • Redis 配置密码,并对客户端进行 IP 限制

另一方面,业务侧从 ACL 拿到登录凭据之后,需要注意如下两个方面:

  • 不得将登录凭据(如 access_token、refresh_token)等泄露到客户端(包括 cookie、url 跳转等),更不得将员工真实 ID 泄露到客户端。
    • 推荐的方式是业务侧生成临时的、指代当前登录身份的 hash 值,以该 hash 值为凭据与客户端交互。
  • 对当前用户身份,不得信任传入的外部信息。需要身份信息之后,必须经过验证后从 ACL 获取。

应用安全

  • 请求签名

    我们应当始终对外部请求持谨慎态度,包括请求的来源,请求的数据,都要进行谨慎的处理。只有通过接口签名验证的请求,才信任为合法的请求。

    对请求的接口签名设计,可参考学习 QQ、微信的相关接口。也可直接接入应用网关,使用应用网关的签名机制。我们也可以设计自己的接口签名算法,但需要重点保证用来签名的关键信息(如 app_secret)不得通过请求本身传递,也要告知和约束用户保证用来签名的关键信息不得泄露。

    TIP: 对请求签名除了保证能区分合法和非法请求,也能通过签名追述每一次请求具体的来源应用。

  • 数据加密

    对于敏感数据,不得明文传输和明文存储。

    对外方面,不得明文传输。我们可以采用 map hash 的方式做映射,如 ACL 返回的身份凭据,我们不应当直接写入客户端 Cookie,而是应当生成临时 hash,以映射的关系与客户端关联;也可以对数据本身进行加密传输,如启用 HTTPS,或者将关键信息通过加密之后再传输。

    对内方面,不得明文存储。我们可以加密后再存储,如密码;也可以直接脱敏处理,如日志。

  • 容器

    在我们打包容器时,应当使用安全可靠的基础镜像进行打包。

运维安全

理想状况下我们不应当登录服务器进行任何操作,但实际生产却不是这样的。由于运维工具功能的不完善和滞后性,我们往往不得不直接登录到服务器上去直接执行命令,进行日志维护、服务进程维护、服务器维护等操作。由于已经直接触达服务器本身,因此这也是整个系统安全最高危的部分。

  • 运行账户

    为减少由于我们的服务被攻击之后造成的损失,减小影响面。在运行应用时,不得以 root 权限运行,而应当另外创建专有用户,并授予最小权限。这样即使应用本身被攻击成功,黑客也需要有一个提权的过程,提高了攻击的门槛。

  • 运维账户和运维审计

    对于运维操作,账户本身应当得到严密的管控,严密管控 root 账号。每一个运维人员都应该采用实名登录,并且在每一次登录、操作、登出都进行完善的记录以备审计。

    对于高危操作,需要提升运维人员的意识,避免发生由于粗心大意造成的严重后果。

  • 高危命令

    谨慎执行高危命令,在必要时使用alias指令重命名高危命令。

    参考:

数据安全

数据的安全是重中之重,以上所有的措施其根本也都是为了保证数据的安全。同时,对于数据本身,我们还有一些可以做的事情。

  • 容器隔离

    容器化部署时,我们往往会挂载外部目录到容器中。此时我们应当确保:

    • 尽可能使用稳定、可靠的云存储,避免单机故障
    • 同一个挂载目录不应当跨容器共享,而应当由单个容器独占
  • 租户隔离

    随着 SaaS 化系统越来越多,我们也采用了更多的多租户设计。在系统交互、数据读写方面,务必强要求区分租户。不得因为租户信息被泄露,导致跨租户的其他数据被泄露,必须严控影响面。

  • 离线票据

    跨系统交互时,我们可能会涉及到一些敏感票据,比如 app_key/app_secret、pub_key/private_key 等。其中往往 app_key、pub_key 是公开的,允许通过请求传递,而 app_secret、private_key 是需要严格保密的,不得直接通过请求传递。对于这些需要严格保密的敏感票据,应当通过线下、脱离于当前系统的方式进行传递,如打印并邮寄,如单独的、人工的邮件等等。

  • 日志脱敏

    日志的敏感性是很容易被忽略的。可能我们花了很大的力气做了系统安全加固,缺忘记了日志里往往也存在大量的敏感信息,导致信息通过日志被泄露了。那么在开发时,我们需要时刻注意日志内容,不应当有敏感信息。在日志传输和存储时,也应当对日志内容进行脱敏。

  • 数据备份

    和日志一样,数据的备份文件也是容易被忽视的地方,我们应当花足够大的精力来保证备份文件不被窃取。

政策合规

对于我们的业务来讲,政策合规性一般不存在问题。但从设计上,也应当留有多个方面的合规审计空间。

  • 操作审计

    记录用户的操作对象和操作历史。

  • 权限审计

    对权限的分配和变更,做严格的控制和记录,避免授予的权限不必要的扩大。同时对于已经失效的用户,应当即使回收权限。

  • 数据审计

    对关键数据的读、写,都应当有权限和审批流程进行控制。实际的示例比如 HRC 和 TOF 中,对员工信息的读取,是需要进行权限申请和审批的。

  • 敏感词审计

    应当接入或建立敏感词库,对于 UGC 内容,务必确保拦截和过滤敏感词,以避免不合规的内容展示到系统中,导致政策和舆论风险。

第二部分:系统保护设计

对系统的保护,主要是要使得系统具有更高的健壮性(鲁棒性),要求系统在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃。这其中包含的内容较为广泛,可做的事情也很多,此处只简略说两个部分。

防御性设计

除了代码及的防御性编程,我们还应当有接口层面的保护。这方面主要致力于解决在网络过载情况下的防护,不至于因为客户端密集高并发调用时,拖垮整个系统,影响接入平台的其他应用。

目前常规的做法是基于微服务架构设计,接入 api 网关来做接口保护。包括“腾讯里约”、“ASF”在内,以及市面上的大多数成熟 api 网关都有相关的功能:

  • IP 白名单
  • 消费者鉴权
  • 消费限额
  • 快速拒绝
  • 服务 QPS 预警和限制
  • 服务熔断
  • 防重复
  • 接口缓存

TIP: 成熟的 Api 网关不仅具有上述功能,一般还具有自定义路由、健康检查、高可用接入、协议适配等其他功能。

有了以上功能,我们的服务在需要提供给外部系统访问时就可以通过 api 网关来公开,使得所有进入的请求都流经 api 网关。实际操作上,我们对自身业务系统的容量有了合理评估之后,可以在 api 网关上设置合适的阈值。

通过配置 IP 白名单、消费者鉴权,可以将非法的请求拒之门外;

通过配置消费限额、快速拒绝,可以将过量的、非预期的请求拒之门外,同时避免(通过了消费鉴权的请求)消耗无谓的系统资源;

通过配置服务 QPS 预警和限制,可以在服务能力即将达到临界时,向运维人员发送告警提醒,在实际达到临界时,拒绝更多请求压垮服务;

通过配置服务熔断,可以在我们的服务达到容量极限无法支撑时,保护系统不再受到更多请求流量的冲击;

通过配置防重复和接口缓存,可以在一定程度上减少涌向服务的请求数,避免过多的资源消耗。

以上,不论是来自外部系统的正常还是非正常高并发请求时,过载的网络请求都不会直接涌至我们的服务,而是被 api 网关给拦截或拒绝了回去,从而实现对我们自身业务系统和平台的保护,不至于由于网络过载而导致请求雪崩,系统不可用。

防御性编程

跟上文“应用安全-请求签名”一节中一样,我们除了对接口请求应当持不信任的态度之外,我们也应该对内部 api、method 的调用方持不信任的态度。此时就需要防御性设计和防御性编程的思想,以此来保证我们的程序能够适应更广泛的输入错误,不至于在意外输入时而崩溃。

总结

以上简略地描述了做好系统安全设计和系统保护设计需要做的工作,具体没有展开。在实际工作中由于各个项目的安全级别、网络环境等不用,也可能导致实际能够落地的部分难以面面俱到,此时需要仔细分析我们系统的薄弱和风险环节,有针对性地采取措施,详细深入地做好对应的防护。

安装 Windows 10 Ubuntu 子系统

(网上很多教程,略)

设置代理上网

如果 Windows 10 本身就通过代理上网,则子系统默认是无法上网的,仍然需要设置代理。

设置 apt 的代理

1
echo 'Acquire::http::Proxy "http://myproxy.com:8080";' | sudo tee /etc/apt/apt.conf.d/my-proxy.conf

设置其他代理

1
echo "export http_proxy='myproxy.com:8080'" | sudo tee -a ~/.bashrc

Protobuf & gRPC & gRPC-Gateway

安装

  1. 安装 protoc,下载地址:https://github.com/protocolbuffers/protobuf/releases

    根据自己的系统下载相应的 protoc,windows 用户统一下载 win32 版本。

  2. 配置 protoc 到系统的环境变量中,执行如下命令查看是否安装成功:

    1
    2
    $ protoc --version
    libprotoc 3.6.1
  3. 安装 ProtoBuf 相关的 golang 依赖库

    1
    2
    # 用于根据 protobuf 生成 golang 代码,语法 protoc --go_out=. *.proto
    $ go get -u github.com/golang/protobuf/protoc-gen-go
  4. 安装语法支持、代码高亮和代码格式化插件

    安装 VSC 插件:

    • vscode-proto3
    • Clang-Format

    安装命令行工具

    • clang-format:npm install -g clang-format

Protobuf

  1. 编写 protobuf 代码 ./pb/user/profile-service.proto。(语法参考

    详细代码示例见下文。

  2. 生成 *.pb.go 代码。

    1
    2
    # 生成 message
    $ protoc --go_out=. ./pb/**/*.proto

    详细命令参数见:https://github.com/golang/protobuf

    通过以上命令把./pb/user/profile-service.proto生成了./api/user/profile-service.pb.go文件。至此,我们生成了 go 代码,其中生成的 struct 和相关的方法都可以在 go 工程里正常使用。

gRPC

  1. 生成 *.pb.go 代码。

    您可能已经发现了,上面生成的代码中找不到我们定义的 service ProfileService,其中的两个方法也找不到。这是为什么呢?是因为我们执行 protoc 命令时,没有指定支持 grpc 的插件,指定支持 grpc 的插件之后,即会生成服务相关代码:

    1
    2
    # 生成 message 和 service(支持gRPC)
    $ protoc --go_out=plugins=grpc:. ./pb/**/*.proto

    执行上述命令之后,您会发现生成的 go 文件里,多出了与 ProfileServiceServerProfileServiceClient 相关的 struct 和 interface,也多出了我们 GetProfileLogout 两个方法相关的代码。

  2. 编写服务端和客户端代码

    服务端代码:./grpc/server/server.go,客户端代码:./grpc/grpc-client/client.go,详细代码见下文。

  3. 运行效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 运行 gRPC 服务
    $ go run ./grpc/server/server.go

    # 运行 gRPC 客户端,调用服务端
    $ go run ./grpc/grpc-client/client.go
    Login: true
    Greeting: austinluo

    # 运行 gRPC 客户端,调用服务端
    $ go run ./grpc/grpc-client/client.go admin
    Login: true
    Greeting: admin

至此,基于 gRPC 通信的服务端和客户端均已经建立起来。

gRPC Gateway

如果我们的 gRPC 服务希望通过 HTTP 协议公开出来,以供现有的其他服务调用,那么我们就需要用到 gRPC Gateway。它是 Google 官方提供的一个反向代理,核心功能是提供 HTTP 接口,将接收到的 JSON 请求解码再编码为 pb 二进制格式,然后通过 gRPC 调用服务端,服务端返回了 pb 二进制的响应之后,它再次解码编码为 HTTP 响应体返回到客户端。

如下为实现的过程示例。

  1. 安装:https://github.com/grpc-ecosystem/grpc-gateway

  2. 在 proto 文件中添加google.api.http相关描述

  3. 执行 protoc 命令

    1
    2
    3
    4
    5
    6
    protoc \
    -I. \
    -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
    --go_out=plugins=grpc:. \
    --grpc-gateway_out=logtostderr=true:. \
    ./pb/**/*.proto

    至此,除了生成了上文所说的./api/user/profile-service.pb.go,也生成了./api/user/profile-service.pb.gw.go 文件,该文件即可用于创建 HTTP 反向代理。

  4. 创建 HTTP 反向代理服务 ./grpc/grpc-gateway/http_proxy.go。详细代码见下文。

  5. 运行效果

    1
    2
    3
    4
    5
    # 运行 gRPC 服务
    $ go run ./grpc/server/server.go

    # 运行 gRPC-gateway 反向代理
    $ go run ./grpc/grpc-gateway/http_proxy.go

    发起请求,执行 Login:

    1
    2
    3
    4
    5
    6
    7
    POST /v1/login
    Host: localhost:8080
    Content-Type: application/json
    accept-encoding: gzip, deflate
    content-length: 46

    { "name": "austinluo", "password": "12345" }
    1
    2
    3
    4
    5
    6
    7
    8
    HTTP/1.1 200
    status: 200
    Content-Type: application/json
    Grpc-Metadata-Content-Type: application/grpc
    Date: Fri, 25 Jan 2019 06:35:27 GMT
    Content-Length: 11

    {"ok":true}

    发起请求,执行 GetProfile:

    1
    2
    3
    GET /v1/profile
    Host: localhost:8080
    accept-encoding: gzip, deflate
    1
    2
    3
    4
    5
    6
    7
    8
    HTTP/1.1 200
    status: 200
    Content-Type: application/json
    Grpc-Metadata-Content-Type: application/grpc
    Date: Fri, 25 Jan 2019 06:41:19 GMT
    Content-Length: 66

    {"profile":{"user":{"staff_id":"61050","staff_name":"austinluo"}}}

    至此,支持 HTTP 请求的 gRPC Gateway 搭建成功,可以通过它访问 gRPC 服务了。

附:完整代码:

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
// ./pb/user/profile-service.proto

syntax = "proto3";
package api.user;

option go_package = "api/user";

import "google/api/annotations.proto";

service ProfileService {
rpc GetProfile(GetProfileReq) returns (GetProfileRes) {
option (google.api.http) = {
get : "/v1/profile"
};
}
rpc Login(LoginReq) returns (LoginRes) {
option (google.api.http) = {
post : "/v1/login",
body : "*"
};
}
}

message GetProfileReq {}
message GetProfileRes { Profile profile = 1; }

message LoginReq {
string name = 1;
string password = 2;
}
message LoginRes { bool ok = 1; }

// Note that the generated Go field names always use camel-case naming, even if
// the field name in the .proto file uses lower-case with underscores (as it should).
// https://developers.google.com/protocol-buffers/docs/reference/go-generated
message Staff {
string staff_id = 1;
string staff_name = 2;
}

message Profile {
Staff user = 1;
repeated string permissions = 2;
}
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
54
55
56
57
58
59
60
// ./grpc/server/server.go

package main

import (
"context"
"errors"
"log"
"net"

pb "git.code.oa.com/fip-team/rasse/api/user"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

const (
port = ":50051"
)

// HACK: the logged in user name
// it should be kept in a state management system with session.
var loggedinUser string

// HACK: the password for users
const password = "12345"

type server struct{}

func (s *server) GetProfile(ctx context.Context, in *pb.GetProfileReq) (*pb.GetProfileRes, error) {
user := &pb.Staff{StaffId: "61050", StaffName: loggedinUser}
permissions := []string{}
return &pb.GetProfileRes{
Profile: &pb.Profile{
User: user,
Permissions: permissions,
}}, nil
}

func (s *server) Login(ctx context.Context, in *pb.LoginReq) (*pb.LoginRes, error) {
log.Printf("Received: %v", in.String())
if in.Password == password {
loggedinUser = in.Name
return &pb.LoginRes{Ok: true}, nil
}
return nil, errors.New("password error")
}

func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterProfileServiceServer(s, &server{})
// Register reflection service on gRPC server.
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
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
// ./grpc/grpc-client/client.go

package main

import (
"context"
"log"
"os"
"time"

pb "git.code.oa.com/fip-team/rasse/api/user"
"google.golang.org/grpc"
)

const (
address = "localhost:50051"
defaultName = "austinluo"
defaultPassword = "12345"
)

func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewProfileServiceClient(conn)

// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

resLogin, err := c.Login(ctx, &pb.LoginReq{Name: name, Password: defaultPassword})
if err != nil {
log.Fatalf("could not login: %v", err)
}
log.Printf("Login: %v", resLogin.Ok)

resProfile, err := c.GetProfile(ctx, &pb.GetProfileReq{})
if err != nil {
log.Fatalf("could not get profile: %v", err)
}
log.Printf("Greeting: %s", resProfile.Profile.User.StaffName)
}
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
// ./grpc/grpc-gateway/http_proxy.go

package main

import (
"flag"
"net/http"

"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"

gw "git.code.oa.com/fip-team/rasse/api/user"
)

var (
echoEndpoint = flag.String("echo_endpoint", "localhost:50051", "endpoint of YourService")
)

func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()

mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := gw.RegisterProfileServiceHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)
if err != nil {
return err
}

return http.ListenAndServe(":8080", mux)
}

func main() {
flag.Parse()
defer glog.Flush()

if err := run(); err != nil {
glog.Fatal(err)
}
}

参考

在 Windows 上使用 Mac/Linux 上的 zip/tar 等命令

开发时时而在 MAC、Linux 上,时而在 Windows 上,脚本执行环境无法统一是个大问题。所幸我们使用的 Git 中带有 MINGW32(或安装 CYGWIN),这里面已经有很多的非 Windows 平台命令了。

但是在使用过程中,还是有一些命令是缺失的,比如 zip,此时我们可以自行下载他们(下载地址),下载之后,将可执行的 exe 文件和相关依赖文件,放到 MING32/CYGWIN 的 bin 目录中即可。

附:

  • Git 的 MINGW32 默认安装目录:C:\Program Files (x86)\Git\mingw32\bin

第一步:用户映射

创建用户映射 (例如 users.txt) ,将 SVN 用户和 Git 用户对应起来,为保留提交记录做准备。

进入 SVN 的目录,执行如下命令,注意其中 $1\@your-company.com 部分应当替换为你实际的映射关系。

1
2
3
4
5
6
7
8
$ svn log --xml | grep -P "^<author" | sort -u | \
perl -pe 's/<author>(.*?)<\/author>/$1 = $1 <$1\@your-company.com>/' > users.txt

# 或

$ svn log -q svn+ssh://your/svn/path | \
awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2); print $2" = "$2" <"$2"@your-company.com>"}' | \
sort -u > users.txt

注意,生成的 users.txt 文件应当以 ANSI 编码保存,并且使用 CRLF 换行,您可以使用记事本另存为的功能选择该编码。

第二步:克隆代码

创建一个用于存放 Git 本地仓库的文件夹,如:E:/git,进入该文件夹,执行 clone 命令。

1
2
cd /e/git
git svn clone svn+ssh://your/svn/path -r 2000:HEAD --no-metadata --authors-file=users.txt --trunk=.

该步骤除了 clone 代码之外,还将导入所有的提交记录,执行较慢,需要耐心等待。中途出现找不到作者的情况时,可以修改 users.txt 补充该作者,重新再执行 git svn clone 命令。

详细命令参数,请参考 git svn help clone

执行完成之后,会在 E:/git 目录下创建以你 SVN path 最后一节为名称的子目录,可以进入该目录下的 .git 文件夹,执行 git log 命令查看提交记录。

第三步:提交到 GIT

这个步骤和常规地创建 GIT 仓库没有任何区别,直接添加远程仓库地址,push 即可:

1
2
git remote add origin http://your/remote/git/path.git
git push -u origin master

至此,代码及提交记录从 SVN 迁移到 GIT 的工作全部完成。

参考链接:

译者注

本文为 QCon 2018 上海站主题演讲嘉宾、Heptio 资深工程师、著名 Go 语言专家 David Cheney 关于 Go 语言实践的英文分享。为方便大家阅读,在此由 Austin Luo 翻译为中文,在文中难以理解之处,也特别增加了译者的理解说明。翻译水平有限,如有偏颇之处,烦请联系我(uonun@163.com)更正。转载请注明出处,保留本节译者注。


目录

  • 指导原则
    • 简单性
    • 可读性
    • 生产率
  • 标识符
    • 选择清晰的名称,而不是简洁的名称
    • 标识符长度
    • 命名中不要包含所属类型的名称
    • 使用一致的命名风格
    • 使用一致的声明风格
    • 成为团队合作者
  • 代码注释
    • 变量和常量上的注释应当描述它的内容,而非目的
    • 总是为公开符号写文档说明
  • 包的设计
    • 一个好的包从它的名称开始
    • 避免将包命名为 base、common、util
    • 快速返回,而不是深层嵌套
    • 让零值变得有意义
    • 避免包级别的状态
  • 项目结构
    • 考虑更少,更大的包
    • 确保 main 包越小越好
  • API 设计
    • 设计难以被误用的 API
    • 针对默认用例设计 API
    • 让函数自身定义它所需的行为
  • 错误处理
    • 通过消除错误来消除错误处理
    • 错误只处理一次
  • 并发
    • 保持自己忙碌,否则自己做
    • 将并发留给调用者
    • 不要启动一个永不停止的协程

引言

接下来这两场我将给大家一些编写 Go 代码的最佳实践。

今天这是一个研讨会风格的演讲,我会摒弃那些绚丽的 PPT,而是使用您们可以直接带走的文档。

您可以在这里找到这个演讲最新的在线版本:
https://dave.cheney.net/practical-go/presentations/qcon-china.html

阅读全文 »

打开 Visual Studio Code 的设置,按如下配置即可:

1
2
3
4
5
6
7
8
9
{
// 找到您的 git 目录
"terminal.integrated.shell.windows": "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
"terminal.integrated.shellArgs.windows": ["--login", "-i"],
// 需要的环境变量
"terminal.integrated.env.windows": {
"HOME": "/d/austinluo/Documents/githome"
}
}

附:

我自己修改了当前用户的个人文件夹,通过上述方式可以解决 VSC 中 terminal 的 HOME 位置不对的问题(可以正常加载 .ssh 及相关的 key),但 VSC 默认自带的 Git 仍然会找到系统默认位置的个人文件夹,这个可以通过 junction 命令来解决:

1
2
# 在系统默认的个人文件夹下创建迁移过的新位置的软连接
junction64.exe -s C:\Users\austinluo\Documents -s D:\austinluo\Documents

通过注册表完整设置和迁移个人文件夹位置可能不会出现上述情况。