准备项目配置和生成文件
创建目录
mkdir user-login
cd user-login
go mod init user-login
创建userlogin.api
- get:使用form
- post: 使用json
type (
RegisterRequest {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
RegisterResponse {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
LoginRequest {
Email string `json:"email"`
Password string `json:"password"`
}
LoginResponse {
Token string `json:"token"`
Expire int64 `json:"expire"`
}
)
service userlogin-api {
@handler RegisterHandler
post /api/register(RegisterRequest) returns (RegisterResponse);
@handler LoginHandler
post /api/login(LoginRequest) returns (LoginResponse);
}
type UserInfoResponse {
ID int64 `form:"id"`
Name string `form:"name"`
Email string `form:"email"`
}
@server(
jwt: Auth // Auth 与 userlogin/internal/config/config.go 中配置的 jwt 参数对应
)
service userlogin-api {
@handler UserInfo
get /api/userinfo returns (UserInfoResponse)
}
生成
goctl api go -api userlogin.api -dir . go mod tidy
如果你使用的是golang或idea,可以安装goctl插件,然后使用一键生成
修改配置
- 文件路径:user-login/etc/userlogin-api.yaml
- DataSource是你自己数据库的配置,记得修改
- Salt是盐值,用于加密
- Auth用于jwt鉴权
Name: userlogin-api
Host: 0.0.0.0
Port: 8888
Mysql:
DataSource: root:root@tcp(127.0.0.1:3306)/gozerouserlogin?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
CacheRedis:
- Host: redis:6379
Type: node
Salt: DWe7OZf6KPlnv7yy
Auth:
AccessSecret: uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl
AccessExpire: 86400
- 文件路径:user-login/internal/config/config.go
type Config struct {
rest.RestConf
Mysql struct {
DataSource string
}
CacheRedis cache.CacheConf
Salt string
Auth struct {
AccessSecret string
AccessExpire int64
}
}
添加用户表
- 路径:user-login/model/user.sql
CREATE TABLE `user` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户姓名',
`email` varchar(255) NOT NULL DEFAULT '' COMMENT '用户邮箱',
`password` varchar(255) NOT NULL DEFAULT '' COMMENT '用户密码',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
生成model文件
goctl model mysql ddl -src="user-login/model/user.sql" -dir="user-login/model/" -c
修改user-login/internal/svc/servicecontext.go
type ServiceContext struct {
Config config.Config
UserModel model.UserModel
}
func NewServiceContext(c config.Config) *ServiceContext {
conn := sqlx.NewMysql(c.Mysql.DataSource)
return &ServiceContext{
Config: c,
UserModel: model.NewUserModel(conn, c.CacheRedis),
}
}
编写业务逻辑代码
编写工具类
- 路径: user-login/common/cryptx/cryptx.go
package cryptx
import (
"fmt"
"golang.org/x/crypto/scrypt"
)
// 加密
func PasswordEncrypt(salt, password string) string {
dk, _ := scrypt.Key([]byte(password), []byte(salt), 32768, 8, 1, 32)
return fmt.Sprintf("%x", string(dk))
}
- 路径:go-zero/user-login/common/jwtx/jwt.go
package jwtx
import (
"github.com/golang-jwt/jwt/v4"
)
func GetToken(secretKey string, iat, seconds, uid int64) (string, error) {
claims := make(jwt.MapClaims)
claims["exp"] = iat + seconds
claims["iat"] = iat
claims["uid"] = uid
token := jwt.New(jwt.SigningMethodHS256)//注意是 HS 不是 ES
token.Claims = claims
return token.SignedString([]byte(secretKey))//注意是Signed
}
注册
- 路径:user-login/internal/logic/registerlogic.go
func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.RegisterResponse, err error) {
// todo: add your logic here and delete this line
_, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
if err == nil {
return nil, status.Error(100, "用户已存在")
}
if err != model.ErrNotFound {
return nil, status.Error(100, err.Error())
}
newUser := model.User{
Name: req.Name,
Email: req.Email,
Password: cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, req.Password),
}
res, err := l.svcCtx.UserModel.Insert(l.ctx, &newUser)
if err != nil {
return nil, status.Error(500, err.Error())
}
newUser.Id, err = res.LastInsertId()
if err != nil {
return nil, status.Error(500, err.Error())
}
return &types.RegisterResponse{
ID: int(newUser.Id),
Name: newUser.Name,
Email: newUser.Email,
}, nil
}
登录
- 路径:user-login/internal/logic/loginlogic.go
func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, err error) {
res, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
if err != nil {
if err == model.ErrNotFound {
return nil, status.Error(100, "用户不存在")
}
return nil, status.Error(500, err.Error())
}
password := cryptx.PasswordEncrypt(l.svcCtx.Config.Salt, req.Password)
if password != res.Password {
return nil, status.Error(100, "密码错误")
}
now := time.Now().Unix()
accessExpire := l.svcCtx.Config.Auth.AccessExpire
accessToken, err := jwtx.GetToken(l.svcCtx.Config.Auth.AccessSecret, now, accessExpire, res.Id)
if err != nil {
return nil, err
}
return &types.LoginResponse{
Token: accessToken,
Expire: now + accessExpire,
}, nil
}
使用Token查询用户信息
- 路径:go-zero/user-login/internal/logic/userinfologic.go
func (l *UserInfoLogic) UserInfo() (resp *types.UserInfoResponse, err error) {
// todo: add your logic here and delete this line
uid, _ := l.ctx.Value("uid").(json.Number).Int64()
one, err := l.svcCtx.UserModel.FindOne(l.ctx, uid)
if err != nil {
return nil, err
}
return &types.UserInfoResponse{
ID: one.Id,
Name: one.Name,
Email: one.Email,
}, nil
}
启动项目
- 先启动redis
go run userlogin.go -f etc/userlogin-api.yaml
# Starting server at 0.0.0.0:8888...