当前位置:网站首页>12. User microservice
12. User microservice
2022-07-22 00:06:00 【Endless character】
Catalog
- Project directory structure description
One 、 Create user table structure
- 1、 Database creation :
- 2、user_srv\model\user.go
package model
import (
"time"
"gorm.io/gorm"
)
type BaseModel struct {
ID int32 `gorm:"primarykey"`
CreatedAt time.Time `gorm:"column:add_time"`
UpdatedAt time.Time `gorm:"column:update_time"`
DeletedAt gorm.DeletedAt
IsDeleted bool
}
type User struct {
BaseModel
Mobile string `gorm:"index:idx_mobile;unique;type:varchar(11);not null"`
Password string `gorm:"type:varchar(100);not null"`
NickName string `gorm:"type:varchar(20)"`
Birthday *time.Time `gorm:"type:datetime"`
Gender string `gorm:"column:gender;default:male;type:varchar(6) comment 'female For a woman , male Male '"`
Role int `gorm:"column:role;default:1;type:int comment '1 For ordinary users , 2 Represents an administrator '"`
}
- 3、user_srv\model\main\main.go: User name and password can be modified by yourself
package main
import (
"log"
"nd/user_srv/model"
"os"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
func main() {
dsn := "root:[email protected](192.168.124.51:3306)/mxshop_user_srv?charset=utf8mb4&parseTime=True&loc=Local"
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 printing
},
)
// Global mode
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
Logger: newLogger,
})
if err != nil {
panic(err)
}
// transfer schema
_ = db.AutoMigrate(&model.User{
}) // There should be sql sentence
}
- 4、 perform user_srv\model\main\main.go: Use gorm Automatically create user tables
Two 、md5 Salt value encryption
1 - encryption algorithm
- Why md5 Salt value encryption : Because the user's password is saved in the database , We don't want it to be stored in the database in clear text
- Generally, ciphertext is used to save user passwords : Because the password saved by the ciphertext cannot be reversed
- Ciphertext encryption method
- Symmetric encryption : The same key is used for encryption and decryption , hidden danger -> As long as you get the key, you can crack the passwords of all databases
- Asymmetric encryption : The same key is not used for encryption and decryption , If the password is not satisfied, it cannot be reversed
- md5 Algorithm : Strictly speaking, it is not an encryption algorithm , Information summarization algorithm ; It can ensure that the password cannot be reversed ( If the password cannot be reversed , In other words, users cannot retrieve their passwords ; The modern approach is to provide a link for users to reset their passwords )
2 - md5 encryption algorithm
- What is? md5:Message Digest Algorithm 5, Information digest algorithm
- md5 The advantages of the algorithm
- ①. compressibility : Any length of data , Work out md5 The values are fixed in length
- ②. Easy to calculate : From the original data md5 Value is easily
- ③. Resistance to change : Make any changes to the original data , Even if the 1 Bytes ,md5 Values vary widely
- ④. Strong collision : I want to find two different data , So that they have the same md5 value , Very difficult
- ⑤. Irreversibility : Irresolvable
- What is? md5 Add salt
- By generating random numbers and md5 Generating strings to combine
- The database also stores md5 Values and salt value , Verify the correctness of using salt Conduct md5 that will do
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
)
func genMd5(code string) string {
Md5 := md5.New()
_, _ = io.WriteString(Md5, code)
return hex.EncodeToString(Md5.Sum(nil))
}
func main() {
fmt.Println(genMd5("123456"))
}
3 - md5 Salt value encryption
- Use md5 Salt encryption of :https://github.com/anaskhan96/go-password-encoder
package main
import (
"crypto/sha512"
"fmt"
"github.com/anaskhan96/go-password-encoder"
)
func main() {
options := &password.Options{
SaltLen: 16, Iterations: 100, KeyLen: 32, HashFunction: sha512.New}
salt, encodedPwd := password.Encode("generic password", options)
fmt.Println(salt)
fmt.Println(encodedPwd)
check := password.Verify("generic password", salt, encodedPwd, options)
fmt.Println(check) // true
}
// Output
//lJSijLVv22RCvDqP
//04440c79158eaa04e1ef7e1d45b00e6724d264624a75eded308269366c221f13
//true
4 - Stitching algorithm 、salt、encodedPwd
- Algorithm 、salt、encodedPwd:
- Splice this 3 Strings are saved together in the database
- It needs to be separated when parsing
- And need Note that the length after splicing should not exceed the length defined in the database
package main
import (
"crypto/sha512"
"fmt"
"strings"
"github.com/anaskhan96/go-password-encoder"
)
func main() {
options := &password.Options{
SaltLen: 16, Iterations: 100, KeyLen: 32, HashFunction: sha512.New}
salt, encodedPwd := password.Encode("generic password", options)
newPassword := fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd) // pbkdf2-sha512 It's an algorithm , Use $ To splice
fmt.Println(len(newPassword)) // Make sure that the length does not exceed the definition of the database field
fmt.Println(newPassword)
// analysis
passwordInfo := strings.Split(newPassword, "$")
fmt.Println(passwordInfo)
check := password.Verify("generic password", passwordInfo[2], passwordInfo[3], options)
fmt.Println(check) // true
}
3、 ... and 、proto Definition
- user_srv\proto\user.proto: Generate command ->
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative *.proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
service User{
rpc GetUserList(PageInfo) returns (UserListResponse); // User list
rpc GetUserByMobile(MobileRequest) returns (UserInfoResponse); // adopt mobile Query the user
rpc GetUserById(IdRequest) returns (UserInfoResponse); // adopt id Query the user
rpc CreateUser(CreateUserInfo) returns (UserInfoResponse); // Add users
rpc UpdateUser(UpdateUserInfo) returns (google.protobuf.Empty); // Update user
rpc CheckPassWord(PasswordCheckInfo) returns (CheckResponse); // Check the password
}
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 CreateUserInfo {
string nickName = 1;
string passWord = 2;
string mobile = 3;
}
message MobileRequest{
string mobile = 1;
}
message IdRequest {
int32 id = 1;
}
message UpdateUserInfo {
int32 id = 1;
string nickName = 2;
string gender = 3;
uint64 birthDay = 4;
}
message PasswordCheckInfo {
string password = 1;
string encryptedPassword = 2;
}
message CheckResponse{
bool success = 1;
}
Four 、 User interface implementation
1 - Query user list interface
- user_srv\global\global.go: Global initialization db, The password needs to be modified by yourself
package global
import (
"log"
"os"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
var (
DB *gorm.DB
)
func init() {
dsn := "root:[email protected](192.168.0.101:3306)/mxshop_user_srv?charset=utf8mb4&parseTime=True&loc=Local"
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 printing
},
)
// Global mode
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
Logger: newLogger,
})
if err != nil {
panic(err)
}
}
- user_srv\handler\user.go: It's used here gorm The paging function of , And simple transformation
package handler
import (
"context"
"fmt"
"nd/user_srv/global"
"nd/user_srv/model"
"nd/user_srv/proto"
"gorm.io/gorm"
)
type UserServer struct {
proto.UnimplementedUserServer
}
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)
}
}
func ModelToRsponse(user model.User) proto.UserInfoResponse {
// stay grpc Of message The fields in have default values , You can't assign values randomly nil go in , It's easy to make mistakes
// Here we need to find out , Which fields have default values
userInfoRsp := proto.UserInfoResponse{
Id: user.ID,
PassWord: user.Password,
NickName: user.NickName,
Gender: user.Gender,
Role: int32(user.Role),
Mobile: user.Mobile,
}
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) {
// Get the list of users
var users []model.User
result := global.DB.Find(&users)
if result.Error != nil {
return nil, result.Error
}
fmt.Println(" User list ")
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 := ModelToRsponse(user)
rsp.Data = append(rsp.Data, &userInfoRsp)
}
return rsp, nil
}
2 - according to id and mobile Query user interface
- user_srv\handler\user.go
func (s *UserServer) GetUserByMobile(ctx context.Context, req *proto.MobileRequest) (*proto.UserInfoResponse, error) {
// Query users through mobile phone numbers
var user model.User
result := global.DB.Where(&model.User{
Mobile: req.Mobile}).First(&user)
if result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, " The user doesn't exist ")
}
if result.Error != nil {
return nil, result.Error
}
userInfoRsp := ModelToRsponse(user)
return &userInfoRsp, nil
}
func (s *UserServer) GetUserById(ctx context.Context, req *proto.IdRequest) (*proto.UserInfoResponse, error) {
// adopt id Query the user
var user model.User
result := global.DB.First(&user, req.Id)
if result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, " The user doesn't exist ")
}
if result.Error != nil {
return nil, result.Error
}
userInfoRsp := ModelToRsponse(user)
return &userInfoRsp, nil
}
3 - A new user
func (s *UserServer) CreateUser(ctx context.Context, req *proto.CreateUserInfo) (*proto.UserInfoResponse, error) {
// A new user
var user model.User
result := global.DB.Where(&model.User{
Mobile: req.Mobile}).First(&user)
if result.RowsAffected == 1 {
return nil, status.Errorf(codes.AlreadyExists, " The user already exists ")
}
user.Mobile = req.Mobile
user.NickName = req.NickName
// Password encryption
options := &password.Options{
SaltLen: 16, Iterations: 100, KeyLen: 32, HashFunction: sha512.New}
salt, encodedPwd := password.Encode(req.PassWord, options)
user.Password = fmt.Sprintf("$pbkdf2-sha512$%s$%s", salt, encodedPwd)
result = global.DB.Create(&user)
if result.Error != nil {
return nil, status.Errorf(codes.Internal, result.Error.Error())
}
userInfoRsp := ModelToRsponse(user)
return &userInfoRsp, nil
}
4 - Update user
func (s *UserServer) UpdateUser(ctx context.Context, req *proto.UpdateUserInfo) (*empty.Empty, error) {
// Personal center updates users
var user model.User
result := global.DB.First(&user, req.Id)
if result.RowsAffected == 0 {
return nil, status.Errorf(codes.NotFound, " The user doesn't exist ")
}
birthDay := time.Unix(int64(req.BirthDay), 0)
user.NickName = req.NickName
user.Birthday = &birthDay
user.Gender = req.Gender
result = global.DB.Save(&user)
if result.Error != nil {
return nil, status.Errorf(codes.Internal, result.Error.Error())
}
return &empty.Empty{
}, nil
}
5 - Check the password
func (s *UserServer) CheckPassWord(ctx context.Context, req *proto.PasswordCheckInfo) (*proto.CheckResponse, error) {
// Check the password
options := &password.Options{
SaltLen: 16, Iterations: 100, KeyLen: 32, HashFunction: sha512.New}
passwordInfo := strings.Split(req.EncryptedPassword, "$")
check := password.Verify(req.Password, passwordInfo[2], passwordInfo[3], options)
return &proto.CheckResponse{
Success: check}, nil
}
5、 ... and 、flag start-up grpc
- user_srv\main.go
package main
import (
"flag"
"fmt"
"nd/user_srv/handler"
"nd/user_srv/proto"
"net"
"google.golang.org/grpc"
)
func main() {
IP := flag.String("ip", "0.0.0.0", "ip Address ")
Port := flag.Int("port", 50051, " Port number ")
flag.Parse()
fmt.Println("ip...", *IP)
fmt.Println("port...", *Port)
server := grpc.NewServer()
proto.RegisterUserServer(server, &handler.UserServer{
})
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
if err != nil {
panic("failed to listen:" + err.Error())
}
err = server.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
- The terminal starts to use flag Methods :
- Use
go build main.go
Generate main.exe - Use
main.exe -port 50052
The start port is 50052 Of grpc service
- Use
6、 ... and 、 The interface test
- user_srv\tests\user.go:
package main
import (
"context"
"fmt"
"nd/user_srv/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
var userClient proto.UserClient
var conn *grpc.ClientConn
func Init() {
var err error
conn, err = grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
userClient = proto.NewUserClient(conn)
}
func TestGetUserList() {
rsp, err := userClient.GetUserList(context.Background(), &proto.PageInfo{
Pn: 1,
PSize: 5,
})
if err != nil {
panic(err)
}
for _, user := range rsp.Data {
fmt.Println(user.Mobile, user.NickName, user.PassWord)
checkRsp, err := userClient.CheckPassWord(context.Background(), &proto.PasswordCheckInfo{
Password: "admin123",
EncryptedPassword: user.PassWord,
})
if err != nil {
panic(err)
}
fmt.Println(checkRsp.Success)
}
}
func TestCreateUser() {
for i := 0; i < 10; i++ {
rsp, err := userClient.CreateUser(context.Background(), &proto.CreateUserInfo{
NickName: fmt.Sprintf("bobby%d", i),
Mobile: fmt.Sprintf("1878222222%d", i),
PassWord: "admin123",
})
if err != nil {
panic(err)
}
fmt.Println(rsp.Id)
}
}
func main() {
Init()
// TestCreateUser()
TestGetUserList()
conn.Close()
}
边栏推荐
- Common auxiliary classes with high concurrency
- Pychart 2019 usage settings make it easier for you to use!
- How to write the use case of APP login function?
- Learn IO from simple to deep
- Mysql/sql server connects to the database through JDBC to add, delete, modify and query
- Set up form
- MySQL learning notes
- File download, how to write the use case?
- 快速解决电脑无线网络无法连接问题
- WebService (soap) request of JMeter
猜你喜欢
Piracy leads to device paralysis | official solemn statement: do not buy or download Navicat through unofficial channels
LVM disk multi partition expansion (fdisk, vgdata, lvdata, DF, resize2fs, lvextend, partprobe)
SQL | null value and SQL count() function
JDBC connection / operation database of JMeter
Zabbix+分布式數據庫TiDB實現分布式數據庫監控
JMeter's response assertion
LVM磁盘多分区扩容(fdisk,vgdata,lvdata,df,resize2fs,lvextend,partprobe)
LR load
Vote | choose the database you want Navicat to support
Dbeaver vs Navicat: database tool duel
随机推荐
How to test insert and update statements before execution
LVM磁盘多分区扩容(fdisk,vgdata,lvdata,df,resize2fs,lvextend,partprobe)
Parsing the sliding window of TCP
Centos7配置MySQL多实例
JMeter之上传文件和下载文件
[software test model evolution]
Implementation of static address book
Zabbix+ distributed database tidb realizes distributed database monitoring
LoadRunner clears browser cache
Common auxiliary classes with high concurrency
What if win7 system forgets its login password? (without startup disk)
快速解决电脑无线网络无法连接问题
JMeter read response header information / get request header
Peoplecode objects and classes
安装交叉编译器:EABI-4.3.3_EmbedSky_20100610.tar.bz2
Does Navicat 16 support native Apple silicon M1 chips| Apple user must read
JMeter aggregation Report
LVM disk multi partition expansion (fdisk, vgdata, lvdata, DF, resize2fs, lvextend, partprobe)
JMeter之响应断言
Learn IO from simple to deep