Compare commits

...

21 Commits

  1. 21
      app/db.go
  2. 124
      app/fns.go
  3. 50
      app/net.go
  4. 357
      app/rts.go
  5. 14
      go.mod
  6. 51
      go.sum
  7. 46
      main.go

@ -5,8 +5,10 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"math" "math"
"strings" "strings"
"time"
) )
// DB 用户数据操作
var DB *gorm.DB var DB *gorm.DB
func ConfigGormDB() { func ConfigGormDB() {
@ -20,11 +22,23 @@ func ConfigGormDB() {
} }
} }
// User 用户
type User struct { type User struct {
gorm.Model gorm.Model
Name string `json:"name"` // 用户名称 Name string `json:"name"` // 用户名称
PhoneNumber string `json:"phone_number"` // 用户手机 PhoneNumber string `json:"phone_number"` // 用户手机
Password string `json:"password"` // 登录密码 Password string `json:"-"` // 登录密码
Admin bool `json:"-"` // 是不是管理员
}
// UserToken 用户令牌
type UserToken struct {
Code string `gorm:"primarykey"` // 主键
UserID uint // 用户ID
User *User // 关联用户
AccessToken string // 授权令牌
RefreshToken string // 刷新令牌
CreatedAt time.Time // 创建时间
} }
// Goods 商品 // Goods 商品
@ -42,6 +56,7 @@ type Price struct {
Price float32 `json:"price"` // 商品价格 Price float32 `json:"price"` // 商品价格
} }
// Paginate 分页查询作用域
func Paginate(r *Request) func(db *gorm.DB) *gorm.DB { func Paginate(r *Request) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB {
page := r.Int("page", 1, func(p int) int { page := r.Int("page", 1, func(p int) int {
@ -55,16 +70,18 @@ func Paginate(r *Request) func(db *gorm.DB) *gorm.DB {
} }
} }
// Search 搜索作用域
func Search(r *Request, key, query string) func(db *gorm.DB) *gorm.DB { func Search(r *Request, key, query string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB {
if keyword, ok := r.Get(key); ok { if keyword, ok := r.Get(key); ok {
return db.Where(query, keyword) return db.Where(query, "%"+keyword+"%")
} else { } else {
return db return db
} }
} }
} }
// TimeRange 时间范围作用域
func TimeRange(r *Request, column string) func(db *gorm.DB) *gorm.DB { func TimeRange(r *Request, column string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB {
var queries []string var queries []string

@ -0,0 +1,124 @@
package app
import (
"context"
"errors"
"github.com/go-chi/jwtauth/v5"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/rs/xid"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"net/http"
"time"
)
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func GenerateAuthToken(r *Request, uid uint) (*UserToken, error) {
code := xid.New().String()
rawToken, err := jwt.NewBuilder().
//Audience().
Expiration(time.Now().Add(time.Hour*24)).
Issuer(r.URL.Hostname()).
IssuedAt(time.Now()).
//JwtID().
//NotBefore().
//Subject().
Claim("code", code).
Build()
if err != nil {
return nil, err
}
claims, err := rawToken.AsMap(r.Context())
if err != nil {
return nil, err
}
_, accessToken, err := tokenAuth.Encode(claims)
if err != nil {
return nil, NewError(1, "生成授权令牌失败")
}
if err = rawToken.Set(jwt.ExpirationKey, time.Now().Add(time.Hour*24*30)); err != nil {
return nil, err
}
if claims, err = rawToken.AsMap(r.Context()); err != nil {
return nil, err
}
_, refreshToken, err := tokenAuth.Encode(claims)
if err != nil {
return nil, NewError(1, "生成刷新令牌失败")
}
return &UserToken{
Code: code,
UserID: uid,
AccessToken: accessToken,
RefreshToken: refreshToken,
CreatedAt: time.Now(),
}, nil
}
func AuthInfo(r *Request) (*UserToken, error) {
token, _, err := jwtauth.FromContext(r.Context())
if err != nil {
return nil, err
}
code, ok := token.Get("code")
if !ok {
return nil, ErrInvalidToken
}
if _, ok = code.(string); !ok {
return nil, ErrInvalidToken
}
var ut UserToken
err = DB.Model(&UserToken{}).Preload("User").First(&ut, "code = ?", code).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrInvalidToken
}
return nil, err
}
return &ut, nil
}
var ErrInvalidToken = &Error{
Status: http.StatusForbidden,
Code: 401,
Message: "错误令牌",
}
func CheckAuthToken(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, _, _ := jwtauth.FromContext(r.Context())
if code, ok := token.Get("code"); !ok {
NewResponseWriter(w).Error(ErrInvalidToken)
} else if codeString, ok := code.(string); !ok {
NewResponseWriter(w).Error(ErrInvalidToken)
} else if _, err := xid.FromString(codeString); err != nil {
NewResponseWriter(w).Error(ErrInvalidToken)
} else {
var ut UserToken
err = DB.First(&ut, "code = ?", codeString).Error
if err != nil {
NewResponseWriter(w).Error(ErrInvalidToken)
} else {
ctx := context.WithValue(r.Context(), "USER_TOKEN", &ut)
next.ServeHTTP(w, r.WithContext(ctx))
}
}
})
}

@ -13,6 +13,11 @@ type Convertor[V any] func(s string) (V, error)
type HandlerFunc func(w *ResponseWriter, r *Request) type HandlerFunc func(w *ResponseWriter, r *Request)
type ParamGetter func(key string) (string, bool) type ParamGetter func(key string) (string, bool)
// Binder 参数绑定接口
type Binder interface {
Bind(r *Request) error
}
func GetParam[V any](r *Params, key string, def V, convertor Convertor[V], taps []TapFunc[V]) V { func GetParam[V any](r *Params, key string, def V, convertor Convertor[V], taps []TapFunc[V]) V {
var v V var v V
if str, ok := r.Get(key); ok { if str, ok := r.Get(key); ok {
@ -35,7 +40,7 @@ type Params struct {
} }
func NewParams(pg ParamGetter) *Params { func NewParams(pg ParamGetter) *Params {
return &Params{pg } return &Params{pg}
} }
func NewPathParams(r *Request) *Params { func NewPathParams(r *Request) *Params {
@ -101,7 +106,7 @@ func (u *Params) Uint(key string, def uint, taps ...TapFunc[uint]) uint {
func (u *Params) Uint32(key string, def uint32, taps ...TapFunc[uint32]) uint32 { func (u *Params) Uint32(key string, def uint32, taps ...TapFunc[uint32]) uint32 {
return GetParam[uint32](u, key, def, func(s string) (uint32, error) { return GetParam[uint32](u, key, def, func(s string) (uint32, error) {
n,e := strconv.ParseUint(s, 10, 64) n, e := strconv.ParseUint(s, 10, 64)
return uint32(n), e return uint32(n), e
}, taps) }, taps)
} }
@ -125,11 +130,10 @@ func (u *Params) Float64(key string, def float64, taps ...TapFunc[float64]) floa
}, taps) }, taps)
} }
type Request struct { type Request struct {
*http.Request *http.Request
*Params *Params
pathParams *Params pathParams *Params
queryParams *Params queryParams *Params
} }
@ -183,15 +187,15 @@ func (r *Request) QueryParams() *Params {
type ResponseWriter struct { type ResponseWriter struct {
http.ResponseWriter http.ResponseWriter
mutex sync.RWMutex mutex sync.RWMutex
sent bool sent bool
} }
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter { func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
return &ResponseWriter{ return &ResponseWriter{
ResponseWriter: w, ResponseWriter: w,
mutex: sync.RWMutex{}, mutex: sync.RWMutex{},
sent: false, sent: false,
} }
} }
@ -212,8 +216,7 @@ func (w *ResponseWriter) Send(status int, body any) {
buf, err := json.Marshal(body) buf, err := json.Marshal(body)
if err != nil { if err != nil {
LogError(err) w.Error(err)
w.Fail(http.StatusInternalServerError, -1, err.Error())
return return
} }
@ -225,27 +228,27 @@ func (w *ResponseWriter) Send(status int, body any) {
} }
} }
func (w *ResponseWriter) Fail(status int, code int, message ...string) {
info := map[string]any{"code": code, "message": ""}
if len(message) > 0 && len(message[0]) > 0 {
info["message"] = message[0]
} else {
info["message"] = http.StatusText(status)
}
w.Send(status, info)
}
func (w *ResponseWriter) Error(err error) { func (w *ResponseWriter) Error(err error) {
var status int
var code int
var message string
if ex, ok := err.(*Error); ok { if ex, ok := err.(*Error); ok {
status := http.StatusBadRequest status = http.StatusBadRequest
code = ex.Code
message = ex.Message
if ex.Status > 0 { if ex.Status > 0 {
status = ex.Status status = ex.Status
} }
w.Fail(status, ex.Code, ex.Message)
} else { } else {
status = http.StatusInternalServerError
code = -1
message = err.Error()
LogError(err) LogError(err)
w.Fail(http.StatusInternalServerError, -1, err.Error())
} }
if len(message) == 0 {
message = http.StatusText(status)
}
w.Send(status, map[string]any{"code": code, "message": message})
} }
func (w *ResponseWriter) Ok(data any, message ...string) { func (w *ResponseWriter) Ok(data any, message ...string) {
@ -265,4 +268,3 @@ func Handler(hf HandlerFunc) http.HandlerFunc {
hf(NewResponseWriter(w), NewRequest(r)) hf(NewResponseWriter(w), NewRequest(r))
} }
} }

