项目结构
studysystem_micro
├─ cmd
│ └─ user
│
├─ go.mod
├─ go.sum
├─ idl
│ ├─ user
│ │ ├─ user.pb.go
│ │ └─ user_grpc.pb.go
│ └─ user.proto
├─ internal
│ ├─ gateway
│ │ ├─ controller
│ │ │ ├─ api
│ │ │ │ └─ user
│ │ │ │ └─ user.go
│ │ │ └─ vo
│ │ │ └─ user.go
│ │ └─ route
│ │ └─ route.go
│ └─ service
│ └─ user
│ ├─ config
│ │ └─ gen.go
│ ├─ dao
│ │ ├─ email.go
│ │ └─ user.go
│ ├─ init.go
│ ├─ logs
│ ├─ model
│ │ └─ user.go
│ └─ service.go
├─ main.go
├─ pkg
│ ├─ init
│ │ ├─ config
│ │ │ └─ init.go
│ │ ├─ log
│ │ │ └─ log.go
│ │ ├─ serviceCenter
│ │ │ └─ consul.go
│ │ ├─ sql
│ │ │ ├─ mysql.go
│ │ │ ├─ pgsql.go
│ │ │ └─ redis.go
│ │ ├─ statuscode
│ │ │ └─ statuscode.go
│ │ └─ tracing
│ │ └─ tracing.go
│ └─ utils
│ ├─ encrypt.go
│ ├─ getlocalip.go
│ ├─ jwt.go
│ ├─ rand.go
│ └─ snowflake.go
├─ README.md
├─ rpc
│ ├─ config.go
│ ├─ initrpc.go
│ └─ user.go
└─ test
├─ configinit_test.go
├─ getwd_test.go
└─ user_test.gob
cmd存储的是每个服务的启动主程序;idl存放proto文件以及根据proto文件生成的grpc调用的文件;internal存放每个服务的业务网关以及中间件;pkg存放的是需要的工具包,以及初始化的工具,rpc注册grpc的客户端.
配置中心
nacos作为注册中心
准备环境
###用docker安装nacos
#下载镜像
docker pull nacos/nacos-server
#查看镜像
docker images
#运行容器
docker run --env MODE=standalone --name mynacos -d -p 8848:8848 docker.io/nacos/nacos-server
#查看启动日志,如果有successful字样就证明启动成功
docker logs -f mynacos
连接nacos获取配置
//连接nacos
package config
import (
"fmt"
"log"
"reflect"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
)
const (
nacos_Host = "xxx.xxx.x.xx"//你的nacos所在ip地址
nacos_Port = 8848//nacos部署端口
)
// 每个服务初始化配置
func Init_config(data_id string, group string, value any) {//value结构初始化的结构体
sc := []constant.ServerConfig{{
IpAddr: nacos_Host,
Port: nacos_Port,
}}
cc := constant.ClientConfig{
NamespaceId: "232e51d6-1528-43b4-ab13-aa69039c7886", // 如果需要支持多namespace,我们可以场景多个client,它们有不同的NamespaceId。当namespace是public时,此处填空字符串。
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "log",
CacheDir: "cache",
LogLevel: "debug",
}
configClient, err := clients.CreateConfigClient(map[string]interface{}{
"serverConfigs": sc,
"clientConfig": cc,
})
if err != nil {
fmt.Println(err.Error())
}
content, err := configClient.GetConfig(vo.ConfigParam{
DataId: data_id,
Group: group,
})
SetConfig(content, value)
if err != nil {
fmt.Println(err.Error())
}
err = configClient.ListenConfig(vo.ConfigParam{
DataId: data_id,
Group: group,
OnChange: func(namespace, group, dataId, data string) {
fmt.Println("配置文件发生了变化...")
fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + data)
SetConfig(data, value)
},
})
if err != nil {
fmt.Println(err.Error())
}
}
//这里通过反射将配置数据绑定到结构体中
func SetConfig(content any, value any) {
tval := reflect.TypeOf(value)
if tval.Kind() != reflect.Ptr {
log.Fatal("非指针类型")
return
}
rval := reflect.ValueOf(value)
val := reflect.ValueOf(content)
rval.Method(0).Call([]reflect.Value{val})
}
测试案例
package test
import (
"bytes"
"fmt"
"testing"
"studysystem_micro/pkg/init/config"
"github.com/spf13/viper"
)
type Test1 struct {
Test `yaml:"test"`
}
type Test struct {
A int `yaml:"a"`
}
func (a *Test1) BindData(c string) {
fmt.Println(c)
var runtime_viper = viper.New()
runtime_viper.SetConfigType("yaml")
runtime_viper.ReadConfig(bytes.NewBuffer([]byte(c)))
runtime_viper.Unmarshal(a)
}
func TestXxx(t *testing.T) {
v := new(Test1)
config.Init_config("test", "DEFAULT_GROUP", v)
fmt.Println(v)
}
注册中心
本项目用consul作为注册中心(用nacos也可以)
准备环境
###用docker安装consul
#下载镜像
docker pull consul
#查看镜像
docker images
#运行容器
docker run --name consul1 -d -p 8500:8500 consul agent -server -bootstrap-expect=1 -ui -bind=0.0.0.0 -client=0.0.0.0
连接consul注册服务和获取服务
package serviceCenter
import (
"fmt"
"github.com/hashicorp/consul/api"
)
// consul 定义一个consul结构体,其内部有一个`*api.Client`字段。
type consul struct {
client *api.Client
}
// NewConsul 连接至consul服务返回一个consul对象
func NewConsul(addr string) (*consul, error) {
cfg := api.DefaultConfig()
cfg.Address = addr
c, err := api.NewClient(cfg)
if err != nil {
return nil, err
}
return &consul{client: c}, nil
}
// RegisterService 将gRPC服务注册到consul
func (c *consul) RegisterService(serviceName string, ip string, port int) error {
// 健康检查
check := &api.AgentServiceCheck{
GRPC: fmt.Sprintf("%s:%d", ip, port), // 这里一定是外部可以访问的地址
Timeout: "10s", // 超时时间
Interval: "10s", // 运行检查的频率
// 指定时间后自动注销不健康的服务节点
// 最小超时时间为1分钟,收获不健康服务的进程每30秒运行一次,因此触发注销的时间可能略长于配置的超时时间。
DeregisterCriticalServiceAfter: "1m",
}
srv := &api.AgentServiceRegistration{
ID: fmt.Sprintf("%s-%s-%d", serviceName, ip, port), // 服务唯一ID
Name: serviceName, // 服务名称
Tags: []string{"grpc", "consul"}, // 为服务打标签
Address: ip,
Port: port,
Check: check,
}
return c.client.Agent().ServiceRegister(srv)
}
// Deregister 注销服务
func (c *consul) Deregister(serviceName string, ip string, port int) error {
return c.client.Agent().ServiceDeregister(fmt.Sprintf("%s-%s-%d", serviceName, ip, port))
}
客户端连接
package test
import (
"context"
"fmt"
pb "studysystem_micro/idl/user"
"testing"
grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
_ "github.com/mbobakov/grpc-consul-resolver"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func TestUser(t *testing.T) {
conn, err := grpc.Dial("consul://xxx.xxx.x.xx:8500/user?healthy=true", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`), grpc.WithChainUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor()))
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewUserClient(conn)
v, _ := client.GetAuthCode(context.Background(), &pb.UserGetAuthCode{
Email: "2745969694@qq.com",
})
fmt.Println(v)
}
链路追踪
package tracing
import (
"io"
"os"
opentracing "github.com/opentracing/opentracing-go"
jaeger "github.com/uber/jaeger-client-go"
con "github.com/uber/jaeger-client-go/config"
)
//服务名称,jaeger所在主机ip,端口
func InitTracer(service string, host string, port string) (opentracing.Tracer, io.Closer) {
os.Setenv("JAEGER_AGENT_HOST", host)
os.Setenv("JAEGER_AGENT_PORT", port)
cfg, err := con.FromEnv()
if err != nil {
panic(err)
}
cfg.ServiceName = service
cfg.Sampler.Type = "const"
cfg.Sampler.Param = 1
cfg.Reporter.LogSpans = true
tracer, closer, err := cfg.NewTracer(con.Logger(jaeger.StdLogger))
if err != nil {
panic(err)
}
return tracer, closer
}
//服务注册添加如下代码
tracer, closer := tracing.InitTracer(gen.C.Server.Name, gen.C.Tracing.Host, gen.C.Tracing.Port)
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
网关
//这边用gin作为微服务的统一网关,用法跟正常单体的web项目差不多
import "github.com/gin-gonic/gin"
数据库(不止数据库)
//用gorm,像redis,rebbitmq这些中间件就用就不一一介绍了
package sql
import (
"fmt"
"reflect"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var db *gorm.DB
func GetDB() *gorm.DB {
return db
}
//这里用反射将结构体的数据提取出来
func InitMysql(val any) {
t := reflect.TypeOf(val)
if t.Kind() != reflect.Struct {
fmt.Println("非结构体类型")
return
}
v := reflect.ValueOf(val)
dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", v.Field(0).String(), v.Field(1).String(), v.Field(2).String(), v.Field(3).String(), v.Field(4).String())
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
if err != nil {
fmt.Println(err)
}
}
后面还有细节啥的,比如上框架,中间件这些,后面有时间再补,如果看完觉得有用的话麻烦帮小编点个赞啦
下面是我用这个项目结构写的一个项目,可以拿来参考参考,麻烦看官顺便点个star呦👉 项目参考链接