简单的微服务
go开发一个用户服务user_service
众所周知把大象放进冰箱只需三步
那么开发一个基本的user_service需要几步呢
1.首先建表数据库
2.创建protobuf消息
3.创建消息实例接口
4.启动服务
5.测试
具体过程如下:
需要安装的包有和软件有
protobuf https://github.com/protocolbuffers/protobuf
github.com/anaskhan96/go-password-encoder v0.0.0-20201010210601-c765b799fd72
github.com/golang/protobuf v1.5.4
google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.1
gorm.io/driver/mysql v1.5.6
gorm.io/gorm v1.25.10
1.建表建库
用gorm链接数据库建表
定义数据用户数据
model/user.go
// 自定义model
type BaseModel struct {
ID int64 `gorm:"primaryKey"`
CreateAt time.Time `gorm:"column:create_time"`
UpdateAt time.Time `gorm:"column:update_time"`
DeleteAt gorm.DeletedAt
isDeletedAt bool
}
type User struct {
BaseModel
Mobile string `gorm:"index:idx_mobile;unique;type:varchar(11) not null comment '手机号'"`
Password string `gorm:"type:varchar(100) not null comment '密码'"`
Nickname string `gorm:"type:varchar(20) comment '昵称'"`
//注意,当结构体的字段默认值是零值的时候比如 0, '', false,这些字段值将不会被保存到数据库中,你可以使用指针类型或者Scanner/Valuer来避免这种情况。
Birthday *time.Time `gorm:"datetime NOT NULL DEFAULT CURRENT_TIMESTAMP comment '生日'"`
Gender string `gorm:"column:gender;default:male;type:varchar(6) comment 'female女, male 男'"`
Role int `gorm:"column:role;default:1;type:int comment '1普通用户, 2管理员'"`
}
连接mysql建表
参考https://gorm.io/zh_CN/docs/index.html
package global
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
)
var DB *gorm.DB
func Init() {
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Info, // Log level
Colorful: true, // Disable color
},
)
// 同步过程 创建数据
dsn := "admin:123456@tcp(192.168.1.9:3306)/shop_service?charset=utf8mb4&parseTime=True&loc=Local"
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
panic("failed to connect database")
}
if err != nil {
panic("failed to migrate database")
}
}
2.创建message proto/user.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
// 定义接口
service User {
rpc GetUserList(PageInfo) returns (UserListResponse); // 用户列表
rpc GetUseByMobile(MobileRequest) returns (UserInfoResponse); // 根据手机号获取用户信息
rpc GetUserById(IdRequest) returns (UserInfoResponse); // 根据id获取用户信息
rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); // 创建用户
rpc UpdateUser(UpdateUserInfo) returns (google.protobuf.Empty); // 更新用户
rpc CheckPassword(CheckPasswordInfo) returns (CheckResponse); // 验证密码
}
message PageInfo {
uint32 pn = 1;
uint32 pSize = 2;
}
message UserInfoResponse {
int32 id = 1;
string password = 2;
string mobile = 3;
string nickname = 4;
uint64 birthday = 5;
string gender = 6;
int32 role = 7;
}
message UserListResponse {
int32 total = 1;
repeated UserInfoResponse data = 2;
}
message CheckResponse{
bool success = 1;
}
message IdRequest {
string id = 1;
}
message MobileRequest {
string mobile = 1;
}
message CreateUserInfo {
string nickname = 1;
string password = 2;
string mobile = 3;
}
message UpdateUserInfo {
int32 id = 1;
string nickname = 2;
string gender = 3;
uint64 birthday = 4;
}
message CheckPasswordInfo {
string password = 1;
string encryptedPassword = 2;
}
用protoc 创建微服务需要的内容接口
命令
protoc --go_out=. --go-grpc_out=. ./proto/user.proto
会生成两个文件
3.创建实例接口
package handler
import (
"context"
"crypto/sha512"
"fmt"
"gfshop_service/user_serve/global"
"gfshop_service/user_serve/model"
"gfshop_service/user_serve/proto"
"github.com/anaskhan96/go-password-encoder"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gorm.io/gorm"
"strings"
"time"
)
// 这里的proto必须写
type UserServer struct {
proto.UnimplementedUserServer
}
// 这里必须实现接口
func (s *UserServer) mustEmbedUnimplementedUserServer() {
//TODO implement me
panic("implement me")
}
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
if page <= 0 {
page = 1
}
switch {
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
// 将model转为proto
func Model2Response(user model.User) proto.UserInfoResponse {
// 在grpc中 字段有默认值 不能直接赋值nil 容易出错 搞清楚哪个字段有默认值需要处理
userInfoRsp := proto.UserInfoResponse{
Id: int32(user.ID),
Password: user.Password,
Mobile: user.Mobile,
Nickname: user.Nickname,
Gender: user.Gender,
Role: int32(user.Role),
}
if user.Birthday != nil {
userInfoRsp.Birthday = uint64(user.Birthday.Unix())
}
return userInfoRsp
}
// 获取用户列表
func (s *UserServer) GetUserList(ctx context.Context, req *proto.PageInfo) (*proto.UserListResponse, error) {
// 获取用户列表
var users []model.User
result := global.DB.Find(&users)
if result.Error != nil {
return nil, result.Error
}
rsp := &proto.UserListResponse{}
rsp.Total = int32(result.RowsAffected)
global.DB.Scopes(Paginate(int(req.Pn), int(req.PSize))).Find(&users)
for _, user := range users {
userInfoRsp := Model2Response(user)
rsp.Data = append(rsp.Data, &userInfoRsp)
}
return rsp, nil
}
// 通过手机号查询用户
func (s *UserServer) GetUseByMobile(ctx context.Context, req *proto.MobileRequest) (*proto.UserInfoResponse, error) {
var users model.User
// 通过手机号查询用户
result := global.DB.Where(&model.User{Mobile: req.Mobile}).Find(&users)
if result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, "用户不存在")
}
if result.Error != nil {
return nil, result.Error
}
userInfoRsp := Model2Response(users)
return &userInfoRsp, nil
}
// 通过id查询用户
func (s *UserServer) GetUserById(ctx context.Context, req *proto.IdRequest) (*proto.UserInfoResponse, error) {
var users model.User
// 通过id查询用户
result := global.DB.Where("id = ?", req.Id).Find(&users)
if result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, "用户不存在")
}
if result.Error != nil {
return nil, result.Error
}
userInfoRsp := Model2Response(users)
return &userInfoRsp, nil
}
// 新建用户
func (s *UserServer) CreateUser(ctx context.Context, req *proto.CreateUserInfo) (*proto.UserInfoResponse, error) {
var users model.User
// 新建用户
// 密码需要处理一下
// 1.查询用户是否存在
result := global.DB.Where(&model.User{Mobile: req.Mobile}).Find(&users)
if result.RowsAffected == 1 {
return nil, status.Error(codes.AlreadyExists, "用户已存在")
}
if result.Error != nil {
return nil, result.Error
}
// 2.注册
users.Mobile = req.Mobile
users.Nickname = req.Nickname
// 密码加密
//生成salt 和加密后的密码
//Using custom options
options := &password.Options{16, 100, 32, sha512.New}
salt, encodedPwd := password.Encode(req.Password, options)
// 生成新的密码
users.Password = fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd)
// 3.保存用户
result = global.DB.Create(&users)
if result.Error != nil {
return nil, status.Errorf(codes.Internal, "创建用户失败 %s", result.Error.Error())
}
userInfoRsp := Model2Response(users)
// 4.返回用户信息
return &userInfoRsp, nil
}
// 更新用户信息
func (s *UserServer) UpdateUser(ctx context.Context, req *proto.UpdateUserInfo) (*empty.Empty, error) {
// 1.创建用户
var users model.User
// 2.查询用户
//result := global.DB.Where("id = ?", req.Id).Find(&users)
result := global.DB.First(&users, req.Id)
if result.RowsAffected == 0 {
return nil, status.Error(codes.NotFound, "用户不存在")
}
// 转换为time类型
birthDay := time.Unix(int64(req.Birthday), 0)
// 3.更新用户
users.Nickname = req.Nickname
users.Birthday = &birthDay
users.Gender = req.Gender
// 4.保存用户
result = global.DB.Save(&users)
if result.Error != nil {
return nil, status.Errorf(codes.Internal, "更新用户失败 %s", result.Error.Error())
}
return &empty.Empty{}, nil
}
// 检查密码
func (s *UserServer) CheckPassword(ctx context.Context, req *proto.CheckPasswordInfo) (*proto.CheckResponse, error) {
// 校验密码
options := &password.Options{16, 100, 32, sha512.New}
passwordInfo := strings.Split(req.EncryptedPassword, "$")
check := password.Verify(req.Password, passwordInfo[2], passwordInfo[3], options)
return &proto.CheckResponse{
Success: check,
}, nil
}
这里有md5加密的内容需要的可以百度了解一下
4.可以启动服务啦
main.go
package main
import (
"flag"
"fmt"
"gfshop_service/user_serve/global"
"gfshop_service/user_serve/handler"
"gfshop_service/user_serve/proto"
"google.golang.org/grpc"
"net"
)
func main() {
// 启动服务
IP := flag.String("IP", "0.0.0.0", "ip地址")
Port := flag.Int("Port", 50051, "port端口号")
flag.Parse()
fmt.Println("ip", *IP)
fmt.Println("port", *Port)
server := grpc.NewServer()
global.Init()
proto.RegisterUserServer(server, &handler.UserServer{})
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
if err != nil {
panic("failed to listen:" + err.Error())
}
err = server.Serve(listen)
if err != nil {
panic("failed to listen:" + err.Error())
}
}
5.测试接口是否创建成功
创建一个test.go
注意先启动服务然后启动测试接口测试
package main
import (
"context"
"fmt"
"gfshop_service/user_serve/proto"
"google.golang.org/grpc"
)
var userClient proto.UserClient
var Conn *grpc.ClientConn
func Init() {
var err error
Conn, err = grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
if err != nil {
panic(err)
}
//defer conn.Close()
userClient = proto.NewUserClient(Conn)
}
func TestGetUserList() {
rsp, err := userClient.GetUserList(context.Background(), &proto.PageInfo{
Pn: 1,
PSize: 2,
})
if err != nil {
panic(err)
}
for _, u := range rsp.Data {
fmt.Println(u.Password, u.Mobile, u.Nickname)
checkRsp, err := userClient.CheckPassword(context.Background(), &proto.CheckPasswordInfo{
Password: "admin123",
EncryptedPassword: u.Password,
})
if err != nil {
panic(err)
}
fmt.Println(checkRsp.Success)
}
}
func main() {
Init()
TestGetUserList()
Conn.Close()
}
成功啦hiahiahia~~~