第一版完成

This commit is contained in:
2025-07-29 12:45:07 +08:00
commit 7fd39fb409
83 changed files with 8717 additions and 0 deletions

View File

251
internal/service/admin.go Normal file
View File

@@ -0,0 +1,251 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sAdmin struct{}
func Admin() *sAdmin {
return &sAdmin{}
}
// Login 管理员登录
func (s *sAdmin) Login(ctx context.Context, req *model.LoginRequest) (*model.LoginResponse, error) {
// 查询管理员
admin := &model.Admin{}
err := dao.Admin.Ctx(ctx).Where("username", req.Username).Where("deleted_at", 0).Scan(admin)
if err != nil {
return nil, err
}
if admin.Id == 0 {
return nil, gerror.New("用户名或密码错误")
}
// 验证密码
if !s.VerifyPassword(req.Password, admin.Password) {
return nil, gerror.New("用户名或密码错误")
}
// 检查状态
if admin.Status != 1 {
return nil, gerror.New("账号已被禁用")
}
// 更新登录信息
_, err = dao.Admin.Ctx(ctx).Where("id", admin.Id).Update(g.Map{
"last_login_at": gtime.Now().Unix(),
"last_login_ip": g.RequestFromCtx(ctx).GetClientIp(),
"updated_at": gtime.Now(),
})
if err != nil {
g.Log().Error(ctx, "更新登录信息失败:", err)
}
// 生成token
token, err := Auth.GenerateToken(ctx, uint64(admin.Id))
if err != nil {
return nil, err
}
// 清除密码字段
admin.Password = ""
return &model.LoginResponse{
Token: token,
Admin: admin,
}, nil
}
// GetById 根据ID获取管理员信息
func (s *sAdmin) GetById(ctx context.Context, id int) (*model.Admin, error) {
admin := &model.Admin{}
err := dao.Admin.Ctx(ctx).Where("id", id).Where("deleted_at", 0).Scan(admin)
if err != nil {
return nil, err
}
if admin.Id == 0 {
return nil, nil
}
// 清除密码字段
admin.Password = ""
return admin, nil
}
// List 获取管理员列表
func (s *sAdmin) List(ctx context.Context, req *model.ListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
keyword = req.Keyword
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
query := dao.Admin.Ctx(ctx).Where("deleted_at", 0)
// 关键词搜索
if keyword != "" {
query = query.WhereOr("username LIKE ?", "%"+keyword+"%").
WhereOr("real_name LIKE ?", "%"+keyword+"%").
WhereOr("email LIKE ?", "%"+keyword+"%")
}
// 状态筛选
if req.Status >= 0 {
query = query.Where("status", req.Status)
}
// 获取总数
total, err := query.Count()
if err != nil {
return nil, err
}
// 获取列表
var admins []*model.Admin
err = query.Page(page, pageSize).OrderDesc("id").Scan(&admins)
if err != nil {
return nil, err
}
// 清除密码字段
for _, admin := range admins {
admin.Password = ""
}
return &model.PageResponse{
List: admins,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Create 创建管理员
func (s *sAdmin) Create(ctx context.Context, req *model.CreateAdminRequest) error {
// 检查用户名是否存在
count, err := dao.Admin.Ctx(ctx).Where("username", req.Username).Where("deleted_at", 0).Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New("用户名已存在")
}
// 检查邮箱是否存在
count, err = dao.Admin.Ctx(ctx).Where("email", req.Email).Where("deleted_at", 0).Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New("邮箱已存在")
}
// 加密密码
hashedPassword := s.HashPassword(req.Password)
// 创建管理员
_, err = dao.Admin.Ctx(ctx).Insert(g.Map{
"username": req.Username,
"password": hashedPassword,
"real_name": req.RealName,
"email": req.Email,
"phone": req.Phone,
"role_id": req.RoleId,
"status": 1,
"created_at": gtime.Now(),
"updated_at": gtime.Now(),
})
return err
}
// Update 更新管理员
func (s *sAdmin) Update(ctx context.Context, id int, req *model.UpdateAdminRequest) error {
// 检查管理员是否存在
admin, err := s.GetById(ctx, id)
if err != nil {
return err
}
if admin == nil {
return gerror.New("管理员不存在")
}
// 检查邮箱是否被其他管理员使用
count, err := dao.Admin.Ctx(ctx).Where("email", req.Email).Where("id !=", id).Where("deleted_at", 0).Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New("邮箱已被使用")
}
// 更新管理员
_, err = dao.Admin.Ctx(ctx).Where("id", id).Update(g.Map{
"real_name": req.RealName,
"email": req.Email,
"phone": req.Phone,
"role_id": req.RoleId,
"status": req.Status,
"updated_at": gtime.Now(),
})
return err
}
// Delete 删除管理员
func (s *sAdmin) Delete(ctx context.Context, id int) error {
// 检查管理员是否存在
admin, err := s.GetById(ctx, id)
if err != nil {
return err
}
if admin == nil {
return gerror.New("管理员不存在")
}
// 软删除
_, err = dao.Admin.Ctx(ctx).Where("id", id).Update(g.Map{
"deleted_at": gtime.Now().Unix(),
"updated_at": gtime.Now(),
})
return err
}
// HashPassword 加密密码
func (s *sAdmin) HashPassword(password string) string {
return gmd5.MustEncrypt(password + "cms_salt_2024")
}
// VerifyPassword 验证密码
func (s *sAdmin) VerifyPassword(password, hashedPassword string) bool {
return gmd5.MustEncrypt(password+"cms_salt_2024") == hashedPassword
}
// Profile 获取当前管理员信息
func (s *sAdmin) Profile(ctx context.Context) (*model.Admin, error) {
adminId := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
return s.GetById(ctx, adminId)
}
// UpdateProfile 更新当前管理员信息
func (s *sAdmin) UpdateProfile(ctx context.Context, req *model.UpdateAdminRequest) error {
adminId := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
return s.Update(ctx, adminId, req)
}

355
internal/service/article.go Normal file
View File

