Go微服务实现:GRPC
原创大约 5 分钟
模型实体
package model
import (
"gorm.io/gorm"
"time"
)
type BaseModel struct {
ID int32 `gorm:"primaryKey"`
Createtime string `gorm:"type:timestamp"`
Deletetime gorm.DeletedAt // 用于软删除字段的
Isdeleted bool
}
type User struct {
BaseModel
Username string `gorm:"index:idx_mobile;unique;type:char(11);not null"` // 手机号
Password string `gorm:"type:varchar(256);not null"`
Nickname string `gorm:"type:varchar(32);"`
Email string `gorm:"type:varchar(64);"`
Birthday *time.Time `gorm:"type:datetime"`
Gender int32 `gorm:"type:tinyint(1) comment '0女,1男';default:1"`
Role int32 `gorm:"type:tinyint(1) comment '1普通用户,2管理员';default:1"`
}
配置结构体
package config
type MySQLConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
User string `mapstructure:"user" json:"user"`
Password string `mapstructure:"password" json:"password"`
Database string `mapstructure:"database" json:"database"`
}
type ServerConfig struct {
Env string `mapstructure:"env" json:"env"`
Name string `mapstructure:"name" json:"name"`
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
MySQLInfo MySQLConfig `mapstructure:"mysql" json:"mysql"`
}
全局变量
package global
import (
"gomicroservice/usersrv/config"
"gorm.io/gorm"
)
const (
ENVIRONMENT_DEV = "dev"
ENVIRONMENT_PRO = "pro"
)
var (
DB *gorm.DB
ServerConfig *config.ServerConfig = new(config.ServerConfig)
)
初始化日志
package initialize
import (
"go.uber.org/zap"
"gomicroservice/userapi/utils"
)
// InitialLogger 初始化日志组件
func InitialLogger() {
logger, _ := utils.GlobalLogger()
zap.ReplaceGlobals(logger)
}
package utils
import (
"go.uber.org/zap"
)
// GlobalLogger 日志工具
func GlobalLogger() (*zap.Logger, error) {
// 生产环境会打印JSON格式的日志,而开发环境不会
cfg := zap.NewProductionConfig()
//cfg := zap.NewDevelopmentConfig()
cfg.OutputPaths = []string{
"/Users/bear/home/work/logs/user_api.log", // 输出到文件
//"stderr", // 输出到标准错误流
"stdout", // 输出到控制台
}
return cfg.Build()
}
初始化配置
package initialize
import (
"encoding/json"
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
"go.uber.org/zap"
"gomicroservice/usersrv/global"
)
// InitialConfiguration 初始化配置
func InitialConfiguration(env string) {
vip := viper.New()
switch env {
case "dev":
vip.SetConfigFile("usersrv/config-dev.yaml")
case "pro":
vip.SetConfigFile("usersrv/config-pro.yaml")
default:
vip.SetConfigFile("usersrv/config-dev.yaml")
}
if err := vip.ReadInConfig(); err != nil {
zap.S().Panicf("读取配置文件失败:%s", err.Error())
}
// 解析出配置文件
if err := vip.Unmarshal(global.NacosConfig); err != nil {
zap.S().Panicf("解析配置文件失败:%s", err.Error())
}
zap.S().Infof("配置文件信息:%v", global.NacosConfig)
// 动态监控变化
vip.WatchConfig()
vip.OnConfigChange(func(in fsnotify.Event) {
_ = vip.ReadInConfig()
_ = vip.Unmarshal(global.NacosConfig)
zap.S().Infof("配置文件修改了:%v", global.NacosConfig)
})
}
初始化数据库连接
package initialize
import (
"fmt"
"gomicroservice/usersrv/global"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"log"
"os"
"time"
)
// InitialDatabase 初始化数据库
func InitialDatabase() {
mysqlInfo := global.ServerConfig.MySQLInfo
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
mysqlInfo.User, mysqlInfo.Password, mysqlInfo.Host, mysqlInfo.Port, mysqlInfo.Database)
// 设置全局的logger,打印每一行sql
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Warn, // Log level
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
ParameterizedQueries: true, // Don't include params in the SQL log
Colorful: true, // Disable color
},
)
var err error
global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
// 禁用外键约束
DisableForeignKeyConstraintWhenMigrating: true,
NamingStrategy: schema.NamingStrategy{
// 表名前缀
TablePrefix: "sys_",
// 禁用默认表名复数
SingularTable: true,
},
Logger: newLogger,
})
if err != nil {
panic("failed to connect database")
}
}
启动代码
package main
import (
"fmt"
"go.uber.org/zap"
"gomicroservice/usersrv/global"
"gomicroservice/usersrv/handler"
"gomicroservice/usersrv/initialize"
"gomicroservice/usersrv/middleware"
"gomicroservice/usersrv/proto"
"gomicroservice/usersrv/utils"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"net"
"os"
"os/signal"
"syscall"
)
/*
config:配置文件
global:全局变量
handler:proto定义的接口实现
initialize:初始化
middleware:中间件服务调用
model:数据库表模型
proto:proto文件及其生成的go源码
tests:测试代码
utils:工具类
*/
// grpc用户服务
func main() {
// 1. 初始化日志
initialize.InitialLogger()
// 2. 初始化配置
initialize.InitialConfiguration(global.ENVIRONMENT_DEV)
// 3. 初始化数据库连接
initialize.InitialDatabase()
// 4. 启动grpc服务
// 生产环境才允许动态获取接口
if global.ENVIRONMENT_PRO == global.ServerConfig.Env {
port, err := utils.GetAvailablePort()
if err == nil {
global.ServerConfig.Port = port
}
}
zap.S().Infof("ip:%s", global.ServerConfig.Host)
zap.S().Infof("port:%d", global.ServerConfig.Port)
server := grpc.NewServer()
proto.RegisterUserServiceServer(server, &handler.UserServiceServer{})
list, err := net.Listen("tcp", fmt.Sprintf("%s:%d",
global.ServerConfig.Host, global.ServerConfig.Port))
if err != nil {
zap.S().Panicf("监听用户grpc服务失败:" + err.Error())
}
err = server.Serve(list)
if err != nil {
zap.S().Panicf("启动用户grpc服务失败:" + err.Error())
}
}
服务实现
package handler
import (
"context"
"crypto/md5"
"encoding/hex"
"gomicroservice/usersrv/global"
"gomicroservice/usersrv/model"
"gomicroservice/usersrv/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"gorm.io/gorm"
"io"
"time"
)
type UserServiceServer struct{}
// Paginate 分页
func Paginate(page, size int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if page <= 0 {
page = 1
}
switch {
case size > 100:
size = 100
case size <= 0:
size = 10
}
offset := (page - 1) * size
return db.Offset(offset).Limit(size)
}
}
// Model2Response 填充返回值
func Model2Response(user model.User) proto.UserInfo {
// grpc的message字段有默认值,如果赋值nil,序列化时会报错
userInfo := proto.UserInfo{
Id: uint32(user.ID),
Username: user.Username,
Password: user.Password,
Nickname: user.Nickname,
Gender: uint32(user.Gender),
Role: uint32(user.Role),
}
if user.Birthday != nil {
userInfo.Birthday = uint64(user.Birthday.Unix())
}
return userInfo
}
// List 获取用户的列表
func (server *UserServiceServer) List(ctx context.Context, page *proto.Page) (*proto.UserList, error) {
// 实例化
var users []model.User
var userList = &proto.UserList{}
// 获取全局的数据库连接
result := global.DB.Find(&users)
if result.Error != nil {
return nil, status.Errorf(codes.Unknown, "查询用户列表失败")
}
// 查询到的用户数量
userList.Total = uint32(result.RowsAffected)
// 分页
global.DB.Scopes(Paginate(int(page.Page), int(page.Size))).Find(&users)
// 填充返回值
for _, user := range users {
userInfo := Model2Response(user)
userList.Data = append(userList.Data, &userInfo)
}
return userList, nil
}
// InfoByName 通过用户名获取用户详情
func (server *UserServiceServer) InfoByName(ctx context.Context, request *proto.UsernameRequest) (*proto.UserInfo, error) {
// 实例化
var user model.User
// 获取全局的数据库连接
result := global.DB.Where(&model.User{Username: request.Username}).First(&user)
if result.Error != nil || result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "用户不存在")
}
userInfo := Model2Response(user)
return &userInfo, nil
}
// InfoById 通过ID获取用户详情
func (server *UserServiceServer) InfoById(ctx context.Context, request *proto.UseridInfo) (*proto.UserInfo, error) {
// 实例化
var user model.User
// 获取全局的数据库连接
result := global.DB.First(&user, request.Id)
if result.Error != nil || result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "用户不存在")
}
userInfo := Model2Response(user)
return &userInfo, nil
}
// Register 注册用户
func (server *UserServiceServer) Register(ctx context.Context, request *proto.CreateUserInfo) (*proto.UserInfo, error) {
// 先查询用户是否存在
var user model.User
result := global.DB.Where(&model.User{Username: request.Username}).First(&user)
if result.RowsAffected != 0 {
return nil, status.Errorf(codes.AlreadyExists, "用户已存在")
}
user.Username = request.Username
user.Nickname = request.Nickname
// 简单md5加密
encrypt := md5.New()
_, _ = io.WriteString(encrypt, request.Password)
user.Password = hex.EncodeToString(encrypt.Sum(nil))
user.Createtime = time.Now().Format("2006-01-02 15:04:05")
// 创建用户
result = global.DB.Create(&user)
if result.Error != nil {
return nil, status.Errorf(codes.Internal, "注册用户失败")
}
userInfo := Model2Response(user)
return &userInfo, nil
}
// Update 更新用户
func (server *UserServiceServer) Update(ctx context.Context, request *proto.UpdateUserInfo) (*emptypb.Empty, error) {
// 先查询用户是否存在
var user model.User
if global.DB.First(&user, request.Id).RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, "用户不存在")
}
user.Nickname = request.Nickname
birthday := time.Unix(int64(request.Birthday), 0)
user.Birthday = &birthday
user.Gender = int32(request.Gender)
user.Role = int32(request.Role)
// 需要更新哪个字段就更新哪个字段
result := global.DB.Model(&user).Select("nickname", "birthday", "gender", "role").Updates(user)
if result.Error != nil {
return nil, status.Errorf(codes.Internal, "更新用户失败")
}
return &emptypb.Empty{}, nil
}
// Password 验证用户密码
func (server *UserServiceServer) Password(ctx context.Context, request *proto.PasswordInfo) (*proto.ValidateResult, error) {
result := md5.New()
_, _ = io.WriteString(result, request.Password)
return &proto.ValidateResult{
Result: request.Encrypt == hex.EncodeToString(result.Sum(nil)),
Errcode: 0,
Message: "密码正确",
}, nil
}
配置文件
env: "env"
name: "user-srv"
host: "127.0.0.1"
port: 9090
mysql:
host: "127.0.0.1"
port: 6379
user: "root"
password: "123456"
database: "gin-user-srv"
感谢支持
更多内容,请移步《超级个体》。