初步架构

This commit is contained in:
xmdhs 2023-09-02 17:39:58 +08:00
parent ed7ed6cd12
commit c35e71d22c
No known key found for this signature in database
GPG Key ID: E809D6D43DEFCC95
23 changed files with 435 additions and 124 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"gopls": {
"buildFlags":["-tags=wireinject"]
},
}

34
cmd/testserver/main.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"context"
"fmt"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/server"
)
func main() {
ctx := context.Background()
config := config.Config{
OfflineUUID: true,
Port: "127.0.0.1:8080",
Log: struct {
Level string
Json bool
}{
Level: "debug",
},
Sql: struct{ MysqlDsn string }{
MysqlDsn: "sizzle1445:jnjjJQ8^YF&8PN@tcp(192.168.20.1)/skin",
},
Node: 0,
Epoch: 1693645718534,
}
s, c, err := server.InitializeRoute(ctx, config)
if err != nil {
panic(err)
}
defer c()
fmt.Println(s.ListenAndServe())
}

View File

@ -2,4 +2,14 @@ package config
type Config struct { type Config struct {
OfflineUUID bool OfflineUUID bool
Port string
Log struct {
Level string
Json bool
}
Sql struct {
MysqlDsn string
}
Node int64
Epoch int64
} }

View File

@ -37,6 +37,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
if q.getUserByEmailStmt, err = db.PrepareContext(ctx, getUserByEmail); err != nil { if q.getUserByEmailStmt, err = db.PrepareContext(ctx, getUserByEmail); err != nil {
return nil, fmt.Errorf("error preparing query GetUserByEmail: %w", err) return nil, fmt.Errorf("error preparing query GetUserByEmail: %w", err)
} }
if q.getUserProfileByNameStmt, err = db.PrepareContext(ctx, getUserProfileByName); err != nil {
return nil, fmt.Errorf("error preparing query GetUserProfileByName: %w", err)
}
if q.listUserStmt, err = db.PrepareContext(ctx, listUser); err != nil { if q.listUserStmt, err = db.PrepareContext(ctx, listUser); err != nil {
return nil, fmt.Errorf("error preparing query ListUser: %w", err) return nil, fmt.Errorf("error preparing query ListUser: %w", err)
} }
@ -70,6 +73,11 @@ func (q *Queries) Close() error {
err = fmt.Errorf("error closing getUserByEmailStmt: %w", cerr) err = fmt.Errorf("error closing getUserByEmailStmt: %w", cerr)
} }
} }
if q.getUserProfileByNameStmt != nil {
if cerr := q.getUserProfileByNameStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getUserProfileByNameStmt: %w", cerr)
}
}
if q.listUserStmt != nil { if q.listUserStmt != nil {
if cerr := q.listUserStmt.Close(); cerr != nil { if cerr := q.listUserStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listUserStmt: %w", cerr) err = fmt.Errorf("error closing listUserStmt: %w", cerr)
@ -112,25 +120,27 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar
} }
type Queries struct { type Queries struct {
db DBTX db DBTX
tx *sql.Tx tx *sql.Tx
createUserStmt *sql.Stmt createUserStmt *sql.Stmt
createUserProfileStmt *sql.Stmt createUserProfileStmt *sql.Stmt
deleteUserStmt *sql.Stmt deleteUserStmt *sql.Stmt
getUserStmt *sql.Stmt getUserStmt *sql.Stmt
getUserByEmailStmt *sql.Stmt getUserByEmailStmt *sql.Stmt
listUserStmt *sql.Stmt getUserProfileByNameStmt *sql.Stmt
listUserStmt *sql.Stmt
} }
func (q *Queries) WithTx(tx *sql.Tx) *Queries { func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{ return &Queries{
db: tx, db: tx,
tx: tx, tx: tx,
createUserStmt: q.createUserStmt, createUserStmt: q.createUserStmt,
createUserProfileStmt: q.createUserProfileStmt, createUserProfileStmt: q.createUserProfileStmt,
deleteUserStmt: q.deleteUserStmt, deleteUserStmt: q.deleteUserStmt,
getUserStmt: q.getUserStmt, getUserStmt: q.getUserStmt,
getUserByEmailStmt: q.getUserByEmailStmt, getUserByEmailStmt: q.getUserByEmailStmt,
listUserStmt: q.listUserStmt, getUserProfileByNameStmt: q.getUserProfileByNameStmt,
listUserStmt: q.listUserStmt,
} }
} }

View File

