租售同体的书屋项目——用户系统(一)
租售同体的书屋项目——用户系统(一)
一、概述
先做一个简单的用户系统,主要包括注册,登录,鉴权功能。
代码已经放在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> 复制代码
2.运行结果展示
五、预告
本专栏持续更新中,下一章将把项目部署在Kubernets上,请感兴趣的同学收藏关注,谢谢。也欢迎大家提出意见或者建议,共同进步。
来源https://juejin.cn/post/7018427464225407012