@@ -0,0 +1,355 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sArticle struct{}
func Article() *sArticle {
return &sArticle{}
}
// GetById 根据ID获取文章信息
func (s *sArticle) GetById(ctx context.Context, id int) (*model.Article, error) {
article, err := dao.Article.GetById(ctx, id)
if err != nil {
return nil, err
}
if article != nil {
// 增加浏览次数
go func() {
dao.Article.IncrementViewCount(context.Background(), id)
}()
}
return article, nil
}
// GetBySlug 根据URL别名获取文章
func (s *sArticle) GetBySlug(ctx context.Context, slug string) (*model.Article, error) {
article, err := dao.Article.GetBySlug(ctx, slug)
if err != nil {
return nil, err
}
if article != nil {
// 增加浏览次数
go func() {
dao.Article.IncrementViewCount(context.Background(), article.Id)
}()
}
return article, nil
}
// List 获取文章列表
func (s *sArticle) List(ctx context.Context, req *model.ArticleListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
articles, total, err := dao.Article.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: articles,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Create 创建文章
func (s *sArticle) Create(ctx context.Context, req *model.CreateArticleRequest) error {
// 检查URL别名是否存在
if req.Slug != "" {
existArticle, err := dao.Article.GetBySlug(ctx, req.Slug)
if err != nil {
return err
}
if existArticle != nil {
return gerror.New("URL别名已存在")
}
} else {
// 如果没有提供slug则根据标题生成
req.Slug = s.generateSlug(req.Title)
}
// 获取当前用户ID
authorId := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
if authorId == 0 {
authorId = gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("user_id"))
}
// 处理HTML内容如果是Markdown需要转换
htmlContent := req.Content
if req.Content != "" {
// 这里可以添加Markdown到HTML的转换逻辑
htmlContent = s.markdownToHTML(req.Content)
}
// 创建文章
article := &model.Article{
Title: req.Title,
Slug: req.Slug,
Summary: req.Summary,
Content: req.Content,
HtmlContent: htmlContent,
CoverImage: req.CoverImage,
CategoryId: req.CategoryId,
Tags: req.Tags,
AuthorId: authorId,
IsPublished: req.IsPublished,
IsFeatured: req.IsFeatured,
IsTop: req.IsTop,
SeoTitle: req.SeoTitle,
SeoDescription: req.SeoDescription,
SeoKeywords: req.SeoKeywords,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
if req.IsPublished == 1 {
article.PublishedAt = gtime.Now()
}
_, err := dao.Article.Create(ctx, article)
return err
}
// Update 更新文章
func (s *sArticle) Update(ctx context.Context, id int, req *model.UpdateArticleRequest) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
// 检查URL别名是否被其他文章使用
if req.Slug != "" && req.Slug != article.Slug {
existArticle, err := dao.Article.GetBySlug(ctx, req.Slug)
if err != nil {
return err
}
if existArticle != nil && existArticle.Id != id {
return gerror.New("URL别名已被使用")
}
}
// 处理HTML内容
htmlContent := req.Content
if req.Content != "" {
htmlContent = s.markdownToHTML(req.Content)
}
// 更新文章
updateData := g.Map{
"title": req.Title,
"slug": req.Slug,
"summary": req.Summary,
"content": req.Content,
"html_content": htmlContent,
"cover_image": req.CoverImage,
"category_id": req.CategoryId,
"tags": req.Tags,
"is_published": req.IsPublished,
"is_featured": req.IsFeatured,
"is_top": req.IsTop,
"seo_title": req.SeoTitle,
"seo_description": req.SeoDescription,
"seo_keywords": req.SeoKeywords,
"updated_at": gtime.Now(),
}
// 如果从草稿变为发布状态,设置发布时间
if req.IsPublished == 1 && article.IsPublished == 0 {
updateData["published_at"] = gtime.Now()
}
return dao.Article.Update(ctx, id, updateData)
}
// Delete 删除文章
func (s *sArticle) Delete(ctx context.Context, id int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
return dao.Article.Delete(ctx, id)
}
// UpdateStatus 更新文章状态
func (s *sArticle) UpdateStatus(ctx context.Context, id int, isPublished int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
updateData := g.Map{
"is_published": isPublished,
"updated_at": gtime.Now(),
}
// 如果是发布状态,设置发布时间
if isPublished == 1 && article.IsPublished == 0 {
updateData["published_at"] = gtime.Now()
}
return dao.Article.Update(ctx, id, updateData)
}
// SetFeatured 设置推荐状态
func (s *sArticle) SetFeatured(ctx context.Context, id int, isFeatured int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
return dao.Article.Update(ctx, id, g.Map{
"is_featured": isFeatured,
"updated_at": gtime.Now(),
})
}
// SetTop 设置置顶状态
func (s *sArticle) SetTop(ctx context.Context, id int, isTop int) error {
// 检查文章是否存在
article, err := s.GetById(ctx, id)
if err != nil {
return err
}
if article == nil {
return gerror.New("文章不存在")
}
return dao.Article.Update(ctx, id, g.Map{
"is_top": isTop,
"updated_at": gtime.Now(),
})
}
// GetStats 获取文章统计信息
func (s *sArticle) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总文章数
totalCount, err := dao.Article.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取已发布文章数
publishedCount, err := dao.Article.GetPublishedCount(ctx)
if err != nil {
return nil, err
}
// 获取草稿数
draftCount := totalCount - publishedCount
// 获取推荐文章数
featuredCount, err := dao.Article.GetFeaturedCount(ctx)
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_count": totalCount,
"published_count": publishedCount,
"draft_count": draftCount,
"featured_count": featuredCount,
}, nil
}
// GetFeatured 获取推荐文章
func (s *sArticle) GetFeatured(ctx context.Context, limit int) ([]*model.Article, error) {
if limit <= 0 {
limit = 5
}
return dao.Article.GetFeatured(ctx, limit)
}
// GetLatest 获取最新文章
func (s *sArticle) GetLatest(ctx context.Context, limit int) ([]*model.Article, error) {
if limit <= 0 {
limit = 10
}
return dao.Article.GetLatest(ctx, limit)
}
// generateSlug 生成URL别名
func (s *sArticle) generateSlug(title string) string {
// 简单的slug生成逻辑实际项目中可能需要更复杂的处理
slug := strings.ToLower(title)
slug = strings.ReplaceAll(slug, " ", "-")
slug = strings.ReplaceAll(slug, " ", "-") // 全角空格
// 移除特殊字符,只保留字母、数字、中文和连字符
// 这里简化处理实际项目中可能需要更完善的slug生成逻辑
return slug
}
// markdownToHTML 将Markdown转换为HTML
func (s *sArticle) markdownToHTML(markdown string) string {
// 这里应该使用Markdown解析库如github.com/russross/blackfriday
// 暂时直接返回原内容实际项目中需要实现Markdown解析
return markdown
}
// Search 搜索文章
func (s *sArticle) Search(ctx context.Context, keyword string, page, pageSize int) (*model.PageResponse, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
articles, total, err := dao.Article.Search(ctx, keyword, page, pageSize)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: articles,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}

View File

