租售同体的书屋项目——用户系统(一)

1,769 阅读2分钟

一、概述

先做一个简单的用户系统,主要包括注册,登录,鉴权功能。

代码已经放在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.测试运行

user rpc.png

三、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上看到结果

user webApi.png

四、Vue前端

使用Vue + Element-UI组件进行简单实现,在GitHubFront目录查看完整文件

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.运行结果展示

user vue login2.png

五、预告

本专栏持续更新中,下一章将更新书籍系统部分,请感兴趣的同学收藏关注,谢谢。也欢迎大家提出意见或者建议,共同进步。