@ -13,6 +13,7 @@ type Querier interface {
DeleteUser(ctx context.Context, id int64) error DeleteUser(ctx context.Context, id int64) error
GetUser(ctx context.Context, id int64) (User, error) GetUser(ctx context.Context, id int64) (User, error)
GetUserByEmail(ctx context.Context, email string) (User, error) GetUserByEmail(ctx context.Context, email string) (User, error)
GetUserProfileByName(ctx context.Context, name string) (UserProfile, error)
ListUser(ctx context.Context) ([]User, error) ListUser(ctx context.Context) ([]User, error)
} }

View File

@ -9,16 +9,7 @@ import (
) )
const createUser = `-- name: CreateUser :execresult const createUser = `-- name: CreateUser :execresult
REPLACE INTO user ( REPLACE INTO user ( id, email, password, salt, state, reg_time ) VALUES (?, ?, ?, ?, ?, ?)
id,
email,
password,
salt,
state,
reg_time
)
VALUES
(?, ?, ?, ?, ?, ?)
` `
type CreateUserParams struct { type CreateUserParams struct {
@ -42,9 +33,7 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (sql.Res
} }
const createUserProfile = `-- name: CreateUserProfile :execresult const createUserProfile = `-- name: CreateUserProfile :execresult
REPLACE INTO ` + "`" + `user_profile` + "`" + ` (` + "`" + `user_id` + "`" + `, ` + "`" + `name` + "`" + `, ` + "`" + `uuid` + "`" + `) REPLACE INTO ` + "`" + `user_profile` + "`" + ` (` + "`" + `user_id` + "`" + `, ` + "`" + `name` + "`" + `, ` + "`" + `uuid` + "`" + `) VALUES (?, ?, ?)
VALUES
(?, ?, ?)
` `
type CreateUserProfileParams struct { type CreateUserProfileParams struct {
@ -58,10 +47,9 @@ func (q *Queries) CreateUserProfile(ctx context.Context, arg CreateUserProfilePa
} }
const deleteUser = `-- name: DeleteUser :exec const deleteUser = `-- name: DeleteUser :exec
DELETE FROM DELETE
user FROM user
WHERE WHERE id = ?
id = ?
` `
func (q *Queries) DeleteUser(ctx context.Context, id int64) error { func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
@ -70,14 +58,10 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
} }
const getUser = `-- name: GetUser :one const getUser = `-- name: GetUser :one
SELECT SELECT id, email, password, salt, state, reg_time
id, email, password, salt, state, reg_time FROM user
FROM WHERE id = ?
user LIMIT 1
WHERE
id = ?
LIMIT
1
` `
func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) { func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) {
@ -95,14 +79,10 @@ func (q *Queries) GetUser(ctx context.Context, id int64) (User, error) {
} }
const getUserByEmail = `-- name: GetUserByEmail :one const getUserByEmail = `-- name: GetUserByEmail :one
SELECT SELECT id, email, password, salt, state, reg_time
id, email, password, salt, state, reg_time FROM user
FROM WHERE email = ?
user LIMIT 1
WHERE
email = ?
LIMIT
1
` `
func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) { func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error) {
@ -119,13 +99,24 @@ func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error
return i, err return i, err
} }
const getUserProfileByName = `-- name: GetUserProfileByName :one
SELECT user_id, name, uuid
FROM ` + "`" + `user_profile` + "`" + `
WHERE ` + "`" + `name` + "`" + ` = ?
LIMIT 1
`
func (q *Queries) GetUserProfileByName(ctx context.Context, name string) (UserProfile, error) {
row := q.queryRow(ctx, q.getUserProfileByNameStmt, getUserProfileByName, name)
var i UserProfile
err := row.Scan(&i.UserID, &i.Name, &i.Uuid)
return i, err
}
const listUser = `-- name: ListUser :many const listUser = `-- name: ListUser :many
SELECT SELECT id, email, password, salt, state, reg_time
id, email, password, salt, state, reg_time FROM user
FROM ORDER BY reg_time
user
ORDER BY
reg_time
` `
func (q *Queries) ListUser(ctx context.Context) ([]User, error) { func (q *Queries) ListUser(ctx context.Context) ([]User, error) {

View File

@ -1,51 +1,27 @@
-- name: GetUser :one -- name: GetUser :one
SELECT SELECT *
* FROM user
FROM WHERE id = ?
user LIMIT 1;
WHERE -- name: ListUser :many
id = ? SELECT *
LIMIT FROM user
1; ORDER BY reg_time;
-- name: CreateUser :execresult
-- name: ListUser :many REPLACE INTO user ( id, email, password, salt, state, reg_time ) VALUES (?, ?, ?, ?, ?, ?);
SELECT -- name: DeleteUser :exec
* DELETE
FROM FROM user
user WHERE id = ?;
ORDER BY -- name: CreateUserProfile :execresult
reg_time; REPLACE INTO `user_profile` (`user_id`, `name`, `uuid`) VALUES (?, ?, ?);
-- name: GetUserByEmail :one
-- name: CreateUser :execresult SELECT *
REPLACE INTO user ( FROM user
id, WHERE email = ?
email, LIMIT 1 for update;
password, -- name: GetUserProfileByName :one
salt, SELECT *
state, FROM `user_profile`
reg_time WHERE `name` = ?
) LIMIT 1 for update;
VALUES
(?, ?, ?, ?, ?, ?);
-- name: DeleteUser :exec
DELETE FROM
user
WHERE
id = ?;
-- name: CreateUserProfile :execresult
REPLACE INTO `user_profile` (`user_id`, `name`, `uuid`)
VALUES
(?, ?, ?);
-- name: GetUserByEmail :one
SELECT
*
FROM
user
WHERE
email = ?
LIMIT
1;

View File

@ -31,4 +31,6 @@ CREATE TABLE IF NOT EXISTS `user_profile` (
user_id BIGINT PRIMARY KEY, user_id BIGINT PRIMARY KEY,
name VARCHAR(20) NOT NULL, name VARCHAR(20) NOT NULL,
uuid text NOT NULL uuid text NOT NULL
); );
CREATE UNIQUE INDEX IF NOT EXISTS name_index ON user_profile (name);

8
go.mod
View File

@ -3,18 +3,20 @@ module github.com/xmdhs/authlib-skin
go 1.21.0 go 1.21.0
require ( require (
github.com/bwmarrin/snowflake v0.3.0
github.com/go-playground/validator/v10 v10.15.3 github.com/go-playground/validator/v10 v10.15.3
github.com/go-sql-driver/mysql v1.7.1
github.com/google/uuid v1.3.1
github.com/google/wire v0.5.0
github.com/julienschmidt/httprouter v1.3.0 github.com/julienschmidt/httprouter v1.3.0
golang.org/x/crypto v0.7.0
) )
require ( require (
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect golang.org/x/text v0.8.0 // indirect

11
go.sum
View File

@ -13,8 +13,14 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo= github.com/go-playground/validator/v10 v10.15.3 h1:S+sSpunYjNPDuXkWbK+x+bA7iXiW296KG4dL3X7xUZo=
github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.15.3/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
@ -28,14 +34,19 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@ -2,6 +2,7 @@ package handle
import ( import (
"database/sql" "database/sql"
"errors"
"log/slog" "log/slog"
"net/http" "net/http"
@ -27,6 +28,11 @@ func Reg(l *slog.Logger, q mysql.Querier, v *validator.Validate, db *sql.DB, sno
} }
err = service.Reg(ctx, u, q, db, snow, c) err = service.Reg(ctx, u, q, db, snow, c)
if err != nil { if err != nil {
if errors.Is(err, service.ErrExistUser) {
l.DebugContext(ctx, err.Error())
handleError(ctx, w, err.Error(), model.ErrExistUser, 400)
return
}
l.WarnContext(ctx, err.Error()) l.WarnContext(ctx, err.Error())
handleError(ctx, w, err.Error(), model.ErrService, 500) handleError(ctx, w, err.Error(), model.ErrService, 500)
return return

View File

@ -6,4 +6,5 @@ const (
OK APIStatus = iota OK APIStatus = iota
ErrInput ErrInput
ErrService ErrService
ErrExistUser
) )

View File

@ -8,6 +8,6 @@ type API[T any] struct {
type User struct { type User struct {
Email string `validate:"required,email"` Email string `validate:"required,email"`
Password string `validate:"required,sha256"` Password string `validate:"required,min=6,max=50"`
Name string `validate:"required,min=3,max=16"` Name string `validate:"required,min=3,max=16"`
} }

73
server/provide.go Normal file
View File

@ -0,0 +1,73 @@
package server
import (
"context"
"database/sql"
"fmt"
"log/slog"
"os"
"github.com/bwmarrin/snowflake"
"github.com/go-playground/validator/v10"
_ "github.com/go-sql-driver/mysql"
"github.com/google/wire"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/db/mysql"
)
func ProvideSlog(c config.Config) slog.Handler {
var level slog.Level
switch c.Log.Level {
case "debug":
level = slog.LevelDebug
case "info":
level = slog.LevelInfo
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
}
o := &slog.HandlerOptions{Level: level}
var h slog.Handler
if c.Log.Json {
h = slog.NewJSONHandler(os.Stderr, o)
} else {
h = slog.NewTextHandler(os.Stderr, o)
}
return h
}
func ProvideDB(c config.Config) (*sql.DB, func(), error) {
db, err := sql.Open("mysql", c.Sql.MysqlDsn)
if err != nil {
return nil, nil, fmt.Errorf("newDB: %w", err)
}
db.SetMaxOpenConns(20)
db.SetMaxIdleConns(10)
return db, func() { db.Close() }, nil
}
func ProvideQuerier(ctx context.Context, db *sql.DB) (mysql.Querier, func(), error) {
q, err := mysql.Prepare(ctx, db)
if err != nil {
return nil, nil, fmt.Errorf("newQuerier: %w", err)
}
return q, func() { q.Close() }, nil
}
func ProvideValidate() *validator.Validate {
return validator.New()
}
func ProvideSnowflake(c config.Config) (*snowflake.Node, error) {
snowflake.Epoch = c.Epoch
n, err := snowflake.NewNode(c.Node)
if err != nil {
return nil, fmt.Errorf("newSnowflake: %w", err)
}
return n, nil
}
var Set = wire.NewSet(ProvideSlog, ProvideDB, ProvideQuerier, ProvideValidate, ProvideSnowflake)

View File

@ -0,0 +1,14 @@
package route
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
func warpCtJSON(handle httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
handle(w, r, p)
}
}

37
server/route/route.go Normal file
View File

@ -0,0 +1,37 @@
package route
import (
"database/sql"
"fmt"
"log/slog"
"github.com/bwmarrin/snowflake"
"github.com/go-playground/validator/v10"
"github.com/julienschmidt/httprouter"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/db/mysql"
"github.com/xmdhs/authlib-skin/handle"
)
func NewRoute(l *slog.Logger, q mysql.Querier, v *validator.Validate, db *sql.DB, snow *snowflake.Node, c config.Config) (*httprouter.Router, error) {
r := httprouter.New()
err := newYggdrasil(r)
if err != nil {
return nil, fmt.Errorf("NewRoute: %w", err)
}
err = newSkinApi(r, l, q, v, db, snow, c)
if err != nil {
return nil, fmt.Errorf("NewRoute: %w", err)
}
return r, nil
}
func newYggdrasil(r *httprouter.Router) error {
r.POST("/api/authserver/authenticate", nil)
return nil
}
func newSkinApi(r *httprouter.Router, l *slog.Logger, q mysql.Querier, v *validator.Validate, db *sql.DB, snow *snowflake.Node, c config.Config) error {
r.PUT("/api/v1/user/reg", handle.Reg(l, q, v, db, snow, c))
return nil
}

View File

@ -1,20 +1,40 @@
package server package server
import ( import (
"log/slog"
"net/http" "net/http"
"sync/atomic"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/utils"
) )
func NewYggdrasil(r *httprouter.Router) error { func NewServer(c config.Config, sl *slog.Logger, route *httprouter.Router) (*http.Server, func()) {
trackid := atomic.Uint64{}
r.POST("/api/authserver/authenticate", nil) s := &http.Server{
return nil ReadTimeout: 10 * time.Second,
} ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 20 * time.Second,
func warpCtJSON(handle httprouter.Handle) httprouter.Handle { Addr: c.Port,
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") ctx := r.Context()
handle(w, r, p) if sl.Enabled(ctx, slog.LevelInfo) {
ip, _ := utils.GetIP(r)
trackid.Add(1)
ctx = setCtx(ctx, &reqInfo{
URL: r.URL.String(),
IP: ip,
TrackId: trackid.Load(),
})
r = r.WithContext(ctx)
}
if sl.Enabled(ctx, slog.LevelDebug) {
sl.DebugContext(ctx, r.Method)
}
route.ServeHTTP(w, r)
}),
} }
return s, func() { s.Close() }
} }

View File

@ -32,7 +32,7 @@ type warpSlogHandle struct {
} }
func (w *warpSlogHandle) Handle(ctx context.Context, r slog.Record) error { func (w *warpSlogHandle) Handle(ctx context.Context, r slog.Record) error {
if w.Enabled(ctx, slog.LevelDebug) { if w.Enabled(ctx, slog.LevelInfo) {
ri := getFromCtx(ctx) ri := getFromCtx(ctx)
if ri != nil { if ri != nil {
r.AddAttrs(slog.String("ip", ri.IP), slog.String("url", ri.URL), slog.Uint64("trackID", ri.TrackId)) r.AddAttrs(slog.String("ip", ri.IP), slog.String("url", ri.URL), slog.Uint64("trackID", ri.TrackId))
@ -40,3 +40,10 @@ func (w *warpSlogHandle) Handle(ctx context.Context, r slog.Record) error {
} }
return w.Handler.Handle(ctx, r) return w.Handler.Handle(ctx, r)
} }
func NewSlog(h slog.Handler) *slog.Logger {
l := slog.New(&warpSlogHandle{
Handler: h,
})
return l
}

16
server/wire.go Normal file
View File

@ -0,0 +1,16 @@
//go:build wireinject
package server
import (
"context"
"net/http"
"github.com/google/wire"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/server/route"
)
func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) {
panic(wire.Build(Set, route.NewRoute, NewSlog, NewServer))
}

53
server/wire_gen.go Normal file
View File

@ -0,0 +1,53 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package server
import (
"context"
"github.com/xmdhs/authlib-skin/config"
"github.com/xmdhs/authlib-skin/server/route"
"net/http"
)
import (
_ "github.com/go-sql-driver/mysql"
)
// Injectors from wire.go:
func InitializeRoute(ctx context.Context, c config.Config) (*http.Server, func(), error) {
handler := ProvideSlog(c)
logger := NewSlog(handler)
db, cleanup, err := ProvideDB(c)
if err != nil {
return nil, nil, err
}
querier, cleanup2, err := ProvideQuerier(ctx, db)
if err != nil {
cleanup()
return nil, nil, err
}
validate := ProvideValidate()
node, err := ProvideSnowflake(c)
if err != nil {
cleanup2()
cleanup()
return nil, nil, err
}
router, err := route.NewRoute(logger, querier, validate, db, node, c)
if err != nil {
cleanup2()
cleanup()
return nil, nil, err
}
server, cleanup3 := NewServer(c, logger, router)
return server, func() {
cleanup3()
cleanup2()
cleanup()
}, nil
}

View File

@ -18,7 +18,10 @@ import (
"github.com/xmdhs/authlib-skin/utils" "github.com/xmdhs/authlib-skin/utils"
) )
var ErrExistUser = errors.New("用户已存在") var (
ErrExistUser = errors.New("邮箱已存在")
ErrExitsName = errors.New("用户名已存在")
)
func Reg(ctx context.Context, u model.User, q mysql.Querier, db *sql.DB, snow *snowflake.Node, func Reg(ctx context.Context, u model.User, q mysql.Querier, db *sql.DB, snow *snowflake.Node,
c config.Config, c config.Config,
@ -30,7 +33,9 @@ func Reg(ctx context.Context, u model.User, q mysql.Querier, db *sql.DB, snow *s
if ou.Email != "" { if ou.Email != "" {
return fmt.Errorf("Reg: %w", ErrExistUser) return fmt.Errorf("Reg: %w", ErrExistUser)
} }
err = utils.WithTx(ctx, &sql.TxOptions{}, q, db, func(q mysql.Querier) error { err = utils.WithTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
}, q, db, func(q mysql.Querier) error {
p, s := utils.Argon2ID(u.Password) p, s := utils.Argon2ID(u.Password)
userID := snow.Generate().Int64() userID := snow.Generate().Int64()
_, err := q.CreateUser(ctx, mysql.CreateUserParams{ _, err := q.CreateUser(ctx, mysql.CreateUserParams{

38
utils/ip.go Normal file
View File

@ -0,0 +1,38 @@
package utils
import (
"fmt"
"net"
"net/http"
"strings"
)
func GetIP(r *http.Request) (string, error) {
//Get IP from the X-REAL-IP header
ip := r.Header.Get("X-REAL-IP")
netIP := net.ParseIP(ip)
if netIP != nil {
return ip, nil
}
//Get IP from X-FORWARDED-FOR header
ips := r.Header.Get("X-FORWARDED-FOR")
splitIps := strings.Split(ips, ",")
for _, ip := range splitIps {
netIP := net.ParseIP(ip)
if netIP != nil {
return ip, nil
}
}
//Get IP from RemoteAddr
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return "", err
}
netIP = net.ParseIP(ip)
if netIP != nil {
return ip, nil
}
return "", fmt.Errorf("no valid ip found")
}

View File

@ -14,7 +14,6 @@ func WithTx(ctx context.Context, opts *sql.TxOptions, q mysql.Querier, db *sql.D
}) })
var tx *sql.Tx var tx *sql.Tx
if ok { if ok {
fmt.Println("事务开启") // remove me
var err error var err error
tx, err = db.BeginTx(ctx, opts) tx, err = db.BeginTx(ctx, opts)
if err != nil { if err != nil {