一、概述
先做一个简单的用户系统,主要包括注册,登录,鉴权功能。
代码已经放在GitHub上,同步更新,可自行查看。
二、微服务
使用Go-zero框架进行微服务的编写(不知道的可以去看我的基础专栏里面讲过的)
1.编写user.proto
文件,protobuf语法自行学习
syntax = "proto3";
package user;
option go_package = "user";
message Request{
int64 ping = 1;
}
message Reply{
bool ok = 1;
string code = 2;
}
message IdReq{
int64 id = 1;
}
message UsernameReq{
string username = 1;
}
message UserInfoReply{
int64 id = 1;
string username = 2;
string password = 3;
string nickname = 4;
string phone = 5;
string email = 6;
}
message UsersInfoReply{
repeated UserInfoReply usersInfo = 1;
}
message RegisterReq{
string username = 1;
string password = 2;
string nickname = 3;
string phone = 4;
string email = 5;
string repeatPassword = 6;
}
message LoginReq{
string username= 1;
string email = 2;
string phone = 3;
string password = 4;
}
message UpdateUserReq{
int64 id = 1;
string username = 2;
string password = 3;
string nickname = 4;
string phone = 5;
string email = 6;
}
service User {
rpc FindOneUserById(IdReq) returns(UserInfoReply);
rpc FindOneUserByUsername(UsernameReq) returns(UserInfoReply);
rpc FindAllUser(Request) returns(UsersInfoReply);
rpc Register(RegisterReq) returns(Reply);
rpc Login(LoginReq) returns(UserInfoReply);
rpc UpdateUser(UpdateUserReq) returns(Reply);
}
复制代码
2.使用goctl生成框架文件
book-store@ubuntu:~/BookStoreProject/Grpc/User$ goctl rpc proto -src user.proto -dir .
复制代码
book-store@ubuntu:~/BookStoreProject/Grpc/User$ tree
.
├── etc
│ └── user.yaml
├── go.mod
├── go.sum
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── findalluserlogic.go
│ │ ├── findoneuserbyidlogic.go
│ │ ├── findoneuserbyusernamelogic.go
│ │ ├── loginlogic.go
│ │ ├── registerlogic.go
│ │ └── updateuserlogic.go
│ ├── server
│ │ └── userserver.go
│ └── svc
│ └── servicecontext.go
├── model //由goctl根据数据库生成,后面2.会描述
│ ├── userinfoextramodel.go
│ ├── userinfomodel.go
│ └── vars.go
├── user
│ └── user.pb.go
├── userclient
│ └── user.go
├── userclient.go
├── user.go
└── user.proto
9 directories, 20 files
复制代码
2.使用goctl生成数据库模型文件
book-store@ubuntu:~/BookStoreProject/Grpc/User$ goctl model mysql datasource -url="bookstore:bookstore@tcp(172.20.3.12:3306)/bs-user" -table="user_info" -dir="./model" -cache=true
复制代码
-
数据库表DDL
CREATE TABLE `user_info` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE,
UNIQUE KEY `nickname` (`nickname`) USING BTREE,
UNIQUE KEY `phone` (`phone`) USING BTREE,
UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
复制代码
3.修改配置文件etc/user.yaml
,根据自己的环境去配置
Name: user.rpc
ListenOn: 0.0.0.0:8080
Mysql:
DataSource: bookstore:bookstore@tcp(172.20.3.12:3306)/bs-user?charset=utf8&parseTime=true
CacheRedis:
- Host: 172.20.3.13:30020
Pass: '123456'
Type: node
复制代码
4.完善逻辑internal/logic
文件夹和服务internal/svc
文件夹中的文件,完整的可去GitHub上搜寻。
loginlogic.go
func (l *LoginLogic) Login(in *user.LoginReq) (*user.UserInfoReply, error) {
var err error
var rep = new(model.UserInfo)
if in.Username != "" {
rep, err = l.svcCtx.Model.FindOneByUsername(in.Username)
} else if in.Email != "" {
rep, err = l.svcCtx.Model.FindOneByEmail(in.Email)
} else if in.Phone != "" {
rep, err = l.svcCtx.Model.FindOneByPhone(in.Phone)
} else {
return &user.UserInfoReply{}, nil
}
if err != nil {
return nil, err
}
if rep.Password == in.Password {
return &user.UserInfoReply{
Id: rep.Id,
Username: rep.Username,
Nickname: rep.Nickname,
//Password: rep.Password,
Email: rep.Email,
Phone: rep.Phone,
}, nil
}
return &user.UserInfoReply{}, nil
}
复制代码
servicecontext.go
type ServiceContext struct {
Config config.Config
Model model.UserInfoModel
}
func NewServiceContext(c config.Config) *ServiceContext {
con := sqlx.NewMysql(c.Mysql.DataSource)
return &ServiceContext{
Config: c,
Model: model.NewUserInfoModel(con, c.CacheRedis),
}
}
复制代码
5.编写一个客户端进行验证userclient.go
func main() {
// 连接服务器
conn, err := grpc.Dial("172.20.3.111:8080", grpc.WithInsecure())
if err != nil {
fmt.Println("连接服务端失败: ", err)
return
}
defer conn.Close()
// 新建一个客户端
c := user.NewUserClient(conn)
// 调用服务端函数
r, err := c.FindAllUser(context.Background(), &user.Request{})
if err != nil {
fmt.Println("调用服务端代码失败: ", err)
return
}
fmt.Println("调用成功: ", r)
}
复制代码
6.测试运行
三、WebApi网关
使用Gin框架进行网关的设计,用注册部分简单说一下,其它类似,在GitHub中查看完整文件
1.文件结构,同基础专栏类似,新增了一个Pb
文件夹用于放grpc的文件和Etc
文件夹用于存放配置文件
book-store@ubuntu:~/BookStoreProject/WebApi$ tree -d
.
├── Apps
│ └── user
├── Assets
├── Databases
├── Etc
├── Middlewares
├── Models
├── Pb
│ └── user
├── Router
├── Services
└── Utils
12 directories
复制代码
2.在Services/grpc.go
中连接Grpc微服务端
func GrpcInit() error {
conn, err := grpc.Dial(C.UserRpc.Host, grpc.WithInsecure())
if err != nil {
return err
}
UserGrpc = user.NewUserClient(conn)
return nil
}
复制代码
3.在Apps/user/login_handler.go
中编写处理注册请求逻辑
func LoginHandler(c *gin.Context) {
username := c.DefaultPostForm("username", "")
password := c.DefaultPostForm("password", "")
email := c.DefaultPostForm("email", "")
phone := c.DefaultPostForm("phone", "")
ctx := context.Background()
rep, err := Services.UserGrpc.Login(ctx, &user.LoginReq{
Username: username,
Password: password,
Email: email,
Phone: phone})
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
} else {
if rep.Username == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "没有找到用户,请先注册"})
return
}
}
now := time.Now().Unix()
jwtToken, err := getJwtToken(Services.C.Jwt.Secret, strconv.FormatInt(now, 10), strconv.FormatInt(Services.C.Jwt.Expire, 10), rep.Username)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, struct {
Name string
NickName string
AccessToken string
AccessExpire int64
RefreshAfter int64
}{
Name: rep.Username,
NickName: rep.Nickname,
AccessToken: jwtToken,
AccessExpire: now + Services.C.Jwt.Expire,
RefreshAfter: now + Services.C.Jwt.Expire/2})
}
复制代码
4.在Router/routers.go
中编写路由
func Init() *gin.Engine {
r := gin.Default()
//r. Use(Middlewares.Cors())
r.Static("/Assets", "./Assets")
r.StaticFile("/favicon.ico", "./Assets/favicon.ico")
userGroup := r.Group("/user")
{
userGroup.POST("/login", user.LoginHandler)
userGroup.POST("/register", user.RegisterHandler)
userGroup.GET("/",Middlewares.JWTSuperuserMiddleware(), user.GetAllUsersHandler)
}
return r
}
复制代码
5.运行,可以在postman上看到结果
四、Vue前端
使用Vue + Element-UI组件进行简单实现,在GitHub中Front
目录查看完整文件
1.展示Login.vue
<template>
<div id="login" style="">
<el-row :gutter="20">
<el-col :span="8" :offset="8">
<div class="grid-content bg-purple">
<el-form ref="form" :model="form" label-width="140px">
<el-form-item label="用户名或邮箱或电话">
<el-input le v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input v-model="form.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit('form')">登录</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'Login',
data () {
return {
form: {
username: '',
password: ''
}
}
},
mounted () {
},
methods: {
onSubmit (form) {
this.$refs[form].validate((valid) => {
if (valid) {
let that = this
let username = ''
let email = ''
let phone = ''
if (/^1\d{10}$/.test(that.form['username'])) {
phone = that.form['username']
} else if (/^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(that.form['username'])) {
email = that.form['username']
} else {
username = that.form['username']
}
let formData = new FormData()
formData.append('username', username)
formData.append('email', email)
formData.append('phone', phone)
formData.append('password', that.form['password'])
that.$axios({
method: 'post',
url: '/api/user/login',
data: formData
}).then(function (response) {
const res = response.data
console.log(res)
localStorage.setItem('Token', res['AccessToken'])
localStorage.setItem('Username', res['Name'])
localStorage.setItem('Nickname', res['NickName'])
that.$message({message: '登录成功', duration: 1000})
setTimeout(function () {
that.$router.push({path: '/'})
window.location.reload()
}, 1000)
}).catch(function (error) {
console.log(error)
alert('用户不存在')
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style scoped>
</style>
复制代码