initial commit

This commit is contained in:
2025-05-22 19:39:08 +08:00
commit 531bb42d01
103 changed files with 10291 additions and 0 deletions

View File

@ -0,0 +1,14 @@
package config
import "github.com/zeromicro/go-zero/zrpc"
type JwtAuth struct {
AccessSecret string
AccessExpire int64
}
type Config struct {
zrpc.RpcServerConf
DataSource string
JwtAuth JwtAuth
}

View File

@ -0,0 +1,50 @@
package logic
import (
"context"
"database/sql"
"errors"
"fmt"
"godemo/user/internal/model"
"godemo/user/internal/svc"
"godemo/user/user"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/status"
)
type GetUserInfoLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoLogic {
return &GetUserInfoLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// 获取用户信息
func (l *GetUserInfoLogic) GetUserInfo(in *user.GetUserInfoRequest) (*user.GetUserInfoResponse, error) {
// todo: add your logic here and delete this line
var userModel model.User
err := l.svcCtx.DB.Get(&userModel, "SELECT * FROM users WHERE user_id = $1", in.UserId)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(status.Code(err), fmt.Sprintf("用户 %s 不存在", in.UserId))
}
return nil, status.Error(status.Code(err), err.Error())
}
return &user.GetUserInfoResponse{
UserId: userModel.UserId,
Username: userModel.Username,
Email: userModel.Email.String,
CreatedAt: userModel.CreatedAt.Unix(),
Roles: userModel.Roles,
}, nil
}

View File