@ -3,52 +3,63 @@ package app
import ( import (
"errors" "errors"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"
"gorm.io/gorm" "gorm.io/gorm"
"net/http" "net/http"
"strconv"
"strings" "strings"
) )
func userInfoFromRequest(r *Request) (*User, error) { var tokenAuth *jwtauth.JWTAuth
var user User
func init() {
tokenAuth = jwtauth.New("HS256", []byte("secret"), nil)
}
type userInfo struct {
id uint
Name string
PhoneNumber string
Password string
}
func (u *userInfo) Bind(r *Request) error {
if name, ok := r.Get("name"); ok && len(name) > 0 { if name, ok := r.Get("name"); ok && len(name) > 0 {
user.Name = name u.Name = name
} else { } else {
return nil, NewError(1, "缺少用户名称") return NewError(1, "缺少用户名称")
} }
if phoneNumber, ok := r.Get("phone_number"); ok && len(phoneNumber) > 0 { if phoneNumber, ok := r.Get("phone_number"); ok && len(phoneNumber) > 0 {
if len(phoneNumber) != 11 { if len(phoneNumber) != 11 {
return nil, NewError(2, "手机号码格式错误") return NewError(2, "手机号码格式错误")
} }
user.PhoneNumber = phoneNumber u.PhoneNumber = phoneNumber
} else { } else {
return nil, NewError(2, "缺少手机号码") return NewError(2, "缺少手机号码")
} }
if password, ok := r.Get("password"); ok && len(password) > 0 { if password, ok := r.Get("password"); ok && len(password) > 0 {
if len(password) < 6 { if len(password) < 6 {
return nil, NewError(2, "密码太短") return NewError(2, "密码太短")
} }
user.Password = password u.Password = password
} else { } else {
return nil, NewError(2, "缺少密码") return NewError(2, "缺少密码")
} }
return &user, nil return nil
} }
// CreateUser 创建用户 // CreateUser 创建用户
func CreateUser(w *ResponseWriter, r *Request) { func CreateUser(w *ResponseWriter, r *Request) {
user, err := userInfoFromRequest(r) var ui userInfo
if err != nil { if err := ui.Bind(r); err != nil {
w.Error(err) w.Error(err)
return return
} }
var count int64 var count int64
if err = DB.Model(&User{}).Where("phone_number = ?", user.PhoneNumber).Count(&count).Error; err != nil { if err := DB.Model(&User{}).Where("phone_number = ?", ui.PhoneNumber).Count(&count).Error; err != nil {
w.Error(err) w.Error(err)
return return
} }
@ -57,10 +68,23 @@ func CreateUser(w *ResponseWriter, r *Request) {
return return
} }
if err = DB.Create(&user).Error; err != nil { hash, err := HashPassword(ui.Password)
if err != nil {
LogError(err)
w.Error(NewError(1, "加密密码失败"))
return
}
user := User{
Name: ui.Name,
PhoneNumber: ui.PhoneNumber,
Password: hash,
Admin: false,
}
if err := DB.Create(&user).Error; err != nil {
w.Error(err) w.Error(err)
} else { } else {
w.Ok(user, "创建用户成功") w.Ok(ui, "创建用户成功")
} }
} }
@ -70,27 +94,193 @@ func UpdateUser(w *ResponseWriter, r *Request) {
if len(id) == 0 { if len(id) == 0 {
w.Error(NewError(1, "缺少用户ID")) w.Error(NewError(1, "缺少用户ID"))
} }
user, err := userInfoFromRequest(r) var ui userInfo
if err != nil { if err := ui.Bind(r); err != nil {
w.Error(err)
return
}
// 查询用户信息
var user User
if err := DB.First(&user, "id = ?", id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = NewError(1, "用户不存在")
}
w.Error(err) w.Error(err)
return return
} }
// 用户信息未发生变化
if ui.Name == user.Name && ui.PhoneNumber == user.PhoneNumber && CheckPasswordHash(ui.Password, user.Password) {
w.Ok(nil, "操作成功")
return
}
// 检查手机号码是否被使用
var count int64
if err := DB.Model(&User{}).Where("phone_number = ? AND id != ?", ui.PhoneNumber, id).Count(&count).Error; err != nil {
w.Error(err)
return
}
if count > 0 {
w.Error(NewError(1, "手机号码已被使用"))
return
}
// 保存用户信息
user.Name = ui.Name
user.PhoneNumber = ui.PhoneNumber
user.Password = ui.Password
if err := DB.Save(&user).Error; err != nil {
w.Error(err)
} else {
w.Ok(nil, "操作成功")
}
} }
// DeleteUser 删除用户 // DeleteUser 删除用户
func DeleteUser(w *ResponseWriter, r *Request) { func DeleteUser(w *ResponseWriter, r *Request) {
id := chi.URLParam(r.Request, "id")
if len(id) == 0 {
w.Error(NewError(1, "缺少用户ID"))
}
// 查询用户信息
var user User
if err := DB.First(&user, "id = ?", id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = NewError(1, "用户不存在")
}
w.Error(err)
return
}
// 删除用户
if err := DB.Delete(&user).Error; err != nil {
LogError(err)
w.Error(NewError(1, "删除用户失败"))
} else {
w.Ok(nil, "删除用户成功")
}
} }
// ListUser 用户列表 // ListUser 用户列表
func ListUser(w *ResponseWriter, r *Request) { func ListUser(w *ResponseWriter, r *Request) {
search := func(db *gorm.DB) *gorm.DB {
return db.
Model(&User{}).
Scopes(Search(r, "name", "name LIKE ?")).
Scopes(Paginate(r))
}
var userList []User
var total int64
var err error
if err = DB.Scopes(search).Count(&total).Error; err == nil {
err = DB.Scopes(search).Find(&userList).Error
}
if err != nil {
w.Error(err)
} else {
w.Ok(map[string]any{
"list": userList,
"total": total,
})
}
} }
// Login 用户登录 // Login 用户登录
func Login(w *ResponseWriter, r *Request) { func Login(w *ResponseWriter, r *Request) {
var phoneNumber string
var password string
var ok bool
// 提交的手机号码
if phoneNumber, ok = r.Get("phone_number"); ok && len(phoneNumber) > 0 {
if len(phoneNumber) != 11 {
w.Error(NewError(2, "手机号码格式错误"))
return
}
} else {
w.Error(NewError(2, "缺少手机号码"))
return
}
// 提交的登陆密码
if password, ok = r.Get("password"); ok && len(password) > 0 {
if len(password) < 6 {
w.Error(NewError(2, "密码太短"))
return
}
} else {
w.Error(NewError(2, "缺少密码"))
return
}
// 查询用户是否存在
var user User
if err := DB.First(&user, "phone_number = ?", phoneNumber).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = NewError(1, "手机号码或密码错误")
}
w.Error(err)
return
}
// 验证密码
if !CheckPasswordHash(password, user.Password) {
w.Error(NewError(1, "手机号码或密码错误"))
return
}
ut, err := GenerateAuthToken(r, user.ID)
if err == nil {
err = DB.Create(&ut).Error
}
if err != nil {
w.Error(err)
return
}
w.Ok(map[string]any{
"user": user,
"access_token": ut.AccessToken,
"refresh_token": ut.RefreshToken,
}, "登录成功")
}
// RefreshToken 刷新授权令牌
func RefreshToken(w *ResponseWriter, r *Request) {
// 获取刷新令牌信息
ut, err := AuthInfo(r)
if err != nil {
w.Error(err)
return
}
// 生成新的令牌
ut2, err := GenerateAuthToken(r, ut.UserID)
if err != nil {
w.Error(err)
return
}
// 删除旧的令牌,保持新的令牌
err = DB.Transaction(func(tx *gorm.DB) error {
if err := tx.Delete(&ut).Error; err != nil {
return err
}
return tx.Create(&ut2).Error
})
if err != nil {
w.Error(err)
return
}
w.Ok(map[string]any{
"access_token": ut.AccessToken,
"refresh_token": ut.RefreshToken,
}, "刷新令牌成功")
} }
// CreateGoods 创建商品 // CreateGoods 创建商品
@ -98,11 +288,11 @@ func CreateGoods(w *ResponseWriter, r *Request) {
name := r.Value("name") name := r.Value("name")
price := r.Float32("price", 0) price := r.Float32("price", 0)
if len(name) == 0 { if len(name) == 0 {
w.Fail(http.StatusBadRequest, 1, "商品名称错误") w.Error(NewError(1, "商品名称错误"))
return return
} }
if price <= 0 { if price <= 0 {
w.Fail(http.StatusBadRequest, 2, "商品价格错误") w.Error(NewError(2, "商品价格错误"))
return return
} }
var goods Goods var goods Goods
@ -114,15 +304,14 @@ func CreateGoods(w *ResponseWriter, r *Request) {
} }
err = DB.Create(&goods).Error err = DB.Create(&goods).Error
if err != nil { if err != nil {
w.Fail(http.StatusBadRequest, 3, "创建商品失败") w.Error(NewError(3, "创建商品失败"))
} else { } else {
w.Ok(goods) w.Ok(goods)
} }
} else if err != nil { } else if err != nil {
LogError(err) w.Error(err)
w.Fail(http.StatusBadRequest, 4, "商品价格错误")
} else { } else {
w.Fail(http.StatusBadRequest, 5, "商品已经存在") w.Error(NewError(5, "商品已经存在"))
} }
} }
@ -132,39 +321,67 @@ func UpdateGoods(w *ResponseWriter, r *Request) {
price := r.Float32("price", 0) price := r.Float32("price", 0)
id := uint(r.Uint64("id", 0)) id := uint(r.Uint64("id", 0))
if len(name) == 0 { if len(name) == 0 {
w.Fail(http.StatusBadRequest, 1, "商品名称错误") w.Error(NewError(1, "商品名称错误"))
return return
} }
if price <= 0 { if price <= 0 {
w.Fail(http.StatusBadRequest, 2, "商品价格错误") w.Error(NewError(2, "商品价格错误"))
return return
} }
var goods Goods var goods Goods
err := DB.First(&goods, "id = ?", id).Error err := DB.First(&goods, "id = ?", id).Error
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
w.Fail(http.StatusBadRequest, 2, "商品不存在") w.Error(NewError(2, "商品不存在"))
return
} else if err != nil { } else if err != nil {
LogError(err) w.Error(err)
w.Fail(http.StatusBadRequest, 3, err.Error()) return
} else { }
// 商品名称不能重复
err = DB.Where("id <> ?", id).First(&Goods{}, "name = ?", name).Error // 商品名称不能重复
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { err = DB.Where("id <> ?", id).First(&Goods{}, "name = ?", name).Error
LogError(err) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
w.Fail(http.StatusBadRequest, 4, err.Error()) w.Error(err)
return return
} }
if goods.Name != name || goods.Price != price {
// 信息未改变
if goods.Name == name && goods.Price == price {
w.Error(NewError(2, "数据未变化"))
return
}
// 价格未改变
if goods.Price == price {
if goods.Name != name {
goods.Name = name goods.Name = name
goods.Price = price
err = DB.Save(&goods).Error err = DB.Save(&goods).Error
if err != nil { if err != nil {
LogError(err) w.Error(err)
w.Fail(http.StatusBadRequest, 5, err.Error())
return return
} }
} }
w.Ok(goods, "修改成功") w.Ok(goods, "修改成功")
return
}
// 修改商品信息并记录价格变化
err = DB.Transaction(func(tx *gorm.DB) error {
goods.Name = name
goods.Price = price
if err = tx.Save(&goods).Error; err != nil {
return err
}
// 记录价格变化
return tx.Create(&Price{
GoodsID: goods.ID,
Price: goods.Price,
}).Error
})
if err != nil {
w.Error(err)
} else {
w.Ok(goods, "修改成功")
} }
} }
@ -184,7 +401,7 @@ func GetGoodsList(w *ResponseWriter, r *Request) {
err = DB.Scopes(search).Find(&goodsList).Error err = DB.Scopes(search).Find(&goodsList).Error
} }
if err != nil { if err != nil {
w.Fail(http.StatusInternalServerError, 1, err.Error()) w.Error(err)
} else { } else {
w.Ok(map[string]any{ w.Ok(map[string]any{
"list": goodsList, "list": goodsList,
@ -213,8 +430,56 @@ func GetGoodsPrices(w *ResponseWriter, r *Request) {
args = append([]any{strings.Join(queries, " AND ")}, args...) args = append([]any{strings.Join(queries, " AND ")}, args...)
return db.Preload("Prices", args...) return db.Preload("Prices", args...)
}).First(&goods, "id = ?", id).Error; err != nil { }).First(&goods, "id = ?", id).Error; err != nil {
w.Fail(http.StatusInternalServerError, 1, err.Error()) w.Error(err)
} else { } else {
w.Ok(goods) w.Ok(goods)
} }
} }
// RegisterRoutes 注册路由
func RegisterRoutes(r chi.Router) {
// 登录接口
r.Post("/login", Handler(Login))
// 需要登录权限的
r.Group(func(r chi.Router) {
r.Use(jwtauth.Verifier(tokenAuth))
r.Use(jwtauth.Authenticator)
r.Use(CheckAuthToken)
// 刷新令牌
r.Get("/refresh-token", Handler(RefreshToken))
// 管理员
r.Group(func(r chi.Router) {
// 验证是不是管理员
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ut, ok := r.Context().Value("USER_TOKEN").(*UserToken)
if ok && ut.User != nil && ut.User.Admin {
next.ServeHTTP(w, r)
return
}
NewResponseWriter(w).Error(&Error{
Status: http.StatusInternalServerError,
Code: 2,
Message: "用户信息错误",
})
})
})
r.Post("/user", Handler(CreateUser))
r.Patch("/user/:id", Handler(UpdateUser))
r.Delete("/user/:id", Handler(DeleteUser))
r.Get("/users", Handler(ListUser))
})
// 普通用户
r.Group(func(r chi.Router) {
r.Post("/goods", Handler(CreateGoods))
r.Patch("/goods/:id", Handler(UpdateGoods))
r.Get("/goods", Handler(GetGoodsList))
r.Get("/goods/:id/prices", Handler(GetGoodsPrices))
})
})
}