@@ -0,0 +1,353 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
type sAttachment struct{}
func Attachment() *sAttachment {
return &sAttachment{}
}
// Upload 上传文件
func (s *sAttachment) Upload(ctx context.Context, file *ghttp.UploadFile, uploadType string) (*model.Attachment, error) {
// 检查文件大小
maxSize := int64(10 * 1024 * 1024) // 10MB
if file.Size > maxSize {
return nil, gerror.New("文件大小不能超过10MB")
}
// 检查文件类型
if !s.isAllowedFileType(file.Filename) {
return nil, gerror.New("不支持的文件类型")
}
// 生成文件名和路径
fileName, filePath, fileUrl := s.generateFilePath(file.Filename, uploadType)
// 确保目录存在
dir := filepath.Dir(filePath)
if !gfile.Exists(dir) {
if err := gfile.Mkdir(dir); err != nil {
return nil, gerror.New("创建目录失败")
}
}
// 保存文件
if _, err := file.Save(filePath, true); err != nil {
return nil, gerror.New("保存文件失败: " + err.Error())
}
// 获取上传者ID
uploadedBy := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
if uploadedBy == 0 {
uploadedBy = gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("user_id"))
}
// 获取客户端IP
uploadIp := g.RequestFromCtx(ctx).GetClientIp()
// 创建附件记录
attachment := &model.Attachment{
OriginalName: file.Filename,
FileName: fileName,
FilePath: filePath,
FileUrl: fileUrl,
FileSize: file.Size,
FileType: s.getFileType(file.Filename),
MimeType: s.getMimeType(file.Filename),
FileExt: s.getFileExt(file.Filename),
StorageType: "local",
UploadIp: uploadIp,
UploadedBy: uploadedBy,
UsageCount: 0,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
id, err := dao.Attachment.Create(ctx, attachment)
if err != nil {
// 如果数据库保存失败,删除已上传的文件
os.Remove(filePath)
return nil, err
}
attachment.Id = int(id)
return attachment, nil
}
// GetById 根据ID获取附件信息
func (s *sAttachment) GetById(ctx context.Context, id int) (*model.Attachment, error) {
return dao.Attachment.GetById(ctx, id)
}
// List 获取附件列表
func (s *sAttachment) List(ctx context.Context, req *model.AttachmentListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
attachments, total, err := dao.Attachment.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: attachments,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Delete 删除附件
func (s *sAttachment) Delete(ctx context.Context, id int) error {
// 检查附件是否存在
attachment, err := s.GetById(ctx, id)
if err != nil {
return err
}
if attachment == nil {
return gerror.New("附件不存在")
}
// 删除物理文件
if gfile.Exists(attachment.FilePath) {
if err := os.Remove(attachment.FilePath); err != nil {
g.Log().Error(ctx, "删除物理文件失败:", err)
}
}
// 删除数据库记录
return dao.Attachment.Delete(ctx, id)
}
// UpdateUsageCount 更新使用次数
func (s *sAttachment) UpdateUsageCount(ctx context.Context, id int) error {
return dao.Attachment.IncrementUsageCount(ctx, id)
}
// GetStats 获取附件统计信息
func (s *sAttachment) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总附件数
totalCount, err := dao.Attachment.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取总文件大小
totalSize, err := dao.Attachment.GetTotalSize(ctx)
if err != nil {
return nil, err
}
// 获取各类型文件数量
imageCount, err := dao.Attachment.GetCountByType(ctx, "image")
if err != nil {
return nil, err
}
documentCount, err := dao.Attachment.GetCountByType(ctx, "document")
if err != nil {
return nil, err
}
videoCount, err := dao.Attachment.GetCountByType(ctx, "video")
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_count": totalCount,
"total_size": totalSize,
"image_count": imageCount,
"document_count": documentCount,
"video_count": videoCount,
}, nil
}
// isAllowedFileType 检查文件类型是否允许
func (s *sAttachment) isAllowedFileType(filename string) bool {
allowedExts := []string{
".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", // 图片
".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", // 文档
".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv", // 视频
".mp3", ".wav", ".flac", ".aac", // 音频
".zip", ".rar", ".7z", ".tar", ".gz", // 压缩包
}
ext := strings.ToLower(filepath.Ext(filename))
for _, allowedExt := range allowedExts {
if ext == allowedExt {
return true
}
}
return false
}
// generateFilePath 生成文件路径
func (s *sAttachment) generateFilePath(originalName, uploadType string) (string, string, string) {
// 获取文件扩展名
ext := filepath.Ext(originalName)
// 生成唯一文件名
hash := gmd5.MustEncrypt(fmt.Sprintf("%s_%d_%s", originalName, time.Now().UnixNano(), grand.S(8)))
fileName := hash + ext
// 根据日期创建目录结构
now := time.Now()
dateDir := fmt.Sprintf("%d/%02d/%02d", now.Year(), now.Month(), now.Day())
// 根据上传类型创建子目录
if uploadType == "" {
uploadType = "general"
}
// 构建完整路径
relativePath := fmt.Sprintf("uploads/%s/%s/%s", uploadType, dateDir, fileName)
fullPath := filepath.Join("storage", relativePath)
fileUrl := "/" + strings.ReplaceAll(relativePath, "\\", "/")
return fileName, fullPath, fileUrl
}
// getFileType 获取文件类型
func (s *sAttachment) getFileType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename))
imageExts := []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
documentExts := []string{".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt"}
videoExts := []string{".mp4", ".avi", ".mov", ".wmv", ".flv", ".mkv"}
audioExts := []string{".mp3", ".wav", ".flac", ".aac"}
archiveExts := []string{".zip", ".rar", ".7z", ".tar", ".gz"}
for _, imageExt := range imageExts {
if ext == imageExt {
return "image"
}
}
for _, docExt := range documentExts {
if ext == docExt {
return "document"
}
}
for _, videoExt := range videoExts {
if ext == videoExt {
return "video"
}
}
for _, audioExt := range audioExts {
if ext == audioExt {
return "audio"
}
}
for _, archiveExt := range archiveExts {
if ext == archiveExt {
return "archive"
}
}
return "other"
}
// getMimeType 获取MIME类型
func (s *sAttachment) getMimeType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename))
mimeTypes := map[string]string{
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".gif": "image/gif",
".bmp": "image/bmp",
".webp": "image/webp",
".pdf": "application/pdf",
".doc": "application/msword",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".ppt": "application/vnd.ms-powerpoint",
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".txt": "text/plain",
".mp4": "video/mp4",
".avi": "video/x-msvideo",
".mov": "video/quicktime",
".wmv": "video/x-ms-wmv",
".flv": "video/x-flv",
".mkv": "video/x-matroska",
".mp3": "audio/mpeg",
".wav": "audio/wav",
".flac": "audio/flac",
".aac": "audio/aac",
".zip": "application/zip",
".rar": "application/x-rar-compressed",
".7z": "application/x-7z-compressed",
".tar": "application/x-tar",
".gz": "application/gzip",
}
if mimeType, exists := mimeTypes[ext]; exists {
return mimeType
}
return "application/octet-stream"
}
// getFileExt 获取文件扩展名
func (s *sAttachment) getFileExt(filename string) string {
ext := filepath.Ext(filename)
if len(ext) > 0 {
return ext[1:] // 去掉点号
}
return ""
}
// BatchDelete 批量删除附件
func (s *sAttachment) BatchDelete(ctx context.Context, ids []int) error {
for _, id := range ids {
if err := s.Delete(ctx, id); err != nil {
g.Log().Error(ctx, "批量删除附件失败, ID:", id, "错误:", err)
// 继续删除其他文件,不中断整个过程
}
}
return nil
}
// GetByIds 根据ID列表获取附件
func (s *sAttachment) GetByIds(ctx context.Context, ids []int) ([]*model.Attachment, error) {
return dao.Attachment.GetByIds(ctx, ids)
}

113
internal/service/auth.go Normal file
View File

