LUCA Enterprise Framework(简称luca)是一款支持多入口、开箱即用的企业级Golang框架。 在设计哲学上,luca更接近于 整洁架构 ,而非 Spring 这种包办一切的大管家。
wget https://gitee.com/woood2/luca/raw/master/scripts/gen.sh
chmod +x ./gen.sh
./gen.sh
遵循:社区规范
├── cmd
│ ├── backend Restful服务
│ │ ├── README.md
│ │ ├── docs swagger文档
│ │ │ ├── docs.go
│ │ │ ├── swagger.json
│ │ │ └── swagger.yaml
│ │ ├── internal
│ │ │ ├── assembly 装配器
│ │ │ │ ├── route.go
│ │ │ │ └── sdk.go
│ │ │ ├── controller C层
│ │ │ │ └── app.go
│ │ │ └── middleware 中间件
│ │ │ ├── auth.go
│ │ │ ├── metrics.go
│ │ │ ├── oplog.go
│ │ │ ├── perm.go
│ │ │ └── trace.go
│ │ └── main.go
│ ├── consumer 队列消费
│ │ ├── internal
│ │ │ ├── alarm 告警
│ │ │ │ ├── alarm.go
│ │ │ │ └── watcher.go
│ │ │ ├── handler C层
│ │ │ │ ├── greet_one.go
│ │ │ │ ├── greet_two.go
│ │ │ │ └── groups.go
│ │ │ ├── middleware 中间件
│ │ │ │ ├── logging.go
│ │ │ │ ├── metrics.go
│ │ │ │ └── trace.go
│ │ │ └── subscriber 订阅者
│ │ │ ├── chain.go
│ │ │ ├── msg_handler.go
│ │ │ ├── retry_consumer.go
│ │ │ └── subscriber.go
│ │ └── main.go
│ ├── cron 定时任务
│ │ ├── internal
│ │ │ ├── handler C层
│ │ │ │ ├── greet.go
│ │ │ │ ├── hello.go
│ │ │ │ └── set.go
│ │ │ ├── job 作业
│ │ │ │ ├── chain.go
│ │ │ │ ├── job.go
│ │ │ │ └── list.go
│ │ │ └── middleware 中间件
│ │ │ ├── logging.go
│ │ │ └── trace.go
│ │ └── main.go
│ └── micro 微服务
│ ├── internal
│ │ ├── endpoint C层
│ │ │ ├── check_perm.go
│ │ │ ├── check_token.go
│ │ │ └── validate.go
│ │ ├── grpc 通信层
│ │ │ ├── check_perm.go
│ │ │ ├── check_token.go
│ │ │ ├── grpc_health_v1
│ │ │ │ ├── health.pb.go
│ │ │ │ ├── health.proto
│ │ │ │ ├── health_grpc.pb.go
│ │ │ │ ├── health_impl.go
│ │ │ │ └── protoc.sh
│ │ │ ├── pb
│ │ │ │ ├── protoc.sh
│ │ │ │ ├── server.pb.go
│ │ │ │ ├── server.proto
│ │ │ │ └── server_grpc.pb.go
│ │ │ └── set.go
│ │ ├── middleware 中间件
│ │ │ ├── auth
│ │ │ │ ├── context.go
│ │ │ │ ├── middleware.go
│ │ │ │ ├── option.go
│ │ │ │ └── token.go
│ │ │ ├── chain.go
│ │ │ ├── logging
│ │ │ │ └── middleware.go
│ │ │ └── metrics
│ │ │ └── middleware.go
│ │ ├── output 输出
│ │ │ └── format.go
│ │ └── param 参数
│ │ ├── check_perm.go
│ │ └── check_token.go
│ ├── main.go
│ └── pkg SDK
│ ├── check_perm.go
│ ├── check_token.go
│ ├── err.go
│ └── sdk.go
├── configs 配置文件
│ ├── application-example.yml
│ └── application.yml
├── deployments 部署
│ ├── docker
│ │ ├── docker-compose-example.yml
│ │ ├── docker-compose.yml
│ │ ├── prometheus-example.yml
│ │ └── prometheus.yml
│ ├── nav-example.html
│ └── nav.html
├── docs 文档
├── go.mod
├── go.sum
├── internal
│ ├── cache 缓存
│ │ ├── cache.go
│ │ ├── dict.go
│ │ ├── key.go
│ │ ├── local.go
│ │ ├── local_test.go
│ │ ├── miss.go
│ │ ├── nop.go
│ │ ├── protector.go
│ │ ├── redis.go
│ │ ├── redis_test.go
│ │ └── serialization.go
│ ├── conf 配置
│ │ └── attr.go
│ ├── db 数据库
│ │ ├── gorm.go
│ │ ├── mongo.go
│ │ └── zapgorm2
│ │ └── zapgorm2.go
│ ├── discovery 服务发现
│ │ └── consul.go
│ ├── errcode 错误
│ │ ├── business.go
│ │ ├── micro.go
│ │ ├── resp.go
│ │ └── restful.go
│ ├── layer 分层
│ │ ├── controller.go
│ │ ├── repository.go
│ │ └── service.go
│ ├── log 日志
│ │ └── zap.go
│ ├── mq 消息
│ │ ├── greet.go
│ │ └── greet_test.go
│ ├── producer 队列生产
│ │ ├── sarama_producer.go
│ │ └── scram_client.go
│ ├── repository DAO层
│ │ ├── app.go
│ │ ├── app_test.go
│ │ ├── student.go
│ │ └── student_test.go
│ ├── sdk SDK引用
│ │ └── demo.go
│ ├── service service层
│ │ ├── acl.go
│ │ ├── app.go
│ │ └── app_test.go
│ ├── status 状态
│ │ ├── ginpprof.go
│ │ ├── hystrix.go
│ │ └── prometheus.go
│ ├── trace 链路追踪
│ │ └── zipkin.go
│ └── util 工具
│ ├── file.go
│ ├── jsontime.go
│ ├── md5.go
│ └── test.go
├── scripts 脚本
│ ├── init.sql
│ ├── init.sh
└── test
└── grpc_client 微服务本地测试
└── main.go
cmd/ 默认拥有4个子目录,对应4个入口、4种作业类型。
├── cmd
│ ├── backend Restful服务
│ │ ├── README.md
│ │ ├── docs
│ │ ├── internal
│ │ └── main.go
│ ├── consumer 队列消费
│ │ ├── internal
│ │ └── main.go
│ ├── cron 定时任务
│ │ ├── internal
│ │ └── main.go
│ └── micro 微服务
│ ├── internal
│ ├── main.go
│ └── pkg
入口专用的代码存放于 cmd/ 的子目录中,跨入口公用的代码存放于 internal/ 下,例如:
同一种作业类型,可以创建多个入口。例如:项目中允许存在backend
与frontend
两个Restful服务。
示例1:添加frontend
入口
cp -a cmd/backend cmd/frontend
cmd/frontend
路径下批量替换:backend=>frontend
、Backend=>Frontend
示例2:删除frontend
入口
rm -rf cmd/frontend
步骤1:安装Go环境以及所依赖的服务,详见 deployments/README.md 。
步骤2:执行init脚本,键入本机IP,生成 configs/application.yml
。
cd scripts
./init.sh
步骤3:按需修改 configs/application.yml
。
步骤4:执行 go run cmd/{xyz}/main.go
。
文件路径
configs/application.yml
程序运行目录/application.yml
文件结构
stub详解
一级 | 二级 | 说明 | 示例 |
---|---|---|---|
host | 本机IP地址 | 192.168.1.100 | |
configType | file:本地文件 consul:配置中心 | consul | |
dataKey | consul kv键名 | config/luca/attr | |
consul | |||
host | consul主机 | 192.168.1.100 | |
port | consul端口 | 8500 |
本地文件示例
application.yml
# === stub part ===
host: {ip}
configType: file
dataKey:
consul:
host:
port:
# === detail part ===
project: luca
mysql:
user: root
pwd: '123456'
host: {ip}
port: 3306
db: luca
配置中心示例
application.yml
# === stub part ===
host: {ip}
configType: consul
dataKey: config/luca/attr
consul:
host: {ip}
port: 8500
访问consul后台,配置KV字典如下:
< Key/Values < config < luca
attr
Value
# === detail part ===
project: luca
mysql:
user: root
pwd: '123456'
host: {ip}
port: 3306
db: luca
detail详解
一级 | 二级 | 说明 | 示例 |
---|---|---|---|
project | 项目名 | luca | |
env | 环境:dev/demo/test/pre/pro | dev | |
consoleLog | true:控制台日志 false:文件日志 |
true | |
pprof | |||
user | 用户名 | qjs | |
pwd | 密码 | 731 | |
mysql | |||
user | 用户名 | root | |
pwd | 密码 | 123456 | |
host | 主机 | 192.168.1.100 | |
port | 端口 | 3306 | |
db | 数据库 | luca | |
maxOpenConns | 最大连接数 | 30 | |
maxIdleConns | 最大空闲连接数 | 10 | |
mongo | |||
user | 用户名 | woood2 | |
pwd | 密码 | 123456 | |
host | 主机 | 192.168.1.100 | |
port | 端口 | 27017 | |
db | 数据库 | luca | |
maxPoolSize | 最大连接数 | 30 | |
minPoolSize | 最小连接数 | 10 | |
redis | |||
addr | 地址 | 192.168.1.100:6379 | |
pwd | 密码 | ||
poolSize | 最大连接数 | 30 | |
minIdleConns | 最小空闲连接数 | 10 | |
zipkin | |||
reporterAddr | 上报地址(允许为空) | 192.168.1.100:6379 | |
kafka | |||
version | 版本 | 2.5.0 | |
brokers | broker地址(允许多个) | 192.168.1.100:9092 | |
enableSASL | 是否开启SASL | true | |
user | 用户名 | qjs | |
password | 密码 | 731 | |
algorithm | 算法 | sha256 | |
backend | |||
addr | Restful服务地址 | :8080 | |
register | 是否注册到consul | true | |
pprofAddr | pprof地址 | :6059 | |
metricsAddr | metrics地址 | :8082 | |
hystrixPort | hystrix端口 | 8282 | |
allowOrigins | 允许来源(允许多个) | * | |
micro | |||
port | 端口 | 8081 | |
register | 是否注册到consul | true | |
pprofAddr | pprof地址 | :6060 | |
metricsAddr | metrics地址 | :6061 | |
hystrixPort | hystrix端口 | 8283 | |
consumer | |||
pprofAddr | pprof地址 | :6064 | |
metricsAddr | metrics地址 | :6063 | |
hystrixPort | hystrix端口 | 8084 | |
cron | |||
pprofAddr | pprof地址 | :6065 | |
hystrixPort | hystrix端口 | 8085 |
luca倡导在项目中基于 github.com/pkg/errors 传递 error,而非使用 panic-recover。
统一的错误字典
为了对调用者友好,微服务SDK冗余了一份错误字典
更新方式
cd cmd/micro/pkg
./update_err.sh
Resp结构体:internal/errcode/resp.go
package errcode
//4 restful & micro service
type Resp struct {
ErrCode string `json:"errCode"`
ErrMsg string `json:"errMsg"`
Data interface{} `json:"data"`
TraceID string `json:"traceID"` //only 4 restful
Stack string `json:"stack"` //only 4 restful, non-pro env
}
一次Restful请求
的错误处理
error
对象被逐层传递到最上层的controller
中。error
对象的controller
前往错误字典
查找对应的错误代码
,并封装成Resp
对象作为http返回值。错误代码
对应一条程序分支,通常由用户的错误操作引发,例如"用户名或密码错误",无需记录日志,http code
为200。错误代码
的error
统一归为errcode.ServerErr
并且记录一条error等级的日志,http code
为500。Resp.ErrCode
与Resp.ErrMsg
均为空值,Resp.Data
携带返回值正文,http code
为200。一次微服务调用
的错误处理
error
对象被逐层传递到最上层的endpoint
中。error
对象的endpoint
前往错误字典
查找对应的错误代码
,并封装成Resp
对象作为返回值。错误代码
对应一条程序分支,通常由client的非法调用引发,例如"非法的鉴权凭证",无需记录日志,无需对接hystrix
。错误代码
的error
需要记录一条error等级的日志,并且对接hystrix
。Resp.ErrCode
与Resp.ErrMsg
均为空值,Resp.Data
携带返回值正文。Crash-free
panic-recover
,但无法保证第三方库没有panic
,必须在goroutine
级别进行recover
,保障整个进程的运行安全。panic
后,任何作业类型均会记录一条error等级的日志。panic
,http code
为500。panic
,需要对接hystrix
,error日志由调用端(SDK)负责记录。luca基于 gin 提供Restful服务。
示例
基于 gin-contrib/cors 支持CORS。
允许多个来源:
backend:
allowOrigins:
- "http://google.com"
- "http://facebook.com"
允许任意来源:
backend:
allowOrigins:
- "*"
基于 swaggo/swag 支持API文档。
示例
cd cmd/backend && swag init
luca 基于 go-kit 提供micro service。
详细选型
API示例
SDK示例
本地测试
luca基于 robfig-cron 提供定时任务。
基于redis实现Secondary Nodes
模式的高可用,详见 main.go 中的campaign()
与renew()
。
job示例
luca 基于 Shopify/sarama 支持 Kafka。
application.yml
kafka:
version: 2.5.0
brokers:
- {ip}:9092
enableSASL: true
user: qjs
password: '731'
algorithm: sha256
示例
推荐方案
controller层包括:
service 层涵盖:
repository 层负责操作ORM层,luca选择 GORM 作为ORM框架。
sdk 或 kafka producer 可视作远程service。
在luca中,依赖注入随处可见:
被依赖的对象 | 接收注入的对象 |
---|---|
logger | 任何需要记录日志的代码 |
db(mysql、redis、mongo等) | repository |
consul client | sdk |
kafka producer | controller、service |
sdk | controller、service |
service | controller |
repository | service |
在Spring的世界中,使用@Autowired
即可自动装配。但是在luca中,我们需要在以下位置手动装配:
luca选择 GORM 作为ORM框架。
application.yml
mysql:
user: root
pwd: '123456'
host: {ip}
port: 3306
db: luca
maxOpenConns: 30
maxIdleConns: 10
示例
luca 基于 mongo-go-driver 操作MongoDB。
application.yml
mongo:
user: woood2
pwd: '123456'
host: {ip}
port: 27017
db: luca
maxPoolSize: 30
minPoolSize: 10
示例
luca提供了统一的 Cache 接口以及3种默认实现:
其中,RedisCache 基于 go-redis 操作Redis,对应配置:
redis:
addr: {ip}:6379
pwd: ''
poolSize: 30
minIdleConns: 10
默认采用json序列化,允许修改 serialization.go 替换成其它序列化算法。
示例
日志维度
日志维度 | 说明 | 日志库 | 输出 | 接入 zipkin | 接入 EFK |
---|---|---|---|---|---|
服务日志 | 服务运行周期中的启动、关闭和状态日志 | 标准库 log | stdout/stderr | 否 | 否 |
请求日志 | 请求/作业周期内产生的日志 | uber-go/zap | stdout/stderr或者./zap.log文件 | 是 | 是 |
application.yml(zap配置)
名称 | 说明 |
---|---|
env | pro: 日志等级Info 其它: 日志等级Debug |
consoleLog | true: 控制台日志 false: 文件日志(./zap.log) |
内置日志点(zap logger)
围绕 | 类型 | 说明 | 等级 |
---|---|---|---|
请求/作业 | |||
access日志 | 覆盖所有作业类型 | info | |
error日志 | 覆盖所有作业类型 | error | |
oplog日志 | 用于Restful服务 | info | |
GORM | |||
sql日志 | debug | ||
slow日志 | warn | ||
error日志 | error | ||
SDK | |||
call日志 | debug | ||
error日志 | error |
luca 选择 zipkin 作为链路追踪的实现引擎。
跟踪范围
从 gin.Context 提取 TraceID:
func (ctr *AnyController) Demo(c *gin.Context) {
traceID := trace.GinID(c)
...
}
将 gin.Context 转换为 context.Context
func (ctr *AnyController) Demo(c *gin.Context) {
ctx:=trace.Ctx(c)
...
}
从 context.Context 提取 TraceID:
func demo(ctx context.Context) {
traceID := trace.ID(ctx)
...
}
向 GORM logger 传递 TraceID:
func (repo *AnyRepoImpl) Create(ctx context.Context, entity *Any) (int, error) {
result := repo.DB.WithContext(ctx).Create(entity)
...
}
从 Restful 响应读取 TraceID:
package errcode
type Resp struct {
ErrCode string `json:"errCode"`
ErrMsg string `json:"errMsg"`
Data interface{} `json:"data"`
TraceID string `json:"traceID"`
Stack string `json:"stack"`
}
application.yml
一级 | 二级 | 说明 | 示例 |
---|---|---|---|
zipkin | |||
reporterAddr | 上报地址(允许为空) | http://192.168.0.100:9411/api/v2/spans |
zipkin server
deployments/nav.html
luca 选择 EFK 日志收集方案:
EFK 安装、使用
支持 pprof 的Go进程:
application.yml
pprof:
user: qjs
pwd: '731'
backend:
pprofAddr: ":6059"
micro:
pprofAddr: ":6060"
consumer:
pprofAddr: ":6064"
cron:
pprofAddr: ":6065"
访问
deployments/nav.html
luca 为3种作业类型接入 prometheus 指标统计:
application.yml
backend:
metricsAddr: ":8082"
micro:
metricsAddr: ":6061"
consumer:
metricsAddr: ":6063"
安装 prometheus
访问 prometheus
deployments/nav.html
推荐方案
环境 | env | 部署位置 | 部署套数 | 用途 | git分支 | zap等级 | swagger |
---|---|---|---|---|---|---|---|
本机 | dev | 本机 | 1/每人 | 开发 | 个人、feature、fixbug | Debug | 开启 |
dev | dev | 远程 | 1 | 联调 | dev | Debug | 开启 |
demo | demo | 远程 | 1 | 对客演示 | demo | Debug | 开启 |
test | test | 远程 | >=1 | 测试 | test | Debug | 开启 |
pre | pre | 远程 | 1 | 发版演练 | pre | Debug | 开启 |
pro | pro | 远程 | >=1 | 生产 | master | Info | 关闭 |
本机 & dev
demo
git 分支策略
。git 分支策略
git 分支命名
分支 | 命名规则 | 示例 |
---|---|---|
主分支 | master | - |
开发 | dev | - |
演示 | demo | - |
测试 | test | - |
预生产 | pre | - |
feature | {name}-feature | first-feature、second-feature |
fixbug | {name}-fixbug | stackoverflow-fixbug |
个人 | 姓名全拼、缩写 | qiujiashu、qjs |
支持优雅关闭的Go进程
工作原理
luca支持微服务及Restful服务的灰度发布,以实现程序的不停服更新。
前提
工作原理
luca鼓励开发者采用主流的table driven
模式进行单元测试。
单元测试的优先目标是计算逻辑,而不是IO。对于一般的CRUD,单元测试意义不大,API测试是更适合的选择。
luca支持在CI/CD中跳过指定的单元测试:
CLI
CI=true go test -v -cover ./...
internal/util/test.go
package util
import (
"os"
"testing"
)
func CI() bool {
return os.Getenv("CI") != ""
}
func SkipCI(t *testing.T) {
if CI() {
t.Skip("Skipping testing in CI environment")
}
}
xxx_test.go
func TestMain(m *testing.M) {
if !util.CI() {
attr := conf.Load("application.yml", "configs/application.yml")
..
}
m.Run()
}
func TestXXX(t *testing.T) {
util.SkipCI(t)
..
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。