@ -4,12 +4,26 @@ go 1.19
require ( require (
github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1
github.com/go-chi/httprate v0.7.0
github.com/go-chi/jwtauth/v5 v5.1.0
github.com/lestrrat-go/jwx/v2 v2.0.6
github.com/rs/xid v1.4.0
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
gorm.io/driver/sqlite v1.4.3 gorm.io/driver/sqlite v1.4.3
gorm.io/gorm v1.24.2 gorm.io/gorm v1.24.2
) )
require ( require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect github.com/mattn/go-sqlite3 v1.14.15 // indirect
) )

@ -1,12 +1,63 @@
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/httprate v0.7.0 h1:8W0dF7Xa2Duz2p8ncGaehIphrxQGNlOtoGY0+NRRfjQ=
github.com/go-chi/httprate v0.7.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
github.com/go-chi/jwtauth/v5 v5.1.0 h1:wJyf2YZ/ohPvNJBwPOzZaQbyzwgMZZceE1m8FOzXLeA=
github.com/go-chi/jwtauth/v5 v5.1.0/go.mod h1:MA93hc1au3tAQwCKry+fI4LqJ5MIVN4XSsglOo+lSc8=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.0.6 h1:RlyYNLV892Ed7+FTfj1ROoF6x7WxL965PGTHso/60G0=
github.com/lestrrat-go/jwx/v2 v2.0.6/go.mod h1:aVrGuwEr3cp2Prw6TtQvr8sQxe+84gruID5C9TxT64Q=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=