@@ -0,0 +1,113 @@
package service
import (
"context"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/golang-jwt/jwt/v5"
)
type sAuth struct{}
var Auth = sAuth{}
// Claims JWT声明
type Claims struct {
UserId uint64 `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
// GenerateToken 生成JWT Token
func (s *sAuth) GenerateToken(ctx context.Context, userId uint64) (string, error) {
// 获取JWT配置
jwtConfig := g.Cfg().MustGet(ctx, "jwt")
signingKey := jwtConfig.Map()["signingKey"].(string)
expire := gconv.Int64(jwtConfig.Map()["expire"])
// 创建声明
claims := Claims{
UserId: userId,
Username: "", // 将在后续获取
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expire) * time.Second)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "cms-api",
Subject: gconv.String(userId),
},
}
// 创建token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 签名token
tokenString, err := token.SignedString([]byte(signingKey))
if err != nil {
return "", gerror.New("生成Token失败")
}
return tokenString, nil
}
// ParseToken 解析JWT Token
func (s *sAuth) ParseToken(ctx context.Context, tokenString string) (*Claims, error) {
// 获取JWT配置
jwtConfig := g.Cfg().MustGet(ctx, "jwt")
signingKey := jwtConfig.Map()["signingKey"].(string)
// 解析token
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(signingKey), nil
})
if err != nil {
return nil, gerror.New("Token解析失败")
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, gerror.New("Token无效")
}
// ValidateToken 验证Token
func (s *sAuth) ValidateToken(ctx context.Context, tokenString string) (uint64, error) {
claims, err := s.ParseToken(ctx, tokenString)
if err != nil {
return 0, err
}
// 检查管理员是否存在且状态正常
admin, err := Admin().GetById(ctx, int(claims.UserId))
if err != nil {
return 0, gerror.New("管理员不存在")
}
if admin == nil || admin.Status != 1 {
return 0, gerror.New("管理员已被禁用")
}
return claims.UserId, nil
}
// RefreshToken 刷新Token
func (s *sAuth) RefreshToken(ctx context.Context, tokenString string) (string, error) {
claims, err := s.ParseToken(ctx, tokenString)
if err != nil {
return "", err
}
// 生成新的Token
return s.GenerateToken(ctx, claims.UserId)
}
// GetTokenExpire 获取Token过期时间
func (s *sAuth) GetTokenExpire(ctx context.Context) int {
jwtConfig := g.Cfg().MustGet(ctx, "jwt")
return gconv.Int(jwtConfig.Map()["expire"])
}

413
internal/service/config.go Normal file
View File

@@ -0,0 +1,413 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"encoding/json"
"regexp"
"strconv"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sConfig struct{}
func Config() *sConfig {
return &sConfig{}
}
// GetByKey 根据配置键获取配置
func (s *sConfig) GetByKey(ctx context.Context, key string) (*model.SiteConfig, error) {
return dao.Config.GetByKey(ctx, key)
}
// GetValue 获取配置值
func (s *sConfig) GetValue(ctx context.Context, key string, defaultValue ...interface{}) interface{} {
config, err := s.GetByKey(ctx, key)
if err != nil || config == nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return nil
}
// 根据配置类型转换值
switch config.ConfigType {
case "number":
return gconv.Int(config.ConfigValue)
case "boolean":
return gconv.Bool(config.ConfigValue)
case "json", "array":
var result interface{}
if err := json.Unmarshal([]byte(config.ConfigValue), &result); err == nil {
return result
}
return config.ConfigValue
default:
return config.ConfigValue
}
}
// GetString 获取字符串配置值
func (s *sConfig) GetString(ctx context.Context, key string, defaultValue ...string) string {
value := s.GetValue(ctx, key)
if value == nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return ""
}
return gconv.String(value)
}
// GetInt 获取整数配置值
func (s *sConfig) GetInt(ctx context.Context, key string, defaultValue ...int) int {
value := s.GetValue(ctx, key)
if value == nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return 0
}
return gconv.Int(value)
}
// GetBool 获取布尔配置值
func (s *sConfig) GetBool(ctx context.Context, key string, defaultValue ...bool) bool {
value := s.GetValue(ctx, key)
if value == nil {
if len(defaultValue) > 0 {
return defaultValue[0]
}
return false
}
return gconv.Bool(value)
}
// List 获取配置列表
func (s *sConfig) List(ctx context.Context, req *model.ConfigListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
configs, total, err := dao.Config.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: configs,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// GetByGroup 根据分组获取配置
func (s *sConfig) GetByGroup(ctx context.Context, groupName string) ([]*model.SiteConfig, error) {
return dao.Config.GetByGroup(ctx, groupName)
}
// GetGroups 获取所有配置分组
func (s *sConfig) GetGroups(ctx context.Context) ([]string, error) {
return dao.Config.GetGroups(ctx)
}
// Create 创建配置
func (s *sConfig) Create(ctx context.Context, req *model.CreateConfigRequest) error {
// 检查配置键是否存在
existConfig, err := dao.Config.GetByKey(ctx, req.ConfigKey)
if err != nil {
return err
}
if existConfig != nil {
return gerror.New("配置键已存在")
}
// 验证配置值格式
if err := s.validateConfigValue(req.ConfigValue, req.ConfigType); err != nil {
return err
}
// 创建配置
config := &model.SiteConfig{
ConfigKey: req.ConfigKey,
ConfigValue: req.ConfigValue,
ConfigType: req.ConfigType,
GroupName: req.GroupName,
Description: req.Description,
SortOrder: req.SortOrder,
IsSystem: req.IsSystem,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
_, err = dao.Config.Create(ctx, config)
return err
}
// Update 更新配置
func (s *sConfig) Update(ctx context.Context, key string, req *model.UpdateConfigRequest) error {
// 检查配置是否存在
config, err := s.GetByKey(ctx, key)
if err != nil {
return err
}
if config == nil {
return gerror.New("配置不存在")
}
// 验证配置值格式
if err := s.validateConfigValue(req.ConfigValue, req.ConfigType); err != nil {
return err
}
// 更新配置
updateData := g.Map{
"config_value": req.ConfigValue,
"config_type": req.ConfigType,
"group_name": req.GroupName,
"description": req.Description,
"sort_order": req.SortOrder,
"is_system": req.IsSystem,
"updated_at": gtime.Now(),
}
return dao.Config.UpdateByKey(ctx, key, updateData)
}
// UpdateValue 更新配置值
func (s *sConfig) UpdateValue(ctx context.Context, key string, value interface{}) error {
// 检查配置是否存在
config, err := s.GetByKey(ctx, key)
if err != nil {
return err
}
if config == nil {
return gerror.New("配置不存在")
}
// 转换值为字符串
var valueStr string
switch config.ConfigType {
case "json", "array":
if jsonBytes, err := json.Marshal(value); err == nil {
valueStr = string(jsonBytes)
} else {
return gerror.New("配置值格式错误")
}
default:
valueStr = gconv.String(value)
}
// 验证配置值格式
if err := s.validateConfigValue(valueStr, config.ConfigType); err != nil {
return err
}
// 更新配置值
return dao.Config.UpdateValue(ctx, key, valueStr)
}
// BatchUpdate 批量更新配置
func (s *sConfig) BatchUpdate(ctx context.Context, req *model.BatchUpdateConfigRequest) error {
for key, value := range req.Configs {
if err := s.UpdateValue(ctx, key, value); err != nil {
g.Log().Error(ctx, "批量更新配置失败, 键:", key, "错误:", err)
// 继续更新其他配置,不中断整个过程
}
}
return nil
}
// Delete 删除配置
func (s *sConfig) Delete(ctx context.Context, key string) error {
// 检查配置是否存在
config, err := s.GetByKey(ctx, key)
if err != nil {
return err
}
if config == nil {
return gerror.New("配置不存在")
}
// 检查是否为系统配置
if config.IsSystem == 1 {
return gerror.New("系统配置不能删除")
}
return dao.Config.DeleteByKey(ctx, key)
}
// InitDefaultConfigs 初始化默认配置
func (s *sConfig) InitDefaultConfigs(ctx context.Context) error {
defaultConfigs := []model.SiteConfig{
{
ConfigKey: "site_name",
ConfigValue: "企业官网CMS系统",
ConfigType: "string",
GroupName: "基础设置",
Description: "网站名称",
SortOrder: 1,
IsSystem: 1,
},
{
ConfigKey: "site_title",
ConfigValue: "企业官网CMS系统 - 专业的内容管理平台",
ConfigType: "string",
GroupName: "基础设置",
Description: "网站标题",
SortOrder: 2,
IsSystem: 1,
},
{
ConfigKey: "site_description",
ConfigValue: "专业的企业官网内容管理系统,提供完整的内容发布和管理功能",
ConfigType: "string",
GroupName: "基础设置",
Description: "网站描述",
SortOrder: 3,
IsSystem: 1,
},
{
ConfigKey: "site_keywords",
ConfigValue: "企业官网,CMS,内容管理,新闻发布",
ConfigType: "string",
GroupName: "基础设置",
Description: "网站关键词",
SortOrder: 4,
IsSystem: 1,
},
{
ConfigKey: "contact_phone",
ConfigValue: "400-123-4567",
ConfigType: "string",
GroupName: "联系方式",
Description: "联系电话",
SortOrder: 1,
IsSystem: 0,
},
{
ConfigKey: "contact_email",
ConfigValue: "contact@example.com",
ConfigType: "string",
GroupName: "联系方式",
Description: "联系邮箱",
SortOrder: 2,
IsSystem: 0,
},
{
ConfigKey: "contact_address",
ConfigValue: "北京市朝阳区xxx大厦xxx室",
ConfigType: "string",
GroupName: "联系方式",
Description: "联系地址",
SortOrder: 3,
IsSystem: 0,
},
{
ConfigKey: "upload_max_size",
ConfigValue: "10485760",
ConfigType: "number",
GroupName: "上传设置",
Description: "最大上传文件大小(字节)",
SortOrder: 1,
IsSystem: 1,
},
{
ConfigKey: "allowed_file_types",
ConfigValue: `["jpg","jpeg","png","gif","pdf","doc","docx","xls","xlsx"]`,
ConfigType: "array",
GroupName: "上传设置",
Description: "允许上传的文件类型",
SortOrder: 2,
IsSystem: 1,
},
}
for _, config := range defaultConfigs {
// 检查配置是否已存在
existConfig, err := dao.Config.GetByKey(ctx, config.ConfigKey)
if err != nil {
continue
}
if existConfig != nil {
continue // 跳过已存在的配置
}
// 创建配置
config.CreatedAt = gtime.Now()
config.UpdatedAt = gtime.Now()
dao.Config.Create(ctx, &config)
}
return nil
}
// validateConfigValue 验证配置值格式
func (s *sConfig) validateConfigValue(value, configType string) error {
switch configType {
case "number":
if _, err := strconv.ParseFloat(value, 64); err != nil {
return gerror.New("配置值必须是数字")
}
case "boolean":
if value != "true" && value != "false" && value != "1" && value != "0" {
return gerror.New("配置值必须是布尔值")
}
case "json", "array":
var temp interface{}
if err := json.Unmarshal([]byte(value), &temp); err != nil {
return gerror.New("配置值必须是有效的JSON格式")
}
}
return nil
}
// isNumeric 检查字符串是否为数字
func (s *sConfig) isNumeric(str string) bool {
matched, _ := regexp.MatchString(`^-?\d+(\.\d+)?$`, str)
return matched
}
// GetPublicConfigs 获取公开配置(前台可访问)
func (s *sConfig) GetPublicConfigs(ctx context.Context) (map[string]interface{}, error) {
// 定义公开配置键
publicKeys := []string{
"site_name",
"site_title",
"site_description",
"site_keywords",
"contact_phone",
"contact_email",
"contact_address",
}
result := make(map[string]interface{})
for _, key := range publicKeys {
value := s.GetValue(ctx, key)
if value != nil {
result[key] = value
}
}
return result, nil
}

286
internal/service/contact.go Normal file
View File

@@ -0,0 +1,286 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sContact struct{}
func Contact() *sContact {
return &sContact{}
}
// GetById 根据ID获取联系信息
func (s *sContact) GetById(ctx context.Context, id int) (*model.Contact, error) {
return dao.Contact.GetById(ctx, id)
}
// List 获取联系信息列表
func (s *sContact) List(ctx context.Context, req *model.ContactListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
contacts, total, err := dao.Contact.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: contacts,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Create 创建联系信息(前台提交表单)
func (s *sContact) Create(ctx context.Context, req *model.CreateContactRequest) error {
// 获取客户端信息
request := g.RequestFromCtx(ctx)
ip := request.GetClientIp()
userAgent := request.Header.Get("User-Agent")
// 创建联系信息
contact := &model.Contact{
Name: req.Name,
Email: req.Email,
Phone: req.Phone,
Company: req.Company,
Subject: req.Subject,
Message: req.Message,
Ip: ip,
UserAgent: userAgent,
Status: 0, // 未处理
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
_, err := dao.Contact.Create(ctx, contact)
return err
}
// Reply 回复联系信息
func (s *sContact) Reply(ctx context.Context, id int, req *model.ReplyContactRequest) error {
// 检查联系信息是否存在
contact, err := s.GetById(ctx, id)
if err != nil {
return err
}
if contact == nil {
return gerror.New("联系信息不存在")
}
// 获取回复人ID
repliedBy := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("admin_id"))
if repliedBy == 0 {
return gerror.New("未获取到管理员信息")
}
// 更新回复信息
updateData := g.Map{
"reply": req.Reply,
"status": 2, // 已回复
"replied_at": gtime.Now(),
"replied_by": repliedBy,
"updated_at": gtime.Now(),
}
return dao.Contact.Update(ctx, id, updateData)
}
// UpdateStatus 更新处理状态
func (s *sContact) UpdateStatus(ctx context.Context, id int, status int) error {
// 检查联系信息是否存在
contact, err := s.GetById(ctx, id)
if err != nil {
return err
}
if contact == nil {
return gerror.New("联系信息不存在")
}
updateData := g.Map{
"status": status,
"updated_at": gtime.Now(),
}
// 如果状态改为已处理但没有回复,只更新状态
if status == 1 && contact.Status == 0 {
// 已处理状态
}
return dao.Contact.Update(ctx, id, updateData)
}
// Delete 删除联系信息
func (s *sContact) Delete(ctx context.Context, id int) error {
// 检查联系信息是否存在
contact, err := s.GetById(ctx, id)
if err != nil {
return err
}
if contact == nil {
return gerror.New("联系信息不存在")
}
return dao.Contact.Delete(ctx, id)
}
// GetStats 获取联系信息统计
func (s *sContact) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总联系信息数
totalCount, err := dao.Contact.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取各状态联系信息数
unprocessedCount, err := dao.Contact.GetCountByStatus(ctx, 0)
if err != nil {
return nil, err
}
processedCount, err := dao.Contact.GetCountByStatus(ctx, 1)
if err != nil {
return nil, err
}
repliedCount, err := dao.Contact.GetCountByStatus(ctx, 2)
if err != nil {
return nil, err
}
// 获取今日新增联系信息数
todayCount, err := dao.Contact.GetTodayCount(ctx)
if err != nil {
return nil, err
}
// 获取本周新增联系信息数
weekCount, err := dao.Contact.GetWeekCount(ctx)
if err != nil {
return nil, err
}
// 获取本月新增联系信息数
monthCount, err := dao.Contact.GetMonthCount(ctx)
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_count": totalCount,
"unprocessed_count": unprocessedCount,
"processed_count": processedCount,
"replied_count": repliedCount,
"today_count": todayCount,
"week_count": weekCount,
"month_count": monthCount,
}, nil
}
// GetLatest 获取最新联系信息
func (s *sContact) GetLatest(ctx context.Context, limit int) ([]*model.Contact, error) {
if limit <= 0 {
limit = 10
}
return dao.Contact.GetLatest(ctx, limit)
}
// GetUnprocessed 获取未处理的联系信息
func (s *sContact) GetUnprocessed(ctx context.Context, limit int) ([]*model.Contact, error) {
if limit <= 0 {
limit = 20
}
return dao.Contact.GetUnprocessed(ctx, limit)
}
// BatchUpdateStatus 批量更新状态
func (s *sContact) BatchUpdateStatus(ctx context.Context, ids []int, status int) error {
for _, id := range ids {
if err := s.UpdateStatus(ctx, id, status); err != nil {
g.Log().Error(ctx, "批量更新联系信息状态失败, ID:", id, "错误:", err)
// 继续更新其他记录,不中断整个过程
}
}
return nil
}
// BatchDelete 批量删除联系信息
func (s *sContact) BatchDelete(ctx context.Context, ids []int) error {
for _, id := range ids {
if err := s.Delete(ctx, id); err != nil {
g.Log().Error(ctx, "批量删除联系信息失败, ID:", id, "错误:", err)
// 继续删除其他记录,不中断整个过程
}
}
return nil
}
// Search 搜索联系信息
func (s *sContact) Search(ctx context.Context, keyword string, page, pageSize int) (*model.PageResponse, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
contacts, total, err := dao.Contact.Search(ctx, keyword, page, pageSize)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: contacts,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Export 导出联系信息
func (s *sContact) Export(ctx context.Context, req *model.ContactListRequest) ([]*model.Contact, error) {
// 设置大的页面大小来获取所有数据
req.Page = 1
req.PageSize = 10000
contacts, _, err := dao.Contact.List(ctx, req)
return contacts, err
}
// GetContactTrends 获取联系信息趋势数据
func (s *sContact) GetContactTrends(ctx context.Context, days int) ([]map[string]interface{}, error) {
if days <= 0 {
days = 7 // 默认7天
}
return dao.Contact.GetTrends(ctx, days)
}
// GetTrends 获取联系信息趋势数据(兼容性方法)
func (s *sContact) GetTrends(ctx context.Context, days int) ([]map[string]interface{}, error) {
return s.GetContactTrends(ctx, days)
}

355
internal/service/news.go Normal file
View File

@@ -0,0 +1,355 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type sNews struct{}
func News() *sNews {
return &sNews{}
}
// GetById 根据ID获取新闻信息
func (s *sNews) GetById(ctx context.Context, id int) (*model.News, error) {
news, err := dao.News.GetById(ctx, id)
if err != nil {
return nil, err
}
if news != nil {
// 增加浏览次数
go func() {
dao.News.IncrementViewCount(context.Background(), id)
}()
}
return news, nil
}
// GetBySlug 根据URL别名获取新闻
func (s *sNews) GetBySlug(ctx context.Context, slug string) (*model.News, error) {
news, err := dao.News.GetBySlug(ctx, slug)
if err != nil {
return nil, err
}
if news != nil {
// 增加浏览次数
go func() {
dao.News.IncrementViewCount(context.Background(), news.Id)
}()
}
return news, nil
}
// List 获取新闻列表
func (s *sNews) List(ctx context.Context, req *model.NewsListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
newsList, total, err := dao.News.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: newsList,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Create 创建新闻
func (s *sNews) Create(ctx context.Context, req *model.CreateNewsRequest) error {
// 检查URL别名是否存在
if req.Slug != "" {
existNews, err := dao.News.GetBySlug(ctx, req.Slug)
if err != nil {
return err
}
if existNews != nil {
return gerror.New("URL别名已存在")
}
} else {
// 如果没有提供slug则根据标题生成
req.Slug = s.generateSlug(req.Title)
}
// 创建新闻
news := &model.News{
Title: req.Title,
Slug: req.Slug,
Summary: req.Summary,
Content: req.Content,
CoverImage: req.CoverImage,
Category: req.Category,
Source: req.Source,
Author: req.Author,
IsPublished: req.IsPublished,
IsFeatured: req.IsFeatured,
IsTop: req.IsTop,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
if req.IsPublished == 1 {
news.PublishedAt = gtime.Now()
}
_, err := dao.News.Create(ctx, news)
return err
}
// Update 更新新闻
func (s *sNews) Update(ctx context.Context, id int, req *model.UpdateNewsRequest) error {
// 检查新闻是否存在
news, err := s.GetById(ctx, id)
if err != nil {
return err
}
if news == nil {
return gerror.New("新闻不存在")
}
// 检查URL别名是否被其他新闻使用
if req.Slug != "" && req.Slug != news.Slug {
existNews, err := dao.News.GetBySlug(ctx, req.Slug)
if err != nil {
return err
}
if existNews != nil && existNews.Id != id {
return gerror.New("URL别名已被使用")
}
}
// 更新新闻
updateData := g.Map{
"title": req.Title,
"slug": req.Slug,
"summary": req.Summary,
"content": req.Content,
"cover_image": req.CoverImage,
"category": req.Category,
"source": req.Source,
"author": req.Author,
"is_published": req.IsPublished,
"is_featured": req.IsFeatured,
"is_top": req.IsTop,
"updated_at": gtime.Now(),
}
// 如果从草稿变为发布状态,设置发布时间
if req.IsPublished == 1 && news.IsPublished == 0 {
updateData["published_at"] = gtime.Now()
}
return dao.News.Update(ctx, id, updateData)
}
// Delete 删除新闻
func (s *sNews) Delete(ctx context.Context, id int) error {
// 检查新闻是否存在
news, err := s.GetById(ctx, id)
if err != nil {
return err
}
if news == nil {
return gerror.New("新闻不存在")
}
return dao.News.Delete(ctx, id)
}
// UpdateStatus 更新新闻状态
func (s *sNews) UpdateStatus(ctx context.Context, id int, isPublished int) error {
// 检查新闻是否存在
news, err := s.GetById(ctx, id)
if err != nil {
return err
}
if news == nil {
return gerror.New("新闻不存在")
}
updateData := g.Map{
"is_published": isPublished,
"updated_at": gtime.Now(),
}
// 如果是发布状态,设置发布时间
if isPublished == 1 && news.IsPublished == 0 {
updateData["published_at"] = gtime.Now()
}
return dao.News.Update(ctx, id, updateData)
}
// SetFeatured 设置推荐状态
func (s *sNews) SetFeatured(ctx context.Context, id int, isFeatured int) error {
// 检查新闻是否存在
news, err := s.GetById(ctx, id)
if err != nil {
return err
}
if news == nil {
return gerror.New("新闻不存在")
}
return dao.News.Update(ctx, id, g.Map{
"is_featured": isFeatured,
"updated_at": gtime.Now(),
})
}
// SetTop 设置置顶状态
func (s *sNews) SetTop(ctx context.Context, id int, isTop int) error {
// 检查新闻是否存在
news, err := s.GetById(ctx, id)
if err != nil {
return err
}
if news == nil {
return gerror.New("新闻不存在")
}
return dao.News.Update(ctx, id, g.Map{
"is_top": isTop,
"updated_at": gtime.Now(),
})
}
// GetStats 获取新闻统计信息
func (s *sNews) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总新闻数
totalCount, err := dao.News.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取已发布新闻数
publishedCount, err := dao.News.GetPublishedCount(ctx)
if err != nil {
return nil, err
}
// 获取草稿数
draftCount := totalCount - publishedCount
// 获取推荐新闻数
featuredCount, err := dao.News.GetFeaturedCount(ctx)
if err != nil {
return nil, err
}
// 获取各分类新闻数
companyNewsCount, err := dao.News.GetCountByCategory(ctx, "company_news")
if err != nil {
return nil, err
}
industryNewsCount, err := dao.News.GetCountByCategory(ctx, "industry_news")
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_count": totalCount,
"published_count": publishedCount,
"draft_count": draftCount,
"featured_count": featuredCount,
"company_news_count": companyNewsCount,
"industry_news_count": industryNewsCount,
}, nil
}
// GetFeatured 获取推荐新闻
func (s *sNews) GetFeatured(ctx context.Context, limit int) ([]*model.News, error) {
if limit <= 0 {
limit = 5
}
return dao.News.GetFeatured(ctx, limit)
}
// GetLatest 获取最新新闻
func (s *sNews) GetLatest(ctx context.Context, limit int) ([]*model.News, error) {
if limit <= 0 {
limit = 10
}
return dao.News.GetLatest(ctx, limit)
}
// GetByCategory 根据分类获取新闻
func (s *sNews) GetByCategory(ctx context.Context, category string, page, pageSize int) (*model.PageResponse, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
newsList, total, err := dao.News.GetByCategory(ctx, category, page, pageSize)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: newsList,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// generateSlug 生成URL别名
func (s *sNews) generateSlug(title string) string {
// 简单的slug生成逻辑
slug := strings.ToLower(title)
slug = strings.ReplaceAll(slug, " ", "-")
slug = strings.ReplaceAll(slug, " ", "-") // 全角空格
return slug
}
// Search 搜索新闻
func (s *sNews) Search(ctx context.Context, keyword string, page, pageSize int) (*model.PageResponse, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
newsList, total, err := dao.News.Search(ctx, keyword, page, pageSize)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: newsList,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}

264
internal/service/partner.go Normal file
View File

@@ -0,0 +1,264 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type sPartner struct{}
func Partner() *sPartner {
return &sPartner{}
}
// GetById 根据ID获取合作伙伴信息
func (s *sPartner) GetById(ctx context.Context, id int) (*model.Partner, error) {
return dao.Partner.GetById(ctx, id)
}
// List 获取合作伙伴列表
func (s *sPartner) List(ctx context.Context, req *model.PartnerListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
partners, total, err := dao.Partner.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: partners,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// GetAll 获取所有启用的合作伙伴
func (s *sPartner) GetAll(ctx context.Context) ([]*model.Partner, error) {
return dao.Partner.GetAll(ctx)
}
// GetFeatured 获取推荐合作伙伴
func (s *sPartner) GetFeatured(ctx context.Context, limit int) ([]*model.Partner, error) {
if limit <= 0 {
limit = 10
}
return dao.Partner.GetFeatured(ctx, limit)
}
// GetByCategory 根据分类获取合作伙伴
func (s *sPartner) GetByCategory(ctx context.Context, category string) ([]*model.Partner, error) {
return dao.Partner.GetByCategory(ctx, category)
}
// Create 创建合作伙伴
func (s *sPartner) Create(ctx context.Context, req *model.CreatePartnerRequest) error {
// 检查合作伙伴名称是否存在
existPartner, err := dao.Partner.GetByName(ctx, req.Name)
if err != nil {
return err
}
if existPartner != nil {
return gerror.New("合作伙伴名称已存在")
}
// 创建合作伙伴
partner := &model.Partner{
Name: req.Name,
Logo: req.Logo,
Website: req.Website,
Description: req.Description,
Category: req.Category,
SortOrder: req.SortOrder,
IsFeatured: req.IsFeatured,
Status: req.Status,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
_, err = dao.Partner.Create(ctx, partner)
return err
}
// Update 更新合作伙伴
func (s *sPartner) Update(ctx context.Context, id int, req *model.UpdatePartnerRequest) error {
// 检查合作伙伴是否存在
partner, err := s.GetById(ctx, id)
if err != nil {
return err
}
if partner == nil {
return gerror.New("合作伙伴不存在")
}
// 检查合作伙伴名称是否被其他记录使用
existPartner, err := dao.Partner.GetByName(ctx, req.Name)
if err != nil {
return err
}
if existPartner != nil && existPartner.Id != id {
return gerror.New("合作伙伴名称已被使用")
}
// 更新合作伙伴
updateData := g.Map{
"name": req.Name,
"logo": req.Logo,
"website": req.Website,
"description": req.Description,
"category": req.Category,
"sort_order": req.SortOrder,
"is_featured": req.IsFeatured,
"status": req.Status,
"updated_at": gtime.Now(),
}
return dao.Partner.Update(ctx, id, updateData)
}
// Delete 删除合作伙伴
func (s *sPartner) Delete(ctx context.Context, id int) error {
// 检查合作伙伴是否存在
partner, err := s.GetById(ctx, id)
if err != nil {
return err
}
if partner == nil {
return gerror.New("合作伙伴不存在")
}
return dao.Partner.Delete(ctx, id)
}
// UpdateStatus 更新合作伙伴状态
func (s *sPartner) UpdateStatus(ctx context.Context, id int, status int) error {
// 检查合作伙伴是否存在
partner, err := s.GetById(ctx, id)
if err != nil {
return err
}
if partner == nil {
return gerror.New("合作伙伴不存在")
}
return dao.Partner.UpdateStatus(ctx, id, status)
}
// SetFeatured 设置推荐状态
func (s *sPartner) SetFeatured(ctx context.Context, id int, isFeatured int) error {
// 检查合作伙伴是否存在
partner, err := s.GetById(ctx, id)
if err != nil {
return err
}
if partner == nil {
return gerror.New("合作伙伴不存在")
}
return dao.Partner.Update(ctx, id, g.Map{
"is_featured": isFeatured,
"updated_at": gtime.Now(),
})
}
// UpdateSortOrder 更新排序
func (s *sPartner) UpdateSortOrder(ctx context.Context, id int, sortOrder int) error {
// 检查合作伙伴是否存在
partner, err := s.GetById(ctx, id)
if err != nil {
return err
}
if partner == nil {
return gerror.New("合作伙伴不存在")
}
return dao.Partner.Update(ctx, id, g.Map{
"sort_order": sortOrder,
"updated_at": gtime.Now(),
})
}
// GetStats 获取合作伙伴统计信息
func (s *sPartner) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总合作伙伴数
totalCount, err := dao.Partner.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取启用的合作伙伴数
activeCount, err := dao.Partner.GetCountByStatus(ctx, 1)
if err != nil {
return nil, err
}
// 获取推荐合作伙伴数
featuredCount, err := dao.Partner.GetFeaturedCount(ctx)
if err != nil {
return nil, err
}
// 获取各分类合作伙伴数
categories, err := dao.Partner.GetCategories(ctx)
if err != nil {
return nil, err
}
categoryStats := make(map[string]int)
for _, category := range categories {
count, err := dao.Partner.GetCountByCategory(ctx, category)
if err == nil {
categoryStats[category] = count
}
}
return map[string]interface{}{
"total_count": totalCount,
"active_count": activeCount,
"featured_count": featuredCount,
"category_stats": categoryStats,
}, nil
}
// BatchUpdateStatus 批量更新状态
func (s *sPartner) BatchUpdateStatus(ctx context.Context, ids []int, status int) error {
for _, id := range ids {
if err := s.UpdateStatus(ctx, id, status); err != nil {
g.Log().Error(ctx, "批量更新合作伙伴状态失败, ID:", id, "错误:", err)
// 继续更新其他记录,不中断整个过程
}
}
return nil
}
// BatchDelete 批量删除合作伙伴
func (s *sPartner) BatchDelete(ctx context.Context, ids []int) error {
for _, id := range ids {
if err := s.Delete(ctx, id); err != nil {
g.Log().Error(ctx, "批量删除合作伙伴失败, ID:", id, "错误:", err)
// 继续删除其他记录,不中断整个过程
}
}
return nil
}

227
internal/service/role.go Normal file
View File

@@ -0,0 +1,227 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"encoding/json"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type sRole struct{}
func Role() *sRole {
return &sRole{}
}
// GetById 根据ID获取角色信息
func (s *sRole) GetById(ctx context.Context, id int) (*model.Role, error) {
return dao.Role.GetById(ctx, id)
}
// List 获取角色列表
func (s *sRole) List(ctx context.Context, req *model.RoleListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
roles, total, err := dao.Role.List(ctx, req)
if err != nil {
return nil, err
}
return &model.PageResponse{
List: roles,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// GetAll 获取所有启用的角色
func (s *sRole) GetAll(ctx context.Context) ([]*model.Role, error) {
return dao.Role.GetAll(ctx)
}
// Create 创建角色
func (s *sRole) Create(ctx context.Context, req *model.CreateRoleRequest) error {
// 检查角色名称是否存在
existRole, err := dao.Role.GetByName(ctx, req.Name)
if err != nil {
return err
}
if existRole != nil {
return gerror.New("角色名称已存在")
}
// 序列化权限列表
permissionsJson, err := json.Marshal(req.Permissions)
if err != nil {
return gerror.New("权限数据格式错误")
}
// 创建角色
role := &model.Role{
Name: req.Name,
Description: req.Description,
Permissions: string(permissionsJson),
Status: req.Status,
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
_, err = dao.Role.Create(ctx, role)
return err
}
// Update 更新角色
func (s *sRole) Update(ctx context.Context, id int, req *model.UpdateRoleRequest) error {
// 检查角色是否存在
role, err := s.GetById(ctx, id)
if err != nil {
return err
}
if role == nil {
return gerror.New("角色不存在")
}
// 检查角色名称是否被其他角色使用
existRole, err := dao.Role.GetByName(ctx, req.Name)
if err != nil {
return err
}
if existRole != nil && existRole.Id != id {
return gerror.New("角色名称已被使用")
}
// 序列化权限列表
permissionsJson, err := json.Marshal(req.Permissions)
if err != nil {
return gerror.New("权限数据格式错误")
}
// 更新角色
updateData := g.Map{
"name": req.Name,
"description": req.Description,
"permissions": string(permissionsJson),
"status": req.Status,
"updated_at": gtime.Now(),
}
return dao.Role.Update(ctx, id, updateData)
}
// Delete 删除角色
func (s *sRole) Delete(ctx context.Context, id int) error {
// 检查角色是否存在
role, err := s.GetById(ctx, id)
if err != nil {
return err
}
if role == nil {
return gerror.New("角色不存在")
}
// TODO: 检查是否有用户使用该角色
// 这里可以添加检查逻辑,防止删除正在使用的角色
return dao.Role.Delete(ctx, id)
}
// UpdateStatus 更新角色状态
func (s *sRole) UpdateStatus(ctx context.Context, id int, status int) error {
// 检查角色是否存在
role, err := s.GetById(ctx, id)
if err != nil {
return err
}
if role == nil {
return gerror.New("角色不存在")
}
return dao.Role.UpdateStatus(ctx, id, status)
}
// GetPermissions 获取所有可用权限
func (s *sRole) GetPermissions(ctx context.Context) ([]model.PermissionGroup, error) {
// 定义系统权限
permissions := []model.PermissionGroup{
{
Name: "用户管理",
Description: "用户相关权限",
Permissions: []model.Permission{
{Key: "user.list", Name: "查看用户列表", Description: "查看用户列表权限", Group: "用户管理"},
{Key: "user.create", Name: "创建用户", Description: "创建用户权限", Group: "用户管理"},
{Key: "user.update", Name: "编辑用户", Description: "编辑用户权限", Group: "用户管理"},
{Key: "user.delete", Name: "删除用户", Description: "删除用户权限", Group: "用户管理"},
{Key: "user.status", Name: "管理用户状态", Description: "管理用户状态权限", Group: "用户管理"},
},
},
{
Name: "角色管理",
Description: "角色权限相关权限",
Permissions: []model.Permission{
{Key: "role.list", Name: "查看角色列表", Description: "查看角色列表权限", Group: "角色管理"},
{Key: "role.create", Name: "创建角色", Description: "创建角色权限", Group: "角色管理"},
{Key: "role.update", Name: "编辑角色", Description: "编辑角色权限", Group: "角色管理"},
{Key: "role.delete", Name: "删除角色", Description: "删除角色权限", Group: "角色管理"},
},
},
{
Name: "内容管理",
Description: "内容相关权限",
Permissions: []model.Permission{
{Key: "article.list", Name: "查看文章列表", Description: "查看文章列表权限", Group: "内容管理"},
{Key: "article.create", Name: "创建文章", Description: "创建文章权限", Group: "内容管理"},
{Key: "article.update", Name: "编辑文章", Description: "编辑文章权限", Group: "内容管理"},
{Key: "article.delete", Name: "删除文章", Description: "删除文章权限", Group: "内容管理"},
{Key: "news.list", Name: "查看新闻列表", Description: "查看新闻列表权限", Group: "内容管理"},
{Key: "news.create", Name: "创建新闻", Description: "创建新闻权限", Group: "内容管理"},
{Key: "news.update", Name: "编辑新闻", Description: "编辑新闻权限", Group: "内容管理"},
{Key: "news.delete", Name: "删除新闻", Description: "删除新闻权限", Group: "内容管理"},
},
},
{
Name: "附件管理",
Description: "附件相关权限",
Permissions: []model.Permission{
{Key: "attachment.list", Name: "查看附件列表", Description: "查看附件列表权限", Group: "附件管理"},
{Key: "attachment.upload", Name: "上传附件", Description: "上传附件权限", Group: "附件管理"},
{Key: "attachment.delete", Name: "删除附件", Description: "删除附件权限", Group: "附件管理"},
},
},
{
Name: "系统管理",
Description: "系统相关权限",
Permissions: []model.Permission{
{Key: "config.list", Name: "查看系统配置", Description: "查看系统配置权限", Group: "系统管理"},
{Key: "config.update", Name: "修改系统配置", Description: "修改系统配置权限", Group: "系统管理"},
},
},
}
return permissions, nil
}
// CheckPermission 检查权限
func (s *sRole) CheckPermission(ctx context.Context, roleId int, permission string) (bool, error) {
return dao.Role.CheckPermission(ctx, roleId, permission)
}

311
internal/service/user.go Normal file
View File

@@ -0,0 +1,311 @@
package service
import (
"context"
"cms-api/internal/dao"
"cms-api/internal/model"
"github.com/gogf/gf/v2/crypto/gmd5"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sUser struct{}
func User() *sUser {
return &sUser{}
}
// Login 用户登录
func (s *sUser) Login(ctx context.Context, req *model.UserLoginRequest) (*model.UserLoginResponse, error) {
// 查询用户
user, err := dao.User.GetByAccount(ctx, req.Account)
if err != nil {
return nil, err
}
if user == nil {
return nil, gerror.New("用户名或密码错误")
}
// 验证密码
if !s.VerifyPassword(req.Password, user.Password) {
return nil, gerror.New("用户名或密码错误")
}
// 检查状态
if user.Status != 0 {
statusText := map[int]string{
1: "账号已被冻结",
2: "账号已被封号",
3: "账号已注销",
}
return nil, gerror.New(statusText[user.Status])
}
// 更新登录信息
err = dao.User.Update(ctx, user.Id, g.Map{
"ip": g.RequestFromCtx(ctx).GetClientIp(),
"updated_at": gtime.Now(),
})
if err != nil {
g.Log().Error(ctx, "更新用户登录信息失败:", err)
}
// 生成token (暂时使用简单的token生成)
token := gmd5.MustEncrypt(gconv.String(user.Id) + "_" + gconv.String(gtime.Now().Unix()))
// 清除密码字段
user.Password = ""
return &model.UserLoginResponse{
Token: token,
ExpiresIn: 7200, // 2小时
User: user,
}, nil
}
// GetById 根据ID获取用户信息
func (s *sUser) GetById(ctx context.Context, id int) (*model.User, error) {
user, err := dao.User.GetById(ctx, id)
if err != nil {
return nil, err
}
if user == nil {
return nil, nil
}
// 清除密码字段
user.Password = ""
return user, nil
}
// List 获取用户列表
func (s *sUser) List(ctx context.Context, req *model.UserListRequest) (*model.PageResponse, error) {
var (
page = req.Page
pageSize = req.PageSize
)
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
// 设置分页参数
req.Page = page
req.PageSize = pageSize
// 获取列表
users, total, err := dao.User.List(ctx, req)
if err != nil {
return nil, err
}
// 清除密码字段
for _, user := range users {
user.Password = ""
}
return &model.PageResponse{
List: users,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: (total + pageSize - 1) / pageSize,
}, nil
}
// Create 创建用户
func (s *sUser) Create(ctx context.Context, req *model.CreateUserRequest) error {
// 检查账号是否存在
existUser, err := dao.User.GetByAccount(ctx, req.Account)
if err != nil {
return err
}
if existUser != nil {
return gerror.New("账号已存在")
}
// 检查邮箱是否存在
existUser, err = dao.User.GetByEmail(ctx, req.Email)
if err != nil {
return err
}
if existUser != nil {
return gerror.New("邮箱已存在")
}
// 加密密码
hashedPassword := s.HashPassword(req.Password)
// 创建用户
user := &model.User{
Account: req.Account,
NickName: req.NickName,
Avatar: req.Avatar,
Email: req.Email,
Password: hashedPassword,
Balance: req.Balance,
RoleId: req.RoleId,
IsSysNotifications: req.IsSysNotifications,
IsCollectionNotifications: req.IsCollectionNotifications,
IsMarketingNotifications: req.IsMarketingNotifications,
Status: 0, // 默认正常状态
CreatedAt: gtime.Now(),
UpdatedAt: gtime.Now(),
}
_, err = dao.User.Create(ctx, user)
return err
}
// Update 更新用户
func (s *sUser) Update(ctx context.Context, id int, req *model.UpdateUserRequest) error {
// 检查用户是否存在
user, err := s.GetById(ctx, id)
if err != nil {
return err
}
if user == nil {
return gerror.New("用户不存在")
}
// 检查邮箱是否被其他用户使用
existUser, err := dao.User.GetByEmail(ctx, req.Email)
if err != nil {
return err
}
if existUser != nil && existUser.Id != id {
return gerror.New("邮箱已被使用")
}
// 更新用户
updateData := g.Map{
"nick_name": req.NickName,
"email": req.Email,
"avatar": req.Avatar,
"balance": req.Balance,
"role_id": req.RoleId,
"status": req.Status,
"is_sys_notifications": req.IsSysNotifications,
"is_collection_notifications": req.IsCollectionNotifications,
"is_marketing_notifications": req.IsMarketingNotifications,
"updated_at": gtime.Now(),
}
return dao.User.Update(ctx, id, updateData)
}
// Delete 删除用户
func (s *sUser) Delete(ctx context.Context, id int) error {
// 检查用户是否存在
user, err := s.GetById(ctx, id)
if err != nil {
return err
}
if user == nil {
return gerror.New("用户不存在")
}
return dao.User.Delete(ctx, id)
}
// ChangePassword 修改密码
func (s *sUser) ChangePassword(ctx context.Context, id int, req *model.ChangePasswordRequest) error {
// 获取用户信息
user, err := dao.User.GetById(ctx, id)
if err != nil {
return err
}
if user == nil {
return gerror.New("用户不存在")
}
// 验证原密码
if !s.VerifyPassword(req.OldPassword, user.Password) {
return gerror.New("原密码错误")
}
// 加密新密码
hashedPassword := s.HashPassword(req.NewPassword)
// 更新密码
updateData := g.Map{
"password": hashedPassword,
"last_reset_password_at": gtime.Now().Unix(),
"updated_at": gtime.Now(),
}
return dao.User.Update(ctx, id, updateData)
}
// UpdateStatus 更新用户状态
func (s *sUser) UpdateStatus(ctx context.Context, id int, status int) error {
// 检查用户是否存在
user, err := s.GetById(ctx, id)
if err != nil {
return err
}
if user == nil {
return gerror.New("用户不存在")
}
return dao.User.UpdateStatus(ctx, id, status)
}
// HashPassword 加密密码
func (s *sUser) HashPassword(password string) string {
return gmd5.MustEncrypt(password + "cms_user_salt_2024")
}
// VerifyPassword 验证密码
func (s *sUser) VerifyPassword(password, hashedPassword string) bool {
return gmd5.MustEncrypt(password+"cms_user_salt_2024") == hashedPassword
}
// Profile 获取当前用户信息
func (s *sUser) Profile(ctx context.Context) (*model.User, error) {
userId := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("user_id"))
return s.GetById(ctx, userId)
}
// UpdateProfile 更新当前用户信息
func (s *sUser) UpdateProfile(ctx context.Context, req *model.UpdateUserRequest) error {
userId := gconv.Int(g.RequestFromCtx(ctx).GetCtxVar("user_id"))
return s.Update(ctx, userId, req)
}
// GetStats 获取用户统计信息
func (s *sUser) GetStats(ctx context.Context) (map[string]interface{}, error) {
// 获取总用户数
totalCount, err := dao.User.GetCount(ctx)
if err != nil {
return nil, err
}
// 获取各状态用户数
normalCount, err := dao.User.GetCountByStatus(ctx, 0)
if err != nil {
return nil, err
}
frozenCount, err := dao.User.GetCountByStatus(ctx, 1)
if err != nil {
return nil, err
}
bannedCount, err := dao.User.GetCountByStatus(ctx, 2)
if err != nil {
return nil, err
}
return map[string]interface{}{
"total_count": totalCount,
"normal_count": normalCount,
"frozen_count": frozenCount,
"banned_count": bannedCount,
}, nil
}