@ -0,0 +1,69 @@
package logic
import (
"context"
"database/sql"
"errors"
"godemo/user/internal/model"
"godemo/user/internal/svc"
"godemo/user/user"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/zeromicro/go-zero/core/logx"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type LoginLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginLogic {
return &LoginLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
func (l *LoginLogic) Login(in *user.LoginRequest) (*user.LoginResponse, error) {
// 1. 查找用户
var u model.User
err := l.svcCtx.DB.Get(&u, "SELECT * FROM users WHERE username = $1", in.Username)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "用户不存在")
}
return nil, status.Error(codes.Internal, err.Error())
}
// 2. 校验密码(假设密码已加密存储)
err = bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(in.Password))
if err != nil {
return nil, status.Error(codes.Unauthenticated, "密码错误")
}
// 3. 签发 JWT Token
claims := jwt.MapClaims{
"sub": u.UserId,
"exp": time.Now().Add(time.Minute * 15).Unix(), // 默认 15 分钟有效期
"iat": time.Now().Unix(),
"roles": u.Roles,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString([]byte(l.svcCtx.Config.JwtAuth.AccessSecret))
if err != nil {
return nil, status.Error(codes.Unavailable, "token生成失败")
}
// 4. 返回响应
return &user.LoginResponse{
Token: signedToken,
ExpiresAt: claims["exp"].(int64),
}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"godemo/user/internal/svc"
"godemo/user/user"
"github.com/zeromicro/go-zero/core/logx"
)
type LogoutLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewLogoutLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LogoutLogic {
return &LogoutLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// 用户登出,可选实现
func (l *LogoutLogic) Logout(in *user.LogoutRequest) (*user.LogoutResponse, error) {
// todo: add your logic here and delete this line
return &user.LogoutResponse{}, nil
}

View File

@ -0,0 +1,31 @@
package logic
import (
"context"
"godemo/user/internal/svc"
"godemo/user/user"
"github.com/zeromicro/go-zero/core/logx"
)
type PingLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
return &PingLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// 健康检查
func (l *PingLogic) Ping(in *user.PingRequest) (*user.PingResponse, error) {
// todo: add your logic here and delete this line
return &user.PingResponse{}, nil
}

View File

@ -0,0 +1,90 @@
package logic
import (
"context"
"database/sql"
"godemo/user/internal/model"
"godemo/user/internal/svc"
"godemo/user/user"
"time"
"github.com/lib/pq"
"github.com/zeromicro/go-zero/core/logx"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type RegisterLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RegisterLogic {
return &RegisterLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}
// 用户注册
func (l *RegisterLogic) Register(in *user.RegisterRequest) (*user.RegisterResponse, error) {
// 1. 检查用户名是否已存在
var existingUser model.User
err := l.svcCtx.DB.Get(&existingUser, "SELECT * FROM users WHERE username = $1", in.Username)
if err == nil {
// 用户名已存在
return nil, status.Error(codes.AlreadyExists, "用户名已存在")
}
// 2. 加密密码
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost)
if err != nil {
// 密码加密失败
return nil, status.Error(codes.Internal, err.Error())
}
// 3. 转换 email 为 sql.NullString
var email sql.NullString
if in.Email != "" {
email = sql.NullString{
String: in.Email,
Valid: true,
}
} else {
email = sql.NullString{Valid: false}
}
// 4. 保存新用户到数据库
userModel := model.User{
Username: in.Username,
PasswordHash: string(hashedPassword),
Email: email, // 使用 sql.NullString 类型存储 email
Roles: []string{"user"}, // 默认角色为 "user"
CreatedAt: time.Now(), // 当前时间戳
}
// 执行插入数据库操作
_, err = l.svcCtx.DB.Exec(
"INSERT INTO users (username, password_hash, email, roles, created_at) VALUES ($1, $2, $3, $4, $5)",
userModel.Username,
userModel.PasswordHash,
userModel.Email,
pq.Array(userModel.Roles), // pq.Array 用于处理 PostgreSQL 数组类型
userModel.CreatedAt,
)
if err != nil {
// 数据库插入失败
return nil, status.Error(codes.Internal, err.Error())
}
// 获取插入的记录的 UserId
l.svcCtx.DB.Get(&existingUser, "SELECT * FROM users WHERE username = $1", in.Username)
// 5. 返回成功响应
return &user.RegisterResponse{
UserId: existingUser.UserId, // 将 UserId 转换为字符串
}, nil
}

View File

@ -0,0 +1,17 @@
package model
import (
"database/sql"
"time"
"github.com/lib/pq"
)
type User struct {
UserId string `db:"user_id"`
Username string `db:"username"`
Email sql.NullString `db:"email"`
PasswordHash string `db:"password_hash"`
Roles pq.StringArray `db:"roles"`
CreatedAt time.Time `db:"created_at"`
}

View File

@ -0,0 +1,54 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.8.3
// Source: user.proto
package server
import (
"context"
"godemo/user/internal/logic"
"godemo/user/internal/svc"
"godemo/user/user"
)
type UserServer struct {
svcCtx *svc.ServiceContext
user.UnimplementedUserServer
}
func NewUserServer(svcCtx *svc.ServiceContext) *UserServer {
return &UserServer{
svcCtx: svcCtx,
}
}
// 健康检查
func (s *UserServer) Ping(ctx context.Context, in *user.PingRequest) (*user.PingResponse, error) {
l := logic.NewPingLogic(ctx, s.svcCtx)
return l.Ping(in)
}
// 用户注册
func (s *UserServer) Register(ctx context.Context, in *user.RegisterRequest) (*user.RegisterResponse, error) {
l := logic.NewRegisterLogic(ctx, s.svcCtx)
return l.Register(in)
}
// 用户登录,返回 JWT Token
func (s *UserServer) Login(ctx context.Context, in *user.LoginRequest) (*user.LoginResponse, error) {
l := logic.NewLoginLogic(ctx, s.svcCtx)
return l.Login(in)
}
// 用户登出,可选实现
func (s *UserServer) Logout(ctx context.Context, in *user.LogoutRequest) (*user.LogoutResponse, error) {
l := logic.NewLogoutLogic(ctx, s.svcCtx)
return l.Logout(in)
}
// 获取用户信息
func (s *UserServer) GetUserInfo(ctx context.Context, in *user.GetUserInfoRequest) (*user.GetUserInfoResponse, error) {
l := logic.NewGetUserInfoLogic(ctx, s.svcCtx)
return l.GetUserInfo(in)
}

View File

@ -0,0 +1,23 @@
package svc
import (
"godemo/user/internal/config"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type ServiceContext struct {
Config config.Config
DB *sqlx.DB
}
func NewServiceContext(c config.Config) *ServiceContext {
db := sqlx.MustConnect("postgres", c.DataSource)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
return &ServiceContext{
Config: c,
DB: db,
}
}