@ -3,20 +3,58 @@ package main
import ( import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/httprate"
"hupeh.vip/pricing/app" "hupeh.vip/pricing/app"
"log" "log"
"net/http" "net/http"
"time"
) )
func main() { func main() {
app.ConfigLogger("debug.log", app.LogWhenMinute) app.ConfigLogger("debug.log", app.LogWhenMinute)
app.ConfigGormDB() app.ConfigGormDB()
r := chi.NewRouter() r := chi.NewRouter()
r.Use(middleware.Logger) r.Use(middleware.Logger)
r.Get("/goods", app.Handler(app.GetGoodsList)) r.Use(middleware.Recoverer)
r.Post("/goods", app.Handler(app.CreateGoods)) r.Use(middleware.NoCache)
r.Get("/goods/:id/prices", app.Handler(app.GetGoodsPrices)) r.Use(middleware.Throttle(15))
r.Post("/goods/:id", app.Handler(app.UpdateGoods)) r.Use(middleware.Heartbeat("/"))
//r.Use(middleware.RouteHeaders().
// Route("Host", "example.com", middleware.New(r)).
// Route("Host", "*.example.com", middleware.New(rSubdomain)).
// Handler)
// Enable httprate request limiter of 100 requests per minute.
//
// In the code example below, rate-limiting is bound to the request IP address
// via the LimitByIP middleware handler.
//
// To have a single rate-limiter for all requests, use httprate.LimitAll(..).
//
// Please see _example/main.go for other more, or read the library code.
r.Use(httprate.LimitByIP(100, 1*time.Minute))
// mounting net/http/pprof
r.Mount("/debug", middleware.Profiler())
// 允许跨域
// see: https://developer.github.com/v3/#cross-origin-resource-sharing
r.Use(cors.Handler(cors.Options{
//AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
//AllowedOrigins: []string{"https://*", "http://*"},
AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
AllowedMethods: []string{"GET", "HEAD", "PATCH", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: false,
MaxAge: 300, // Maximum value not ignored by any of major browsers
}))
app.RegisterRoutes(r)
if err := http.ListenAndServe(":3000", r); err != nil { if err := http.ListenAndServe(":3000", r); err != nil {
log.Fatalln(err) log.Fatalln(err)

Loading…
Cancel
Save