init: 毛孩子计划 小程序 + Go 后端 + 内嵌后台

- pets-fe: 微信原生小程序(首页/计划/记录/报告/社区/引导),
  服务端驱动、无假数据;弹层改用 scroll-view,打开时隐藏自定义 tabBar
- pets-be: Gin + GORM(MySQL, sundynix_ 前缀) + MinIO,统一响应/分页,
  微信 code2session 登录,provider-neutral AI(DeepSeek),go:embed React 后台
- 修复:分段选择类型不匹配(字符串 vs 数字)导致选不中

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-03 15:33:31 +08:00
commit 609f7d06cf
180 changed files with 15259 additions and 0 deletions
+161
View File
@@ -0,0 +1,161 @@
package service
import (
"golang.org/x/crypto/bcrypt"
"github.com/sundynix/pets-be/internal/model"
)
// AdminLogin 校验管理员账号密码
func (s *Service) AdminLogin(username, password string) (*model.Admin, error) {
var admin model.Admin
if err := s.db.Where("username = ?", username).First(&admin).Error; err != nil {
return nil, ErrNotFound
}
if admin.Disabled {
return nil, ErrNotFound
}
if err := bcrypt.CompareHashAndPassword([]byte(admin.PasswordHash), []byte(password)); err != nil {
return nil, ErrNotFound
}
return &admin, nil
}
// GetAdmin 取管理员
func (s *Service) GetAdmin(id uint) (*model.Admin, error) {
var admin model.Admin
if err := s.db.First(&admin, id).Error; err != nil {
return nil, ErrNotFound
}
return &admin, nil
}
// AdminStats 后台统计
type AdminStats struct {
Users int64 `json:"users"`
Pets int64 `json:"pets"`
Posts int64 `json:"posts"`
Records int64 `json:"records"`
}
// Stats Dashboard 计数
func (s *Service) Stats() (*AdminStats, error) {
var st AdminStats
s.db.Model(&model.User{}).Count(&st.Users)
s.db.Model(&model.Pet{}).Count(&st.Pets)
s.db.Model(&model.Post{}).Count(&st.Posts)
s.db.Model(&model.HealthRecord{}).Count(&st.Records)
return &st, nil
}
// ListUsers 用户分页(keyword 匹配昵称)
func (s *Service) ListUsers(keyword string, offset, limit int) ([]model.User, int64, error) {
q := s.db.Model(&model.User{})
if keyword != "" {
q = q.Where("nickname LIKE ?", "%"+keyword+"%")
}
var total int64
q.Count(&total)
var users []model.User
err := q.Order("id desc").Offset(offset).Limit(limit).Find(&users).Error
return users, total, err
}
// SetUserDisabled 启用/禁用用户
func (s *Service) SetUserDisabled(id uint, disabled bool) error {
return s.db.Model(&model.User{}).Where("id = ?", id).Update("disabled", disabled).Error
}
// ListPetsAdmin 宠物分页
func (s *Service) ListPetsAdmin(offset, limit int) ([]model.Pet, int64, error) {
var total int64
s.db.Model(&model.Pet{}).Count(&total)
var pets []model.Pet
err := s.db.Order("id desc").Offset(offset).Limit(limit).Find(&pets).Error
return pets, total, err
}
// ListPostsAdmin 帖子分页(status 可选过滤)
func (s *Service) ListPostsAdmin(status string, offset, limit int) ([]model.Post, int64, error) {
q := s.db.Model(&model.Post{})
if status != "" {
q = q.Where("status = ?", status)
}
var total int64
q.Count(&total)
var posts []model.Post
err := q.Order("id desc").Offset(offset).Limit(limit).Find(&posts).Error
return posts, total, err
}
// SetPostStatus 审核帖子(published/hidden/deleted
func (s *Service) SetPostStatus(id uint, status string) error {
res := s.db.Model(&model.Post{}).Where("id = ?", id).Update("status", status)
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return ErrNotFound
}
return nil
}
// ListCommentsAdmin 评论分页
func (s *Service) ListCommentsAdmin(offset, limit int) ([]model.Comment, int64, error) {
var total int64
s.db.Model(&model.Comment{}).Count(&total)
var comments []model.Comment
err := s.db.Order("id desc").Offset(offset).Limit(limit).Find(&comments).Error
return comments, total, err
}
// DeleteCommentAdmin 删除评论
func (s *Service) DeleteCommentAdmin(id uint) error {
return s.db.Model(&model.Comment{}).Where("id = ?", id).Update("status", "deleted").Error
}
// ListArticlesAdmin 文章分页(含未发布)
func (s *Service) ListArticlesAdmin(offset, limit int) ([]model.Article, int64, error) {
var total int64
s.db.Model(&model.Article{}).Count(&total)
var articles []model.Article
err := s.db.Order("id desc").Offset(offset).Limit(limit).Find(&articles).Error
return articles, total, err
}
// SaveArticle 新增或更新文章(ID 为 0 则新增)
func (s *Service) SaveArticle(a *model.Article) error {
if a.ID == 0 {
return s.db.Create(a).Error
}
return s.db.Model(&model.Article{}).Where("id = ?", a.ID).Updates(map[string]any{
"icon": a.Icon,
"title": a.Title,
"description": a.Description,
"content": a.Content,
"category": a.Category,
"related_sheet_type": a.RelatedSheetType,
"published": a.Published,
}).Error
}
// DeleteArticle 删除文章
func (s *Service) DeleteArticle(id uint) error {
res := s.db.Delete(&model.Article{}, id)
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return ErrNotFound
}
return nil
}
// ListProAdmin 会员分页
func (s *Service) ListProAdmin(offset, limit int) ([]model.ProMembership, int64, error) {
var total int64
s.db.Model(&model.ProMembership{}).Count(&total)
var list []model.ProMembership
err := s.db.Order("id desc").Offset(offset).Limit(limit).Find(&list).Error
return list, total, err
}