package app import ( "errors" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" "gorm.io/gorm" "net/http" "strings" ) var tokenAuth *jwtauth.JWTAuth 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 { u.Name = name } else { return NewError(1, "缺少用户名称") } if phoneNumber, ok := r.Get("phone_number"); ok && len(phoneNumber) > 0 { if len(phoneNumber) != 11 { return NewError(2, "手机号码格式错误") } u.PhoneNumber = phoneNumber } else { return NewError(2, "缺少手机号码") } if password, ok := r.Get("password"); ok && len(password) > 0 { if len(password) < 6 { return NewError(2, "密码太短") } u.Password = password } else { return NewError(2, "缺少密码") } return nil } // CreateUser 创建用户 func CreateUser(w *ResponseWriter, r *Request) { var ui userInfo if err := ui.Bind(r); err != nil { w.Error(err) return } var count int64 if err := DB.Model(&User{}).Where("phone_number = ?", ui.PhoneNumber).Count(&count).Error; err != nil { w.Error(err) return } if count > 0 { w.Error(NewError(2, "手机号码已经被使用了")) return } 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) } else { w.Ok(ui, "创建用户成功") } } // UpdateUser 更新用户 func UpdateUser(w *ResponseWriter, r *Request) { id := chi.URLParam(r.Request, "id") if len(id) == 0 { w.Error(NewError(1, "缺少用户ID")) } var ui userInfo 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) 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 删除用户 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 用户列表 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 用户登录 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 创建商品 func CreateGoods(w *ResponseWriter, r *Request) { name := r.Value("name") price := r.Float32("price", 0) if len(name) == 0 { w.Error(NewError(1, "商品名称错误")) return } if price <= 0 { w.Error(NewError(2, "商品价格错误")) return } // 加载登录用户信息 ut, ok := UserTokenFromContext(r.Context()) if !ok { w.Error(NewError(1, "找不到登录用户消息")) return } // 防止商品重名 var goods Goods err := DB.First(&goods, "name = ?", name).Error if errors.Is(err, gorm.ErrRecordNotFound) { goods = Goods{ Name: name, Price: price, CreatedBy: ut.UserID, Creator: ut.User, UpdatedBy: ut.UserID, Updater: ut.User, } err = DB.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&goods).Error; err != nil { return err } return tx.Create(&Price{ GoodsID: goods.ID, Price: goods.Price, CreatedBy: goods.CreatedBy, CreatedAt: goods.CreatedAt, }).Error }) if err != nil { w.Error(NewError(3, "创建商品失败")) } else { w.Ok(goods) } } else if err != nil { w.Error(err) } else { w.Error(NewError(5, "商品已经存在")) } } // UpdateGoods 更新产品信息 func UpdateGoods(w *ResponseWriter, r *Request) { name := r.Value("name") price := r.Float32("price", 0) id := uint(r.Uint64("id", 0)) if len(name) == 0 { w.Error(NewError(1, "商品名称错误")) return } if price <= 0 { w.Error(NewError(2, "商品价格错误")) return } // 加载登录用户信息 ut, ok := UserTokenFromContext(r.Context()) if !ok { w.Error(NewError(1, "找不到登录用户消息")) return } // 查询商品 var goods Goods err := DB.First(&goods, "id = ?", id).Error if errors.Is(err, gorm.ErrRecordNotFound) { w.Error(NewError(2, "商品不存在")) return } else if err != nil { w.Error(err) return } // 商品名称不能重复 err = DB.Where("id <> ?", id).First(&Goods{}, "name = ?", name).Error if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { w.Error(err) return } // 信息未改变 if goods.Name == name && goods.Price == price { w.Error(NewError(2, "数据未变化")) return } // 价格未改变 if goods.Price == price { if goods.Name != name { goods.Name = name goods.UpdatedBy = ut.UserID goods.Updater = ut.User err = DB.Save(&goods).Error if err != nil { w.Error(err) return } } w.Ok(goods, "修改成功") return } // 修改商品信息并记录价格变化 err = DB.Transaction(func(tx *gorm.DB) error { goods.Name = name goods.Price = price goods.UpdatedBy = ut.UserID goods.Updater = ut.User if err = tx.Save(&goods).Error; err != nil { return err } // 记录价格变化 return tx.Create(&Price{ GoodsID: goods.ID, Price: goods.Price, CreatedBy: ut.UserID, Creator: ut.User, }).Error }) if err != nil { w.Error(err) } else { w.Ok(goods, "修改成功") } } // ListGoods 查询商品列表 func ListGoods(w *ResponseWriter, r *Request) { search := func(db *gorm.DB) *gorm.DB { return db. Model(&Goods{}). Scopes(TimeRange(r, "created_at")). Scopes(Paginate(r)). Scopes(Search(r, "name", "name LIKE ?")) } var goodsList []Goods var total int64 var err error if err = DB.Scopes(search).Count(&total).Error; err == nil { err = DB.Scopes(search).Preload("Creator").Preload("Updater").Find(&goodsList).Error } if err != nil { w.Error(err) } else { w.Ok(map[string]any{ "list": goodsList, "total": total, }) } } // 查询价格列表 func ListPrices(w *ResponseWriter, r *Request) { id := uint(r.Uint64("id", 0)) var goods Goods if err := DB.Scopes(func(db *gorm.DB) *gorm.DB { var queries []string var args []any if val, ok := r.Get("start_time"); ok { queries = append(queries, "created_at >= ?") args = append(args, val) } if val, ok := r.Get("end_time"); ok { queries = append(queries, "created_at <= ?") args = append(args, val) } if len(queries) == 0 { return db.Preload("Prices.Creator") } args = append([]any{strings.Join(queries, " AND ")}, args...) return db.Preload("Prices", args...) }).First(&goods, "id = ?", id).Error; err != nil { w.Error(err) } else { 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(ListGoods)) r.Get("/goods/:id/prices", Handler(ListPrices)) }) }) }