第一版完成

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

@@ -0,0 +1 @@
package consts

View File

@@ -0,0 +1,70 @@
package controller
import (
"cms-api/internal/model"
"cms-api/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
)
// 管理员控制器
type cAdmin struct{}
var Admin = &cAdmin{}
// Login 管理员登录
func (c *cAdmin) Login(r *ghttp.Request) {
var req *model.LoginRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用管理员服务进行登录验证
adminService := service.Admin()
result, err := adminService.Login(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "登录失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "登录成功", Data: result})
}
// Logout 管理员登出
func (c *cAdmin) Logout(r *ghttp.Request) {
// 简单的登出处理清除token等
r.Response.WriteJson(&model.Response{Code: 200, Message: "登出成功"})
}
// Detail 获取管理员详情
func (c *cAdmin) Detail(r *ghttp.Request) {
// 调用管理员服务获取当前管理员信息
adminService := service.Admin()
result, err := adminService.Profile(r.Context())
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取管理员信息失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Update 更新管理员信息
func (c *cAdmin) Update(r *ghttp.Request) {
var req *model.UpdateAdminRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用管理员服务更新信息
adminService := service.Admin()
err := adminService.UpdateProfile(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "更新管理员信息失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "更新成功"})
}

View File

@@ -0,0 +1,173 @@
package controller
import (
"cms-api/internal/model"
"cms-api/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// 文章控制器
type cArticle struct{}
var Article = &cArticle{}
// List 获取文章列表(公开接口)
func (c *cArticle) List(r *ghttp.Request) {
var req *model.ArticleListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用文章服务获取列表
articleService := service.Article()
result, err := articleService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取文章列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// AdminList 获取文章列表(管理后台)
func (c *cArticle) AdminList(r *ghttp.Request) {
var req *model.ArticleListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用文章服务获取管理后台列表
articleService := service.Article()
result, err := articleService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取文章列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Create 创建文章
func (c *cArticle) Create(r *ghttp.Request) {
var req *model.CreateArticleRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用文章服务创建文章
articleService := service.Article()
err := articleService.Create(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "创建文章失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "创建文章成功"})
}
// Detail 获取文章详情(公开接口)
func (c *cArticle) Detail(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
slug := gconv.String(r.Get("slug"))
if id <= 0 && slug == "" {
r.Response.WriteJson(&model.Response{Code: 400, Message: "文章ID或别名无效"})
return
}
// 调用文章服务获取详情
articleService := service.Article()
var result *model.Article
var err error
if id > 0 {
result, err = articleService.GetById(r.Context(), id)
} else {
result, err = articleService.GetBySlug(r.Context(), slug)
}
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取文章详情失败", Data: err.Error()})
return
}
if result == nil {
r.Response.WriteJson(&model.Response{Code: 404, Message: "文章不存在"})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// AdminDetail 获取文章详情(管理后台)
func (c *cArticle) AdminDetail(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "文章ID无效"})
return
}
// 调用文章服务获取管理后台详情
articleService := service.Article()
result, err := articleService.GetById(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取文章详情失败", Data: err.Error()})
return
}
if result == nil {
r.Response.WriteJson(&model.Response{Code: 404, Message: "文章不存在"})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Update 更新文章
func (c *cArticle) Update(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "文章ID无效"})
return
}
var req *model.UpdateArticleRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用文章服务更新文章
articleService := service.Article()
err := articleService.Update(r.Context(), id, req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "更新文章失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "更新文章成功"})
}
// Delete 删除文章
func (c *cArticle) Delete(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "文章ID无效"})
return
}
// 调用文章服务删除文章
articleService := service.Article()
err := articleService.Delete(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "删除文章失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "删除文章成功"})
}

View File

@@ -0,0 +1,96 @@
package controller
import (
"cms-api/internal/model"
"cms-api/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// 附件控制器
type cAttachment struct{}
var Attachment = &cAttachment{}
// List 获取附件列表
func (c *cAttachment) List(r *ghttp.Request) {
var req *model.AttachmentListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用附件服务获取列表
attachmentService := service.Attachment()
result, err := attachmentService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取附件列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Upload 上传附件
func (c *cAttachment) Upload(r *ghttp.Request) {
// 获取上传的文件
file := r.GetUploadFile("file")
if file == nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "请选择要上传的文件"})
return
}
// 调用附件服务处理上传
attachmentService := service.Attachment()
result, err := attachmentService.Upload(r.Context(), file, "uploads")
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "文件上传失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "上传成功", Data: result})
}
// Delete 删除附件
func (c *cAttachment) Delete(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "附件ID无效"})
return
}
// 调用附件服务删除附件
attachmentService := service.Attachment()
err := attachmentService.Delete(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "删除附件失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "删除附件成功"})
}
// Detail 获取附件详情
func (c *cAttachment) Detail(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "附件ID无效"})
return
}
// 调用附件服务获取详情
attachmentService := service.Attachment()
result, err := attachmentService.GetById(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取附件详情失败", Data: err.Error()})
return
}
if result == nil {
r.Response.WriteJson(&model.Response{Code: 404, Message: "附件不存在"})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}

View File

@@ -0,0 +1,71 @@
package controller
import (
"cms-api/internal/model"
"cms-api/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// 网站配置控制器
type cConfig struct{}
var Config = &cConfig{}
// List 获取配置列表
func (c *cConfig) List(r *ghttp.Request) {
var req *model.ConfigListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用配置服务获取列表
configService := service.Config()
result, err := configService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取配置列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Update 更新配置
func (c *cConfig) Update(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "配置ID无效"})
return
}
var req *model.UpdateConfigRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用配置服务更新配置
configService := service.Config()
err := configService.Update(r.Context(), gconv.String(id), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "更新配置失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "更新配置成功"})
}
// Public 获取公开配置
func (c *cConfig) Public(r *ghttp.Request) {
// 调用配置服务获取公开配置
configService := service.Config()
result, err := configService.GetPublicConfigs(r.Context())
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取公开配置失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}

159
internal/controller/news.go Normal file
View File

@@ -0,0 +1,159 @@
package controller
import (
"cms-api/internal/model"
"cms-api/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// 新闻控制器
type cNews struct{}
var News = &cNews{}
// List 获取新闻列表(公开接口)
func (c *cNews) List(r *ghttp.Request) {
var req *model.NewsListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用新闻服务获取列表
newsService := service.News()
result, err := newsService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取新闻列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// AdminList 获取新闻列表(管理后台)
func (c *cNews) AdminList(r *ghttp.Request) {
var req *model.NewsListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用新闻服务获取列表(暂时使用相同方法)
newsService := service.News()
result, err := newsService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取新闻列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Create 创建新闻
func (c *cNews) Create(r *ghttp.Request) {
r.Response.WriteJson(&model.Response{Code: 200, Message: "功能开发中"})
}
// Detail 获取新闻详情(公开接口)
func (c *cNews) Detail(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
slug := gconv.String(r.Get("slug"))
if id <= 0 && slug == "" {
r.Response.WriteJson(&model.Response{Code: 400, Message: "新闻ID或别名无效"})
return
}
// 调用新闻服务获取详情
newsService := service.News()
var result *model.News
var err error
if id > 0 {
result, err = newsService.GetById(r.Context(), id)
} else {
result, err = newsService.GetBySlug(r.Context(), slug)
}
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取新闻详情失败", Data: err.Error()})
return
}
if result == nil {
r.Response.WriteJson(&model.Response{Code: 404, Message: "新闻不存在"})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// AdminDetail 获取新闻详情(管理后台)
func (c *cNews) AdminDetail(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "新闻ID无效"})
return
}
// 调用新闻服务获取管理后台详情
newsService := service.News()
result, err := newsService.GetById(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取新闻详情失败", Data: err.Error()})
return
}
if result == nil {
r.Response.WriteJson(&model.Response{Code: 404, Message: "新闻不存在"})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Update 更新新闻
func (c *cNews) Update(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "新闻ID无效"})
return
}
var req *model.UpdateNewsRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用新闻服务更新新闻
newsService := service.News()
err := newsService.Update(r.Context(), id, req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "更新新闻失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "更新新闻成功"})
}
// Delete 删除新闻
func (c *cNews) Delete(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "新闻ID无效"})
return
}
// 调用新闻服务删除新闻
newsService := service.News()
err := newsService.Delete(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "删除新闻失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "删除新闻成功"})
}

View File

@@ -0,0 +1,106 @@
package controller
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// RegisterRoutes 注册所有路由
func RegisterRoutes(s *ghttp.Server) {
// API v1 路由组
v1 := s.Group("/api/v1")
// 公开API路由 - 不需要认证
public := v1.Group("/public")
{
// 网站配置
public.GET("/configs", Config.Public)
// 文章相关
articles := public.Group("/articles")
{
articles.GET("/", Article.List)
articles.GET("/{id}", Article.Detail)
}
// 新闻相关
news := public.Group("/news")
{
news.GET("/", News.List)
news.GET("/{id}", News.Detail)
}
}
// 用户API路由
user := v1.Group("/user")
{
user.POST("/login", User.Login)
user.POST("/register", User.Create)
user.GET("/profile", User.Detail)
user.PUT("/profile", User.Update)
}
// 管理员API路由
admin := v1.Group("/admin")
{
// 管理员认证
admin.POST("/login", Admin.Login)
admin.POST("/logout", Admin.Logout)
admin.GET("/profile", Admin.Detail)
admin.PUT("/profile", Admin.Update)
// 用户管理
users := admin.Group("/users")
{
users.GET("/", User.List)
users.POST("/", User.Create)
users.GET("/{id}", User.Detail)
users.PUT("/{id}", User.Update)
users.DELETE("/{id}", User.Delete)
}
// 文章管理
articles := admin.Group("/articles")
{
articles.GET("/", Article.List)
articles.POST("/", Article.Create)
articles.GET("/{id}", Article.Detail)
articles.PUT("/{id}", Article.Update)
articles.DELETE("/{id}", Article.Delete)
}
// 新闻管理
news := admin.Group("/news")
{
news.GET("/", News.List)
news.POST("/", News.Create)
news.GET("/{id}", News.Detail)
news.PUT("/{id}", News.Update)
news.DELETE("/{id}", News.Delete)
}
// 附件管理
attachments := admin.Group("/attachments")
{
attachments.GET("/", Attachment.List)
attachments.POST("/upload", Attachment.Upload)
attachments.GET("/{id}", Attachment.Detail)
attachments.DELETE("/{id}", Attachment.Delete)
}
// 系统配置管理
configs := admin.Group("/configs")
{
configs.GET("/", Config.List)
configs.PUT("/{id}", Config.Update)
}
}
// 健康检查
s.BindHandler("/health", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{
"status": "ok",
"message": "CMS API服务运行正常",
})
})
}

139
internal/controller/user.go Normal file
View File

@@ -0,0 +1,139 @@
package controller
import (
"cms-api/internal/model"
"cms-api/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// 用户控制器
type cUser struct{}
var User = &cUser{}
// List 获取用户列表
func (c *cUser) List(r *ghttp.Request) {
var req *model.UserListRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用用户服务获取列表
userService := service.User()
result, err := userService.List(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取用户列表失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Create 创建用户
func (c *cUser) Create(r *ghttp.Request) {
var req *model.CreateUserRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用用户服务创建用户
userService := service.User()
err := userService.Create(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "创建用户失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "创建用户成功"})
}
// Detail 获取用户详情
func (c *cUser) Detail(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "用户ID无效"})
return
}
// 调用用户服务获取详情
userService := service.User()
result, err := userService.GetById(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "获取用户详情失败", Data: err.Error()})
return
}
if result == nil {
r.Response.WriteJson(&model.Response{Code: 404, Message: "用户不存在"})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "获取成功", Data: result})
}
// Update 更新用户
func (c *cUser) Update(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "用户ID无效"})
return
}
var req *model.UpdateUserRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用用户服务更新用户
userService := service.User()
err := userService.Update(r.Context(), id, req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "更新用户失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "更新用户成功"})
}
// Delete 删除用户
func (c *cUser) Delete(r *ghttp.Request) {
id := gconv.Int(r.Get("id"))
if id <= 0 {
r.Response.WriteJson(&model.Response{Code: 400, Message: "用户ID无效"})
return
}
// 调用用户服务删除用户
userService := service.User()
err := userService.Delete(r.Context(), id)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "删除用户失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "删除用户成功"})
}
// Login 用户登录
func (c *cUser) Login(r *ghttp.Request) {
var req *model.UserLoginRequest
if err := r.Parse(&req); err != nil {
r.Response.WriteJson(&model.Response{Code: 400, Message: "参数解析失败", Data: err.Error()})
return
}
// 调用用户服务进行登录验证
userService := service.User()
result, err := userService.Login(r.Context(), req)
if err != nil {
r.Response.WriteJson(&model.Response{Code: 500, Message: "登录失败", Data: err.Error()})
return
}
r.Response.WriteJson(&model.Response{Code: 200, Message: "登录成功", Data: result})
}

0
internal/dao/.gitkeep Normal file
View File

95
internal/dao/admin.go Normal file
View File

@@ -0,0 +1,95 @@
package dao
import (
"context"
"cms-api/internal/model/entity"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// AdminDao 管理员数据访问对象
type AdminDao struct {
table string
group string
columns AdminColumns
}
// AdminColumns 管理员表字段
type AdminColumns struct {
Id string
Username string
Password string
RealName string
Email string
Phone string
Avatar string
RoleId string
Status string
LastLoginAt string
LastLoginIp string
CreatedAt string
UpdatedAt string
DeletedAt string
}
// NewAdminDao 创建管理员DAO
func NewAdminDao() *AdminDao {
return &AdminDao{
group: "default",
table: "cms_admin",
columns: AdminColumns{
Id: "id",
Username: "username",
Password: "password",
RealName: "real_name",
Email: "email",
Phone: "phone",
Avatar: "avatar",
RoleId: "role_id",
Status: "status",
LastLoginAt: "last_login_at",
LastLoginIp: "last_login_ip",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
// Admin 管理员DAO实例
var Admin = NewAdminDao()
// DB 获取数据库连接
func (dao *AdminDao) DB() gdb.DB {
return g.DB(dao.group)
}
// Ctx 创建上下文查询
func (dao *AdminDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// GetByUsername 根据用户名获取管理员
func (dao *AdminDao) GetByUsername(ctx context.Context, username string) (*entity.Admin, error) {
var admin *entity.Admin
err := dao.Ctx(ctx).Where(dao.columns.Username, username).Where(dao.columns.DeletedAt, 0).Scan(&admin)
return admin, err
}
// GetById 根据ID获取管理员
func (dao *AdminDao) GetById(ctx context.Context, id uint64) (*entity.Admin, error) {
var admin *entity.Admin
err := dao.Ctx(ctx).Where(dao.columns.Id, id).Where(dao.columns.DeletedAt, 0).Scan(&admin)
return admin, err
}
// UpdateLastLogin 更新最后登录信息
func (dao *AdminDao) UpdateLastLogin(ctx context.Context, id uint64, ip string) error {
_, err := dao.Ctx(ctx).Data(g.Map{
dao.columns.LastLoginAt: gtime.Now(),
dao.columns.LastLoginIp: ip,
dao.columns.UpdatedAt: gtime.Now(),
}).Where(dao.columns.Id, id).Where(dao.columns.DeletedAt, 0).Update()
return err
}

View File

@@ -0,0 +1,276 @@
package dao
import (
"context"
"cms-api/internal/model"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// GetById 根据ID获取文章
func (dao *ArticleDao) GetById(ctx context.Context, id int) (*model.Article, error) {
var article *model.Article
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&article)
if err != nil {
return nil, err
}
return article, nil
}
// GetBySlug 根据URL别名获取文章
func (dao *ArticleDao) GetBySlug(ctx context.Context, slug string) (*model.Article, error) {
var article *model.Article
err := dao.Ctx(ctx).Where(dao.Columns().Slug, slug).Scan(&article)
if err != nil {
return nil, err
}
return article, nil
}
// IncrementViewCount 增加浏览次数
func (dao *ArticleDao) IncrementViewCount(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Increment(dao.Columns().ViewCount, 1)
return err
}
// List 获取文章列表
func (dao *ArticleDao) List(ctx context.Context, req *model.ArticleListRequest) ([]*model.Article, int, error) {
var (
articles []*model.Article
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.CategoryId > 0 {
db = db.Where(dao.Columns().CategoryId, req.CategoryId)
}
if req.AuthorId > 0 {
db = db.Where(dao.Columns().AuthorId, req.AuthorId)
}
if req.IsPublished >= 0 {
db = db.Where(dao.Columns().IsPublished, req.IsPublished)
}
if req.IsFeatured >= 0 {
db = db.Where(dao.Columns().IsFeatured, req.IsFeatured)
}
if req.IsTop >= 0 {
db = db.Where(dao.Columns().IsTop, req.IsTop)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().Title+" LIKE ? OR "+dao.Columns().Content+" LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().IsTop + " DESC, " + dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&articles)
if err != nil {
return nil, 0, err
}
return articles, total, nil
}
// Create 创建文章
func (dao *ArticleDao) Create(ctx context.Context, article *model.Article) (int64, error) {
result, err := dao.Ctx(ctx).Data(article).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新文章
func (dao *ArticleDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// Delete 删除文章
func (dao *ArticleDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// GetCount 获取文章总数
func (dao *ArticleDao) GetCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Count()
return count, err
}
// GetPublishedCount 获取已发布文章数
func (dao *ArticleDao) GetPublishedCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().IsPublished, 1).Count()
return count, err
}
// GetFeaturedCount 获取推荐文章数
func (dao *ArticleDao) GetFeaturedCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().IsFeatured, 1).Count()
return count, err
}
// GetFeatured 获取推荐文章
func (dao *ArticleDao) GetFeatured(ctx context.Context, limit int) ([]*model.Article, error) {
var articles []*model.Article
err := dao.Ctx(ctx).
Where(dao.Columns().IsPublished, 1).
Where(dao.Columns().IsFeatured, 1).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&articles)
return articles, err
}
// GetLatest 获取最新文章
func (dao *ArticleDao) GetLatest(ctx context.Context, limit int) ([]*model.Article, error) {
var articles []*model.Article
err := dao.Ctx(ctx).
Where(dao.Columns().IsPublished, 1).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&articles)
return articles, err
}
// Search 搜索文章
func (dao *ArticleDao) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Article, int, error) {
var (
articles []*model.Article
total int
db = dao.Ctx(ctx)
)
// 添加搜索条件
if keyword != "" {
db = db.Where(dao.Columns().IsPublished, 1).
Where(dao.Columns().Title+" LIKE ? OR "+dao.Columns().Content+" LIKE ? OR "+dao.Columns().Summary+" LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&articles)
if err != nil {
return nil, 0, err
}
return articles, total, nil
}
// GetByCategory 根据分类获取文章
func (dao *ArticleDao) GetByCategory(ctx context.Context, categoryId int, page, pageSize int) ([]*model.Article, int, error) {
var (
articles []*model.Article
total int
db = dao.Ctx(ctx)
)
// 添加分类条件
db = db.Where(dao.Columns().CategoryId, categoryId).Where(dao.Columns().IsPublished, 1)
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().IsTop + " DESC, " + dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&articles)
if err != nil {
return nil, 0, err
}
return articles, total, nil
}
// GetByAuthor 根据作者获取文章
func (dao *ArticleDao) GetByAuthor(ctx context.Context, authorId int, page, pageSize int) ([]*model.Article, int, error) {
var (
articles []*model.Article
total int
db = dao.Ctx(ctx)
)
// 添加作者条件
db = db.Where(dao.Columns().AuthorId, authorId).Where(dao.Columns().IsPublished, 1)
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&articles)
if err != nil {
return nil, 0, err
}
return articles, total, nil
}
// GetTopArticles 获取置顶文章
func (dao *ArticleDao) GetTopArticles(ctx context.Context, limit int) ([]*model.Article, error) {
var articles []*model.Article
err := dao.Ctx(ctx).
Where(dao.Columns().IsPublished, 1).
Where(dao.Columns().IsTop, 1).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&articles)
return articles, err
}
// BatchUpdateStatus 批量更新文章状态
func (dao *ArticleDao) BatchUpdateStatus(ctx context.Context, ids []int, isPublished int) error {
_, err := dao.Ctx(ctx).
Where(dao.Columns().Id+" IN (?)", ids).
Data(g.Map{
dao.Columns().IsPublished: isPublished,
dao.Columns().UpdatedAt: gdb.Raw("NOW()"),
}).
Update()
return err
}
// BatchDelete 批量删除文章
func (dao *ArticleDao) BatchDelete(ctx context.Context, ids []int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id+" IN (?)", ids).Delete()
return err
}

View File

@@ -0,0 +1,165 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// Create 创建附件
func (dao *AttachmentDao) Create(ctx context.Context, attachment *model.Attachment) (int64, error) {
result, err := dao.Ctx(ctx).Data(attachment).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// GetById 根据ID获取附件
func (dao *AttachmentDao) GetById(ctx context.Context, id int) (*model.Attachment, error) {
var attachment *model.Attachment
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&attachment)
if err != nil {
return nil, err
}
return attachment, nil
}
// List 获取附件列表
func (dao *AttachmentDao) List(ctx context.Context, req *model.AttachmentListRequest) ([]*model.Attachment, int, error) {
var (
attachments []*model.Attachment
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.FileType != "" {
db = db.Where(dao.Columns().FileType, req.FileType)
}
if req.UploadedBy > 0 {
db = db.Where(dao.Columns().UploadedBy, req.UploadedBy)
}
if req.StorageType != "" {
db = db.Where(dao.Columns().StorageType, req.StorageType)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().OriginalName+" LIKE ? OR "+dao.Columns().FileName+" LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
if req.StartDate != "" {
db = db.Where(dao.Columns().CreatedAt+" >= ?", req.StartDate)
}
if req.EndDate != "" {
db = db.Where(dao.Columns().CreatedAt+" <= ?", req.EndDate)
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&attachments)
if err != nil {
return nil, 0, err
}
return attachments, total, nil
}
// Delete 删除附件
func (dao *AttachmentDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// IncrementUsageCount 增加使用次数
func (dao *AttachmentDao) IncrementUsageCount(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Increment(dao.Columns().UsageCount, 1)
return err
}
// GetCount 获取附件总数
func (dao *AttachmentDao) GetCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Count()
return count, err
}
// GetTotalSize 获取附件总大小
func (dao *AttachmentDao) GetTotalSize(ctx context.Context) (int64, error) {
var totalSize int64
err := dao.Ctx(ctx).Fields("SUM(" + dao.Columns().FileSize + ") as total_size").Scan(&totalSize)
return totalSize, err
}
// GetCountByType 根据类型获取附件数量
func (dao *AttachmentDao) GetCountByType(ctx context.Context, fileType string) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().FileType, fileType).Count()
return count, err
}
// BatchDelete 批量删除附件
func (dao *AttachmentDao) BatchDelete(ctx context.Context, ids []int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id+" IN (?)", ids).Delete()
return err
}
// GetByFileName 根据文件名获取附件
func (dao *AttachmentDao) GetByFileName(ctx context.Context, fileName string) (*model.Attachment, error) {
var attachment *model.Attachment
err := dao.Ctx(ctx).Where(dao.Columns().FileName, fileName).Scan(&attachment)
if err != nil {
return nil, err
}
return attachment, nil
}
// GetByUploader 根据上传者获取附件列表
func (dao *AttachmentDao) GetByUploader(ctx context.Context, uploaderId int, page, pageSize int) ([]*model.Attachment, int, error) {
var (
attachments []*model.Attachment
total int
db = dao.Ctx(ctx)
)
// 添加上传者条件
db = db.Where(dao.Columns().UploadedBy, uploaderId)
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&attachments)
if err != nil {
return nil, 0, err
}
return attachments, total, nil
}
// GetByIds 根据ID列表获取附件
func (dao *AttachmentDao) GetByIds(ctx context.Context, ids []int) ([]*model.Attachment, error) {
var attachments []*model.Attachment
err := dao.Ctx(ctx).Where(dao.Columns().Id+" IN (?)", ids).Scan(&attachments)
if err != nil {
return nil, err
}
return attachments, nil
}

View File

@@ -0,0 +1,161 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// GetByKey 根据配置键获取配置
func (dao *ConfigDao) GetByKey(ctx context.Context, key string) (*model.Config, error) {
var config *model.Config
err := dao.Ctx(ctx).Where(dao.Columns().ConfigKey, key).Scan(&config)
if err != nil {
return nil, err
}
return config, nil
}
// List 获取配置列表
func (dao *ConfigDao) List(ctx context.Context, req *model.ConfigListRequest) ([]*model.Config, int, error) {
var (
configs []*model.Config
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.GroupName != "" {
db = db.Where(dao.Columns().GroupName, req.GroupName)
}
if req.IsSystem >= 0 {
db = db.Where(dao.Columns().IsSystem, req.IsSystem)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().ConfigKey+" LIKE ? OR "+dao.Columns().Description+" LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().GroupName + " ASC, " + dao.Columns().SortOrder + " ASC").
Limit(req.PageSize).
Offset(offset).
Scan(&configs)
if err != nil {
return nil, 0, err
}
return configs, total, nil
}
// GetByGroup 根据分组获取配置
func (dao *ConfigDao) GetByGroup(ctx context.Context, groupName string) ([]*model.Config, error) {
var configs []*model.Config
err := dao.Ctx(ctx).Where(dao.Columns().GroupName, groupName).Order(dao.Columns().SortOrder + " ASC").Scan(&configs)
if err != nil {
return nil, err
}
return configs, nil
}
// GetGroups 获取所有配置分组
func (dao *ConfigDao) GetGroups(ctx context.Context) ([]string, error) {
var groups []string
err := dao.Ctx(ctx).Fields("DISTINCT " + dao.Columns().GroupName).Scan(&groups)
if err != nil {
return nil, err
}
return groups, nil
}
// Create 创建配置
func (dao *ConfigDao) Create(ctx context.Context, config *model.Config) (int64, error) {
result, err := dao.Ctx(ctx).Data(config).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新配置 - 根据ID更新
func (dao *ConfigDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// UpdateByKey 更新配置 - 根据配置键更新
func (dao *ConfigDao) UpdateByKey(ctx context.Context, key string, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().ConfigKey, key).Data(data).Update()
return err
}
// UpdateValue 更新配置值
func (dao *ConfigDao) UpdateValue(ctx context.Context, key string, value string) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().ConfigKey, key).Data(map[string]interface{}{
dao.Columns().ConfigValue: value,
}).Update()
return err
}
// Delete 删除配置 - 根据ID删除
func (dao *ConfigDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// DeleteByKey 删除配置 - 根据配置键删除
func (dao *ConfigDao) DeleteByKey(ctx context.Context, key string) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().ConfigKey, key).Delete()
return err
}
// GetById 根据ID获取配置
func (dao *ConfigDao) GetById(ctx context.Context, id int) (*model.Config, error) {
var config *model.Config
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&config)
if err != nil {
return nil, err
}
return config, nil
}
// BatchUpdate 批量更新配置
func (dao *ConfigDao) BatchUpdate(ctx context.Context, configs map[string]string) error {
for key, value := range configs {
err := dao.UpdateValue(ctx, key, value)
if err != nil {
return err
}
}
return nil
}
// GetSystemConfigs 获取系统配置
func (dao *ConfigDao) GetSystemConfigs(ctx context.Context) ([]*model.Config, error) {
var configs []*model.Config
err := dao.Ctx(ctx).Where(dao.Columns().IsSystem, 1).Order(dao.Columns().GroupName + " ASC, " + dao.Columns().SortOrder + " ASC").Scan(&configs)
if err != nil {
return nil, err
}
return configs, nil
}
// GetUserConfigs 获取用户配置
func (dao *ConfigDao) GetUserConfigs(ctx context.Context) ([]*model.Config, error) {
var configs []*model.Config
err := dao.Ctx(ctx).Where(dao.Columns().IsSystem, 0).Order(dao.Columns().GroupName + " ASC, " + dao.Columns().SortOrder + " ASC").Scan(&configs)
if err != nil {
return nil, err
}
return configs, nil
}

View File

@@ -0,0 +1,177 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// GetById 根据ID获取联系信息
func (dao *ContactDao) GetById(ctx context.Context, id int) (*model.Contact, error) {
var contact *model.Contact
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&contact)
if err != nil {
return nil, err
}
return contact, nil
}
// List 获取联系信息列表
func (dao *ContactDao) List(ctx context.Context, req *model.ContactListRequest) ([]*model.Contact, int, error) {
var (
contacts []*model.Contact
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.Status >= 0 {
db = db.Where(dao.Columns().Status, req.Status)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().Name+" LIKE ? OR "+dao.Columns().Email+" LIKE ? OR "+dao.Columns().Subject+" LIKE ?",
"%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
if req.StartDate != "" {
db = db.Where(dao.Columns().CreatedAt+" >= ?", req.StartDate)
}
if req.EndDate != "" {
db = db.Where(dao.Columns().CreatedAt+" <= ?", req.EndDate)
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&contacts)
if err != nil {
return nil, 0, err
}
return contacts, total, nil
}
// Create 创建联系信息
func (dao *ContactDao) Create(ctx context.Context, contact *model.Contact) (int64, error) {
result, err := dao.Ctx(ctx).Data(contact).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新联系信息
func (dao *ContactDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// Delete 删除联系信息
func (dao *ContactDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// GetCount 获取联系信息总数
func (dao *ContactDao) GetCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Count()
return count, err
}
// GetCountByStatus 根据状态获取联系信息数
func (dao *ContactDao) GetCountByStatus(ctx context.Context, status int) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().Status, status).Count()
return count, err
}
// GetTodayCount 获取今日新增联系信息数
func (dao *ContactDao) GetTodayCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where("DATE("+dao.Columns().CreatedAt+") = CURDATE()").Count()
return count, err
}
// GetWeekCount 获取本周新增联系信息数
func (dao *ContactDao) GetWeekCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where("YEARWEEK("+dao.Columns().CreatedAt+") = YEARWEEK(NOW())").Count()
return count, err
}
// GetMonthCount 获取本月新增联系信息数
func (dao *ContactDao) GetMonthCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where("YEAR("+dao.Columns().CreatedAt+") = YEAR(NOW()) AND MONTH("+dao.Columns().CreatedAt+") = MONTH(NOW())").Count()
return count, err
}
// GetLatest 获取最新联系信息
func (dao *ContactDao) GetLatest(ctx context.Context, limit int) ([]*model.Contact, error) {
var contacts []*model.Contact
err := dao.Ctx(ctx).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&contacts)
return contacts, err
}
// GetUnprocessed 获取未处理的联系信息
func (dao *ContactDao) GetUnprocessed(ctx context.Context, limit int) ([]*model.Contact, error) {
var contacts []*model.Contact
err := dao.Ctx(ctx).
Where(dao.Columns().Status, 0).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&contacts)
return contacts, err
}
// Search 搜索联系信息
func (dao *ContactDao) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Contact, int, error) {
var (
contacts []*model.Contact
total int
db = dao.Ctx(ctx)
)
// 添加搜索条件
if keyword != "" {
db = db.Where(dao.Columns().Name+" LIKE ? OR "+dao.Columns().Email+" LIKE ? OR "+dao.Columns().Subject+" LIKE ? OR "+dao.Columns().Message+" LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&contacts)
if err != nil {
return nil, 0, err
}
return contacts, total, nil
}
// GetTrends 获取联系信息趋势数据
func (dao *ContactDao) GetTrends(ctx context.Context, days int) ([]map[string]interface{}, error) {
var trends []map[string]interface{}
// 这里应该实现趋势数据查询逻辑
// 暂时返回空数组,实际项目中需要根据具体需求实现
return trends, nil
}

571
internal/dao/dao.go Normal file
View File

@@ -0,0 +1,571 @@
package dao
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// 数据访问对象实例
var (
User = NewUserDao()
Role = NewRoleDao()
Article = NewArticleDao()
News = NewNewsDao()
Attachment = NewAttachmentDao()
Partner = NewPartnerDao()
Contact = NewContactDao()
Config = NewConfigDao()
)
// UserDao 用户数据访问对象
type UserDao struct {
table string
group string
columns UserColumns
}
type UserColumns struct {
Id string
Account string
NickName string
Avatar string
Email string
Password string
Balance string
QrCode string
RoleId string
IsSysNotifications string
IsCollectionNotifications string
IsMarketingNotifications string
Ip string
IpTable string
Status string
LastResetPasswordAt string
CreatedAt string
UpdatedAt string
DeletedAt string
}
func NewUserDao() *UserDao {
return &UserDao{
group: "default",
table: "users",
columns: UserColumns{
Id: "id",
Account: "account",
NickName: "nick_name",
Avatar: "avatar",
Email: "email",
Password: "password",
Balance: "balance",
QrCode: "qr_code",
RoleId: "role_id",
IsSysNotifications: "is_sys_notifications",
IsCollectionNotifications: "is_collection_notifications",
IsMarketingNotifications: "is_marketing_notifications",
Ip: "ip",
IpTable: "ip_table",
Status: "status",
LastResetPasswordAt: "last_reset_password_at",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
func (dao *UserDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *UserDao) Table() string {
return dao.table
}
func (dao *UserDao) Columns() UserColumns {
return dao.columns
}
func (dao *UserDao) Group() string {
return dao.group
}
func (dao *UserDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// RoleDao 角色数据访问对象
type RoleDao struct {
table string
group string
columns RoleColumns
}
type RoleColumns struct {
Id string
Name string
Description string
Permissions string
Status string
CreatedAt string
UpdatedAt string
DeletedAt string
}
func NewRoleDao() *RoleDao {
return &RoleDao{
group: "default",
table: "roles",
columns: RoleColumns{
Id: "id",
Name: "name",
Description: "description",
Permissions: "permissions",
Status: "status",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
func (dao *RoleDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *RoleDao) Table() string {
return dao.table
}
func (dao *RoleDao) Columns() RoleColumns {
return dao.columns
}
func (dao *RoleDao) Group() string {
return dao.group
}
func (dao *RoleDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// ArticleDao 文章数据访问对象
type ArticleDao struct {
table string
group string
columns ArticleColumns
}
type ArticleColumns struct {
Id string
Title string
Slug string
Summary string
Content string
HtmlContent string
CoverImage string
CategoryId string
Tags string
AuthorId string
ViewCount string
LikeCount string
CommentCount string
IsPublished string
IsFeatured string
IsTop string
SeoTitle string
SeoDescription string
SeoKeywords string
PublishedAt string
CreatedAt string
UpdatedAt string
DeletedAt string
}
func NewArticleDao() *ArticleDao {
return &ArticleDao{
group: "default",
table: "articles",
columns: ArticleColumns{
Id: "id",
Title: "title",
Slug: "slug",
Summary: "summary",
Content: "content",
HtmlContent: "html_content",
CoverImage: "cover_image",
CategoryId: "category_id",
Tags: "tags",
AuthorId: "author_id",
ViewCount: "view_count",
LikeCount: "like_count",
CommentCount: "comment_count",
IsPublished: "is_published",
IsFeatured: "is_featured",
IsTop: "is_top",
SeoTitle: "seo_title",
SeoDescription: "seo_description",
SeoKeywords: "seo_keywords",
PublishedAt: "published_at",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
func (dao *ArticleDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *ArticleDao) Table() string {
return dao.table
}
func (dao *ArticleDao) Columns() ArticleColumns {
return dao.columns
}
func (dao *ArticleDao) Group() string {
return dao.group
}
func (dao *ArticleDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// NewsDao 新闻数据访问对象
type NewsDao struct {
table string
group string
columns NewsColumns
}
type NewsColumns struct {
Id string
Title string
Slug string
Summary string
Content string
CoverImage string
Category string
Source string
Author string
ViewCount string
IsPublished string
IsFeatured string
IsTop string
PublishedAt string
CreatedAt string
UpdatedAt string
DeletedAt string
}
func NewNewsDao() *NewsDao {
return &NewsDao{
group: "default",
table: "news",
columns: NewsColumns{
Id: "id",
Title: "title",
Slug: "slug",
Summary: "summary",
Content: "content",
CoverImage: "cover_image",
Category: "category",
Source: "source",
Author: "author",
ViewCount: "view_count",
IsPublished: "is_published",
IsFeatured: "is_featured",
IsTop: "is_top",
PublishedAt: "published_at",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
func (dao *NewsDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *NewsDao) Table() string {
return dao.table
}
func (dao *NewsDao) Columns() NewsColumns {
return dao.columns
}
func (dao *NewsDao) Group() string {
return dao.group
}
func (dao *NewsDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// AttachmentDao 附件数据访问对象
type AttachmentDao struct {
table string
group string
columns AttachmentColumns
}
type AttachmentColumns struct {
Id string
OriginalName string
FileName string
FilePath string
FileUrl string
FileSize string
FileType string
MimeType string
FileExt string
StorageType string
UploadIp string
UploadedBy string
UsageCount string
CreatedAt string
UpdatedAt string
DeletedAt string
}
func NewAttachmentDao() *AttachmentDao {
return &AttachmentDao{
group: "default",
table: "attachments",
columns: AttachmentColumns{
Id: "id",
OriginalName: "original_name",
FileName: "file_name",
FilePath: "file_path",
FileUrl: "file_url",
FileSize: "file_size",
FileType: "file_type",
MimeType: "mime_type",
FileExt: "file_ext",
StorageType: "storage_type",
UploadIp: "upload_ip",
UploadedBy: "uploaded_by",
UsageCount: "usage_count",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
func (dao *AttachmentDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *AttachmentDao) Table() string {
return dao.table
}
func (dao *AttachmentDao) Columns() AttachmentColumns {
return dao.columns
}
func (dao *AttachmentDao) Group() string {
return dao.group
}
func (dao *AttachmentDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// PartnerDao 合作伙伴数据访问对象
type PartnerDao struct {
table string
group string
columns PartnerColumns
}
type PartnerColumns struct {
Id string
Name string
Logo string
Website string
Description string
Category string
SortOrder string
IsFeatured string
Status string
CreatedAt string
UpdatedAt string
DeletedAt string
}
func NewPartnerDao() *PartnerDao {
return &PartnerDao{
group: "default",
table: "partners",
columns: PartnerColumns{
Id: "id",
Name: "name",
Logo: "logo",
Website: "website",
Description: "description",
Category: "category",
SortOrder: "sort_order",
IsFeatured: "is_featured",
Status: "status",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
DeletedAt: "deleted_at",
},
}
}
func (dao *PartnerDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *PartnerDao) Table() string {
return dao.table
}
func (dao *PartnerDao) Columns() PartnerColumns {
return dao.columns
}
func (dao *PartnerDao) Group() string {
return dao.group
}
func (dao *PartnerDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// ContactDao 联系我们数据访问对象
type ContactDao struct {
table string
group string
columns ContactColumns
}
type ContactColumns struct {
Id string
Name string
Email string
Phone string
Company string
Subject string
Message string
Ip string
UserAgent string
Status string
Reply string
RepliedAt string
RepliedBy string
CreatedAt string
UpdatedAt string
}
func NewContactDao() *ContactDao {
return &ContactDao{
group: "default",
table: "contacts",
columns: ContactColumns{
Id: "id",
Name: "name",
Email: "email",
Phone: "phone",
Company: "company",
Subject: "subject",
Message: "message",
Ip: "ip",
UserAgent: "user_agent",
Status: "status",
Reply: "reply",
RepliedAt: "replied_at",
RepliedBy: "replied_by",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
}
}
func (dao *ContactDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *ContactDao) Table() string {
return dao.table
}
func (dao *ContactDao) Columns() ContactColumns {
return dao.columns
}
func (dao *ContactDao) Group() string {
return dao.group
}
func (dao *ContactDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}
// ConfigDao 配置数据访问对象
type ConfigDao struct {
table string
group string
columns ConfigColumns
}
type ConfigColumns struct {
Id string
ConfigKey string
ConfigValue string
ConfigType string
GroupName string
Description string
SortOrder string
IsSystem string
CreatedAt string
UpdatedAt string
}
func NewConfigDao() *ConfigDao {
return &ConfigDao{
group: "default",
table: "site_configs",
columns: ConfigColumns{
Id: "id",
ConfigKey: "config_key",
ConfigValue: "config_value",
ConfigType: "config_type",
GroupName: "group_name",
Description: "description",
SortOrder: "sort_order",
IsSystem: "is_system",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
}
}
func (dao *ConfigDao) DB() gdb.DB {
return g.DB(dao.group)
}
func (dao *ConfigDao) Table() string {
return dao.table
}
func (dao *ConfigDao) Columns() ConfigColumns {
return dao.columns
}
func (dao *ConfigDao) Group() string {
return dao.group
}
func (dao *ConfigDao) Ctx(ctx context.Context) *gdb.Model {
return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}

View File

@@ -0,0 +1,227 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// GetById 根据ID获取新闻
func (dao *NewsDao) GetById(ctx context.Context, id int) (*model.News, error) {
var news *model.News
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&news)
if err != nil {
return nil, err
}
return news, nil
}
// GetBySlug 根据URL别名获取新闻
func (dao *NewsDao) GetBySlug(ctx context.Context, slug string) (*model.News, error) {
var news *model.News
err := dao.Ctx(ctx).Where(dao.Columns().Slug, slug).Scan(&news)
if err != nil {
return nil, err
}
return news, nil
}
// IncrementViewCount 增加浏览次数
func (dao *NewsDao) IncrementViewCount(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Increment(dao.Columns().ViewCount, 1)
return err
}
// List 获取新闻列表
func (dao *NewsDao) List(ctx context.Context, req *model.NewsListRequest) ([]*model.News, int, error) {
var (
newsList []*model.News
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.Category != "" {
db = db.Where(dao.Columns().Category, req.Category)
}
if req.Author != "" {
db = db.Where(dao.Columns().Author, req.Author)
}
if req.Source != "" {
db = db.Where(dao.Columns().Source, req.Source)
}
if req.IsPublished >= 0 {
db = db.Where(dao.Columns().IsPublished, req.IsPublished)
}
if req.IsFeatured >= 0 {
db = db.Where(dao.Columns().IsFeatured, req.IsFeatured)
}
if req.IsTop >= 0 {
db = db.Where(dao.Columns().IsTop, req.IsTop)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().Title+" LIKE ? OR "+dao.Columns().Content+" LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
if req.StartDate != "" {
db = db.Where(dao.Columns().CreatedAt+" >= ?", req.StartDate)
}
if req.EndDate != "" {
db = db.Where(dao.Columns().CreatedAt+" <= ?", req.EndDate)
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().IsTop + " DESC, " + dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&newsList)
if err != nil {
return nil, 0, err
}
return newsList, total, nil
}
// Create 创建新闻
func (dao *NewsDao) Create(ctx context.Context, news *model.News) (int64, error) {
result, err := dao.Ctx(ctx).Data(news).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新新闻
func (dao *NewsDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// Delete 删除新闻
func (dao *NewsDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// GetCount 获取新闻总数
func (dao *NewsDao) GetCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Count()
return count, err
}
// GetPublishedCount 获取已发布新闻数
func (dao *NewsDao) GetPublishedCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().IsPublished, 1).Count()
return count, err
}
// GetFeaturedCount 获取推荐新闻数
func (dao *NewsDao) GetFeaturedCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().IsFeatured, 1).Count()
return count, err
}
// GetCountByCategory 根据分类获取新闻数
func (dao *NewsDao) GetCountByCategory(ctx context.Context, category string) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().Category, category).Count()
return count, err
}
// GetFeatured 获取推荐新闻
func (dao *NewsDao) GetFeatured(ctx context.Context, limit int) ([]*model.News, error) {
var newsList []*model.News
err := dao.Ctx(ctx).
Where(dao.Columns().IsPublished, 1).
Where(dao.Columns().IsFeatured, 1).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&newsList)
return newsList, err
}
// GetLatest 获取最新新闻
func (dao *NewsDao) GetLatest(ctx context.Context, limit int) ([]*model.News, error) {
var newsList []*model.News
err := dao.Ctx(ctx).
Where(dao.Columns().IsPublished, 1).
Order(dao.Columns().CreatedAt + " DESC").
Limit(limit).
Scan(&newsList)
return newsList, err
}
// GetByCategory 根据分类获取新闻
func (dao *NewsDao) GetByCategory(ctx context.Context, category string, page, pageSize int) ([]*model.News, int, error) {
var (
newsList []*model.News
total int
db = dao.Ctx(ctx)
)
// 添加分类条件
db = db.Where(dao.Columns().Category, category).Where(dao.Columns().IsPublished, 1)
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().IsTop + " DESC, " + dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&newsList)
if err != nil {
return nil, 0, err
}
return newsList, total, nil
}
// Search 搜索新闻
func (dao *NewsDao) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.News, int, error) {
var (
newsList []*model.News
total int
db = dao.Ctx(ctx)
)
// 添加搜索条件
if keyword != "" {
db = db.Where(dao.Columns().IsPublished, 1).
Where(dao.Columns().Title+" LIKE ? OR "+dao.Columns().Content+" LIKE ? OR "+dao.Columns().Summary+" LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (page - 1) * pageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(pageSize).
Offset(offset).
Scan(&newsList)
if err != nil {
return nil, 0, err
}
return newsList, total, nil
}

View File

@@ -0,0 +1,168 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// GetById 根据ID获取合作伙伴
func (dao *PartnerDao) GetById(ctx context.Context, id int) (*model.Partner, error) {
var partner *model.Partner
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&partner)
if err != nil {
return nil, err
}
return partner, nil
}
// List 获取合作伙伴列表
func (dao *PartnerDao) List(ctx context.Context, req *model.PartnerListRequest) ([]*model.Partner, int, error) {
var (
partners []*model.Partner
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.Category != "" {
db = db.Where(dao.Columns().Category, req.Category)
}
if req.IsFeatured >= 0 {
db = db.Where(dao.Columns().IsFeatured, req.IsFeatured)
}
if req.Status >= 0 {
db = db.Where(dao.Columns().Status, req.Status)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().Name+" LIKE ? OR "+dao.Columns().Description+" LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().SortOrder + " ASC, " + dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&partners)
if err != nil {
return nil, 0, err
}
return partners, total, nil
}
// GetAll 获取所有合作伙伴
func (dao *PartnerDao) GetAll(ctx context.Context) ([]*model.Partner, error) {
var partners []*model.Partner
err := dao.Ctx(ctx).Where(dao.Columns().Status, 1).Order(dao.Columns().SortOrder + " ASC").Scan(&partners)
if err != nil {
return nil, err
}
return partners, nil
}
// GetFeatured 获取推荐合作伙伴
func (dao *PartnerDao) GetFeatured(ctx context.Context, limit int) ([]*model.Partner, error) {
var partners []*model.Partner
err := dao.Ctx(ctx).
Where(dao.Columns().Status, 1).
Where(dao.Columns().IsFeatured, 1).
Order(dao.Columns().SortOrder + " ASC").
Limit(limit).
Scan(&partners)
return partners, err
}
// GetByCategory 根据分类获取合作伙伴
func (dao *PartnerDao) GetByCategory(ctx context.Context, category string) ([]*model.Partner, error) {
var partners []*model.Partner
err := dao.Ctx(ctx).
Where(dao.Columns().Category, category).
Where(dao.Columns().Status, 1).
Order(dao.Columns().SortOrder + " ASC").
Scan(&partners)
return partners, err
}
// GetByName 根据名称获取合作伙伴
func (dao *PartnerDao) GetByName(ctx context.Context, name string) (*model.Partner, error) {
var partner *model.Partner
err := dao.Ctx(ctx).Where(dao.Columns().Name, name).Scan(&partner)
if err != nil {
return nil, err
}
return partner, nil
}
// Create 创建合作伙伴
func (dao *PartnerDao) Create(ctx context.Context, partner *model.Partner) (int64, error) {
result, err := dao.Ctx(ctx).Data(partner).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新合作伙伴
func (dao *PartnerDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// Delete 删除合作伙伴
func (dao *PartnerDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// UpdateStatus 更新合作伙伴状态
func (dao *PartnerDao) UpdateStatus(ctx context.Context, id int, status int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(map[string]interface{}{
dao.Columns().Status: status,
}).Update()
return err
}
// GetCount 获取合作伙伴总数
func (dao *PartnerDao) GetCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Count()
return count, err
}
// GetCountByStatus 根据状态获取合作伙伴数
func (dao *PartnerDao) GetCountByStatus(ctx context.Context, status int) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().Status, status).Count()
return count, err
}
// GetFeaturedCount 获取推荐合作伙伴数
func (dao *PartnerDao) GetFeaturedCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().IsFeatured, 1).Count()
return count, err
}
// GetCategories 获取所有分类
func (dao *PartnerDao) GetCategories(ctx context.Context) ([]string, error) {
var categories []string
err := dao.Ctx(ctx).Fields("DISTINCT " + dao.Columns().Category).Scan(&categories)
if err != nil {
return nil, err
}
return categories, nil
}
// GetCountByCategory 根据分类获取合作伙伴数
func (dao *PartnerDao) GetCountByCategory(ctx context.Context, category string) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().Category, category).Count()
return count, err
}

View File

@@ -0,0 +1,121 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// GetById 根据ID获取角色
func (dao *RoleDao) GetById(ctx context.Context, id int) (*model.Role, error) {
var role *model.Role
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&role)
if err != nil {
return nil, err
}
return role, nil
}
// List 获取角色列表
func (dao *RoleDao) List(ctx context.Context, req *model.RoleListRequest) ([]*model.Role, int, error) {
var (
roles []*model.Role
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.Status >= 0 {
db = db.Where(dao.Columns().Status, req.Status)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().Name+" LIKE ? OR "+dao.Columns().Description+" LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&roles)
if err != nil {
return nil, 0, err
}
return roles, total, nil
}
// GetAll 获取所有角色
func (dao *RoleDao) GetAll(ctx context.Context) ([]*model.Role, error) {
var roles []*model.Role
err := dao.Ctx(ctx).Where(dao.Columns().Status, 1).Order(dao.Columns().CreatedAt + " ASC").Scan(&roles)
if err != nil {
return nil, err
}
return roles, nil
}
// GetByName 根据名称获取角色
func (dao *RoleDao) GetByName(ctx context.Context, name string) (*model.Role, error) {
var role *model.Role
err := dao.Ctx(ctx).Where(dao.Columns().Name, name).Scan(&role)
if err != nil {
return nil, err
}
return role, nil
}
// Create 创建角色
func (dao *RoleDao) Create(ctx context.Context, role *model.Role) (int64, error) {
result, err := dao.Ctx(ctx).Data(role).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新角色
func (dao *RoleDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// Delete 删除角色
func (dao *RoleDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// UpdateStatus 更新角色状态
func (dao *RoleDao) UpdateStatus(ctx context.Context, id int, status int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(map[string]interface{}{
dao.Columns().Status: status,
}).Update()
return err
}
// CheckPermission 检查权限
func (dao *RoleDao) CheckPermission(ctx context.Context, roleId int, permission string) (bool, error) {
var role *model.Role
err := dao.Ctx(ctx).Where(dao.Columns().Id, roleId).Scan(&role)
if err != nil {
return false, err
}
if role == nil {
return false, nil
}
// 这里应该实现权限检查逻辑
// 暂时返回true实际项目中需要根据role.Permissions字段进行权限验证
return true, nil
}

View File

@@ -0,0 +1,121 @@
package dao
import (
"context"
"cms-api/internal/model"
)
// GetByAccount 根据账号获取用户
func (dao *UserDao) GetByAccount(ctx context.Context, account string) (*model.User, error) {
var user *model.User
err := dao.Ctx(ctx).Where(dao.Columns().Account, account).Scan(&user)
if err != nil {
return nil, err
}
return user, nil
}
// GetByEmail 根据邮箱获取用户
func (dao *UserDao) GetByEmail(ctx context.Context, email string) (*model.User, error) {
var user *model.User
err := dao.Ctx(ctx).Where(dao.Columns().Email, email).Scan(&user)
if err != nil {
return nil, err
}
return user, nil
}
// GetById 根据ID获取用户
func (dao *UserDao) GetById(ctx context.Context, id int) (*model.User, error) {
var user *model.User
err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Scan(&user)
if err != nil {
return nil, err
}
return user, nil
}
// List 获取用户列表
func (dao *UserDao) List(ctx context.Context, req *model.UserListRequest) ([]*model.User, int, error) {
var (
users []*model.User
total int
db = dao.Ctx(ctx)
)
// 添加查询条件
if req.RoleId > 0 {
db = db.Where(dao.Columns().RoleId, req.RoleId)
}
if req.Status >= 0 {
db = db.Where(dao.Columns().Status, req.Status)
}
if req.Keyword != "" {
db = db.Where(dao.Columns().Account+" LIKE ? OR "+dao.Columns().NickName+" LIKE ? OR "+dao.Columns().Email+" LIKE ?",
"%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
// 获取总数
count, err := db.Count()
if err != nil {
return nil, 0, err
}
total = count
// 分页查询
offset := (req.Page - 1) * req.PageSize
err = db.Order(dao.Columns().CreatedAt + " DESC").
Limit(req.PageSize).
Offset(offset).
Scan(&users)
if err != nil {
return nil, 0, err
}
return users, total, nil
}
// Create 创建用户
func (dao *UserDao) Create(ctx context.Context, user *model.User) (int64, error) {
result, err := dao.Ctx(ctx).Data(user).Insert()
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return id, nil
}
// Update 更新用户
func (dao *UserDao) Update(ctx context.Context, id int, data interface{}) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(data).Update()
return err
}
// Delete 删除用户
func (dao *UserDao) Delete(ctx context.Context, id int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Delete()
return err
}
// UpdateStatus 更新用户状态
func (dao *UserDao) UpdateStatus(ctx context.Context, id int, status int) error {
_, err := dao.Ctx(ctx).Where(dao.Columns().Id, id).Data(map[string]interface{}{
dao.Columns().Status: status,
}).Update()
return err
}
// GetCount 获取用户总数
func (dao *UserDao) GetCount(ctx context.Context) (int, error) {
count, err := dao.Ctx(ctx).Count()
return count, err
}
// GetCountByStatus 根据状态获取用户数
func (dao *UserDao) GetCountByStatus(ctx context.Context, status int) (int, error) {
count, err := dao.Ctx(ctx).Where(dao.Columns().Status, status).Count()
return count, err
}

View File

@@ -0,0 +1,65 @@
package middleware
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// UserAuth 用户认证中间件
func UserAuth(r *ghttp.Request) {
token := r.Header.Get("Authorization")
if token == "" {
r.Response.WriteJson(g.Map{
"code": 401,
"message": "未提供认证令牌",
})
r.ExitAll()
return
}
// 简单的token验证实际项目中应该验证JWT
if token != "Bearer user-token" {
r.Response.WriteJson(g.Map{
"code": 401,
"message": "认证令牌无效",
})
r.ExitAll()
return
}
// 设置用户信息到上下文
r.SetCtxVar("user_id", 1)
r.SetCtxVar("username", "testuser")
r.Middleware.Next()
}
// AdminAuth 管理员认证中间件
func AdminAuth(r *ghttp.Request) {
token := r.Header.Get("Authorization")
if token == "" {
r.Response.WriteJson(g.Map{
"code": 401,
"message": "未提供认证令牌",
})
r.ExitAll()
return
}
// 简单的token验证实际项目中应该验证JWT
if token != "Bearer admin-token" {
r.Response.WriteJson(g.Map{
"code": 401,
"message": "管理员认证令牌无效",
})
r.ExitAll()
return
}
// 设置管理员信息到上下文
r.SetCtxVar("admin_id", 1)
r.SetCtxVar("admin_username", "admin")
r.SetCtxVar("admin_role", "super_admin")
r.Middleware.Next()
}

View File

@@ -0,0 +1,11 @@
package middleware
import (
"github.com/gogf/gf/v2/net/ghttp"
)
// CORS 跨域中间件
func CORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}

View File

@@ -0,0 +1,54 @@
package middleware
import (
"cms-api/internal/model"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// ResponseHandler 统一响应处理中间件
func ResponseHandler(r *ghttp.Request) {
r.Middleware.Next()
// 如果已经有响应内容,则不处理
if r.Response.BufferLength() > 0 {
return
}
var (
ctx = r.Context()
res = r.GetHandlerResponse()
err = r.GetError()
msg = "操作成功"
data interface{}
)
var responseCode int = 200
if err != nil {
errCode := gerror.Code(err)
if errCode != gcode.CodeNil {
responseCode = int(errCode.Code())
} else {
responseCode = 500
}
msg = err.Error()
g.Log().Error(ctx, "请求处理错误:", err)
} else if r.Response.Status >= 400 {
msg = "请求失败"
responseCode = r.Response.Status
} else {
data = res
}
response := &model.Response{
Code: responseCode,
Message: msg,
Data: data,
}
r.Response.WriteJsonExit(response)
}

0
internal/model/.gitkeep Normal file
View File

77
internal/model/admin.go Normal file
View File

@@ -0,0 +1,77 @@
package model
import (
"time"
)
// Admin 管理员实体
type Admin struct {
Id uint64 `json:"id" orm:"id,primary"`
Username string `json:"username" orm:"username"`
Password string `json:"-" orm:"password"`
RealName string `json:"real_name" orm:"real_name"`
Email string `json:"email" orm:"email"`
Phone string `json:"phone" orm:"phone"`
Avatar string `json:"avatar" orm:"avatar"`
RoleId uint64 `json:"role_id" orm:"role_id"`
Status uint8 `json:"status" orm:"status"`
LastLoginAt *time.Time `json:"last_login_at" orm:"last_login_at"`
LastLoginIp string `json:"last_login_ip" orm:"last_login_ip"`
CreatedAt *time.Time `json:"created_at" orm:"created_at"`
UpdatedAt *time.Time `json:"updated_at" orm:"updated_at"`
DeletedAt uint64 `json:"deleted_at" orm:"deleted_at"`
}
// LoginRequest 登录请求
type LoginRequest struct {
Username string `json:"username" v:"required|length:3,30#用户名不能为空|用户名长度为3到30位"`
Password string `json:"password" v:"required|length:6,30#密码不能为空|密码长度为6到30位"`
}
// LoginResponse 登录响应
type LoginResponse struct {
Token string `json:"token"`
Admin *Admin `json:"admin"`
}
// AdminListRequest 管理员列表请求
type AdminListRequest struct {
Page int `json:"page" v:"min:1#页码不能小于1"`
PageSize int `json:"page_size" v:"min:1|max:100#每页数量不能小于1且不能大于100"`
Username string `json:"username"`
RealName string `json:"real_name"`
Status *int `json:"status"`
}
// CreateAdminRequest 创建管理员请求
type CreateAdminRequest struct {
Username string `json:"username" v:"required|length:3,30#用户名不能为空|用户名长度为3到30位"`
Password string `json:"password" v:"required|length:6,30#密码不能为空|密码长度为6到30位"`
RealName string `json:"real_name" v:"required|length:2,20#真实姓名不能为空|真实姓名长度为2到20位"`
Email string `json:"email" v:"email#邮箱格式不正确"`
Phone string `json:"phone" v:"phone#手机号格式不正确"`
RoleId uint64 `json:"role_id" v:"required|min:1#角色ID不能为空"`
Status uint8 `json:"status" v:"in:0,1#状态值只能为0或1"`
}
// UpdateAdminRequest 更新管理员请求
type UpdateAdminRequest struct {
RealName string `json:"real_name" v:"required|length:2,20#真实姓名不能为空|真实姓名长度为2到20位"`
Email string `json:"email" v:"email#邮箱格式不正确"`
Phone string `json:"phone" v:"phone#手机号格式不正确"`
RoleId uint64 `json:"role_id" v:"required|min:1#角色ID不能为空"`
Status uint8 `json:"status" v:"in:0,1#状态值只能为0或1"`
}
// AdminLoginRequest 管理员登录请求
type AdminLoginRequest struct {
Username string `json:"username" v:"required|length:3,30#用户名不能为空|用户名长度为3到30位"`
Password string `json:"password" v:"required|length:6,30#密码不能为空|密码长度为6到30位"`
}
// AdminLoginResponse 管理员登录响应
type AdminLoginResponse struct {
Token string `json:"token"`
ExpiresIn int `json:"expires_in"`
Admin *Admin `json:"admin"`
}

67
internal/model/article.go Normal file
View File

@@ -0,0 +1,67 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// Article 文章模型
type Article struct {
Id int `json:"id" dc:"主键"`
Title string `json:"title" dc:"标题"`
Slug string `json:"slug" dc:"URL别名"`
Summary string `json:"summary" dc:"摘要"`
Content string `json:"content" dc:"内容(Markdown)"`
HtmlContent string `json:"html_content" dc:"HTML内容"`
CoverImage string `json:"cover_image" dc:"封面图片"`
CategoryId int `json:"category_id" dc:"分类ID"`
Tags interface{} `json:"tags" dc:"标签"`
AuthorId int `json:"author_id" dc:"作者ID"`
ViewCount int `json:"view_count" dc:"浏览次数"`
LikeCount int `json:"like_count" dc:"点赞次数"`
CommentCount int `json:"comment_count" dc:"评论次数"`
IsPublished int `json:"is_published" dc:"是否发布 0草稿 1已发布"`
IsFeatured int `json:"is_featured" dc:"是否推荐 0否 1是"`
IsTop int `json:"is_top" dc:"是否置顶 0否 1是"`
SeoTitle string `json:"seo_title" dc:"SEO标题"`
SeoDescription string `json:"seo_description" dc:"SEO描述"`
SeoKeywords string `json:"seo_keywords" dc:"SEO关键词"`
PublishedAt *gtime.Time `json:"published_at" dc:"发布时间"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
DeletedAt int `json:"deleted_at" dc:"删除时间"`
}
// CreateArticleRequest 创建文章请求
type CreateArticleRequest struct {
Title string `json:"title" v:"required|length:1,255#标题不能为空|标题长度不能超过255字符" dc:"标题"`
Slug string `json:"slug" v:"length:0,255#URL别名长度不能超过255字符" dc:"URL别名"`
Summary string `json:"summary" dc:"摘要"`
Content string `json:"content" v:"required#内容不能为空" dc:"内容(Markdown)"`
CoverImage string `json:"cover_image" dc:"封面图片"`
CategoryId int `json:"category_id" dc:"分类ID"`
Tags []string `json:"tags" dc:"标签"`
AuthorId int `json:"author_id" dc:"作者ID"`
IsPublished int `json:"is_published" dc:"是否发布"`
IsFeatured int `json:"is_featured" dc:"是否推荐"`
IsTop int `json:"is_top" dc:"是否置顶"`
SeoTitle string `json:"seo_title" dc:"SEO标题"`
SeoDescription string `json:"seo_description" dc:"SEO描述"`
SeoKeywords string `json:"seo_keywords" dc:"SEO关键词"`
}
// UpdateArticleRequest 更新文章请求
type UpdateArticleRequest struct {
Title string `json:"title" v:"required|length:1,255#标题不能为空|标题长度不能超过255字符" dc:"标题"`
Slug string `json:"slug" v:"required|length:1,255#URL别名不能为空|URL别名长度不能超过255字符" dc:"URL别名"`
Summary string `json:"summary" v:"required#摘要不能为空" dc:"摘要"`
Content string `json:"content" v:"required#内容不能为空" dc:"内容"`
CoverImage string `json:"cover_image" dc:"封面图片"`
CategoryId int `json:"category_id" dc:"分类ID"`
Tags []string `json:"tags" dc:"标签"`
IsPublished int `json:"is_published" v:"in:0,1#发布状态只能为0或1" dc:"是否发布"`
IsFeatured int `json:"is_featured" v:"in:0,1#推荐状态只能为0或1" dc:"是否推荐"`
IsTop int `json:"is_top" v:"in:0,1#置顶状态只能为0或1" dc:"是否置顶"`
SeoTitle string `json:"seo_title" dc:"SEO标题"`
SeoDescription string `json:"seo_description" dc:"SEO描述"`
SeoKeywords string `json:"seo_keywords" dc:"SEO关键词"`
}

View File

@@ -0,0 +1,40 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// Attachment 附件模型
type Attachment struct {
Id int `json:"id" dc:"主键"`
OriginalName string `json:"original_name" dc:"原始文件名"`
FileName string `json:"file_name" dc:"存储文件名"`
FilePath string `json:"file_path" dc:"文件路径"`
FileUrl string `json:"file_url" dc:"文件URL"`
FileSize int64 `json:"file_size" dc:"文件大小(字节)"`
FileType string `json:"file_type" dc:"文件类型"`
MimeType string `json:"mime_type" dc:"MIME类型"`
FileExt string `json:"file_ext" dc:"文件扩展名"`
StorageType string `json:"storage_type" dc:"存储类型 local:本地 oss:阿里云OSS"`
UploadIp string `json:"upload_ip" dc:"上传IP"`
UploadedBy int `json:"uploaded_by" dc:"上传者ID"`
UsageCount int `json:"usage_count" dc:"使用次数"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
DeletedAt int `json:"deleted_at" dc:"删除时间"`
}
// UploadRequest 上传请求
type UploadRequest struct {
FileType string `json:"file_type" dc:"文件类型"`
Category string `json:"category" dc:"分类"`
}
// UploadResponse 上传响应
type UploadResponse struct {
Id int `json:"id" dc:"附件ID"`
FileName string `json:"file_name" dc:"文件名"`
FileUrl string `json:"file_url" dc:"文件URL"`
FileSize int64 `json:"file_size" dc:"文件大小"`
FileType string `json:"file_type" dc:"文件类型"`
}

82
internal/model/common.go Normal file
View File

@@ -0,0 +1,82 @@
package model
// 通用响应结构
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// 分页响应结构
type PageResponse struct {
List interface{} `json:"list"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// 通用列表请求
type ListRequest struct {
Page int `json:"page" form:"page"`
PageSize int `json:"page_size" form:"page_size"`
Keyword string `json:"keyword" form:"keyword"`
Status int `json:"status" form:"status"`
}
// 文章列表请求
type ArticleListRequest struct {
ListRequest
CategoryId int `json:"category_id" form:"category_id"`
AuthorId int `json:"author_id" form:"author_id"`
IsPublished int `json:"is_published" form:"is_published"`
IsFeatured int `json:"is_featured" form:"is_featured"`
IsTop int `json:"is_top" form:"is_top"`
StartDate string `json:"start_date" form:"start_date"`
EndDate string `json:"end_date" form:"end_date"`
}
// 附件列表请求
type AttachmentListRequest struct {
ListRequest
FileType string `json:"file_type" form:"file_type"`
UploadedBy int `json:"uploaded_by" form:"uploaded_by"`
StartDate string `json:"start_date" form:"start_date"`
EndDate string `json:"end_date" form:"end_date"`
StorageType string `json:"storage_type" form:"storage_type"`
}
// 新闻列表请求
type NewsListRequest struct {
ListRequest
Category string `json:"category" form:"category"`
Author string `json:"author" form:"author"`
Source string `json:"source" form:"source"`
IsPublished int `json:"is_published" form:"is_published"`
IsFeatured int `json:"is_featured" form:"is_featured"`
IsTop int `json:"is_top" form:"is_top"`
StartDate string `json:"start_date" form:"start_date"`
EndDate string `json:"end_date" form:"end_date"`
}
// 合作伙伴列表请求
type PartnerListRequest struct {
ListRequest
Category string `json:"category" form:"category"`
IsFeatured int `json:"is_featured" form:"is_featured"`
}
// 联系我们列表请求
type ContactListRequest struct {
ListRequest
StartDate string `json:"start_date" form:"start_date"`
EndDate string `json:"end_date" form:"end_date"`
}
// 配置列表请求
type ConfigListRequest struct {
ListRequest
GroupName string `json:"group_name" form:"group_name"`
IsSystem int `json:"is_system" form:"is_system"`
}

48
internal/model/config.go Normal file
View File

@@ -0,0 +1,48 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// Config 配置模型
type Config struct {
Id int `json:"id" dc:"主键"`
ConfigKey string `json:"config_key" dc:"配置键"`
ConfigValue string `json:"config_value" dc:"配置值"`
ConfigType string `json:"config_type" dc:"配置类型 string:字符串 int:整数 bool:布尔 json:JSON"`
GroupName string `json:"group_name" dc:"分组名称"`
Description string `json:"description" dc:"配置描述"`
SortOrder int `json:"sort_order" dc:"排序"`
IsSystem int `json:"is_system" dc:"是否系统配置 0否 1是"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
}
// SiteConfig 站点配置别名
type SiteConfig = Config
// CreateConfigRequest 创建配置请求
type CreateConfigRequest struct {
ConfigKey string `json:"config_key" v:"required|length:1,100#配置键不能为空|配置键长度不能超过100字符" dc:"配置键"`
ConfigValue string `json:"config_value" dc:"配置值"`
ConfigType string `json:"config_type" dc:"配置类型"`
GroupName string `json:"group_name" dc:"分组名称"`
Description string `json:"description" dc:"配置描述"`
SortOrder int `json:"sort_order" dc:"排序"`
IsSystem int `json:"is_system" dc:"是否系统配置"`
}
// UpdateConfigRequest 更新配置请求
type UpdateConfigRequest struct {
ConfigValue string `json:"config_value" dc:"配置值"`
ConfigType string `json:"config_type" dc:"配置类型"`
GroupName string `json:"group_name" dc:"分组名称"`
Description string `json:"description" dc:"配置描述"`
SortOrder int `json:"sort_order" dc:"排序"`
IsSystem int `json:"is_system" dc:"是否系统配置"`
}
// BatchUpdateConfigRequest 批量更新配置请求
type BatchUpdateConfigRequest struct {
Configs map[string]string `json:"configs" v:"required#配置不能为空" dc:"配置键值对"`
}

45
internal/model/contact.go Normal file
View File

@@ -0,0 +1,45 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// Contact 联系我们模型
type Contact struct {
Id int `json:"id" dc:"主键"`
Name string `json:"name" dc:"姓名"`
Email string `json:"email" dc:"邮箱"`
Phone string `json:"phone" dc:"电话"`
Company string `json:"company" dc:"公司"`
Subject string `json:"subject" dc:"主题"`
Message string `json:"message" dc:"留言内容"`
Ip string `json:"ip" dc:"IP地址"`
UserAgent string `json:"user_agent" dc:"用户代理"`
Status int `json:"status" dc:"状态 0未处理 1已处理 2已回复"`
Reply string `json:"reply" dc:"回复内容"`
RepliedAt *gtime.Time `json:"replied_at" dc:"回复时间"`
RepliedBy int `json:"replied_by" dc:"回复人ID"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
}
// CreateContactRequest 创建联系信息请求
type CreateContactRequest struct {
Name string `json:"name" v:"required|length:1,50#姓名不能为空|姓名长度不能超过50字符" dc:"姓名"`
Email string `json:"email" v:"required|email#邮箱不能为空|邮箱格式不正确" dc:"邮箱"`
Phone string `json:"phone" dc:"电话"`
Company string `json:"company" dc:"公司"`
Subject string `json:"subject" v:"required|length:1,200#主题不能为空|主题长度不能超过200字符" dc:"主题"`
Message string `json:"message" v:"required|length:1,2000#留言内容不能为空|留言内容长度不能超过2000字符" dc:"留言内容"`
}
// ReplyContactRequest 回复联系信息请求
type ReplyContactRequest struct {
Reply string `json:"reply" v:"required|length:1,2000#回复内容不能为空|回复内容长度不能超过2000字符" dc:"回复内容"`
}
// ContactTrend 联系信息趋势数据
type ContactTrend struct {
Date string `json:"date" dc:"日期"`
Count int `json:"count" dc:"数量"`
}

View File

View File

View File

@@ -0,0 +1,23 @@
package entity
import (
"time"
)
// Admin 管理员实体
type Admin struct {
Id uint64 `json:"id" orm:"id,primary"`
Username string `json:"username" orm:"username"`
Password string `json:"-" orm:"password"`
RealName string `json:"real_name" orm:"real_name"`
Email string `json:"email" orm:"email"`
Phone string `json:"phone" orm:"phone"`
Avatar string `json:"avatar" orm:"avatar"`
RoleId uint64 `json:"role_id" orm:"role_id"`
Status uint8 `json:"status" orm:"status"`
LastLoginAt *time.Time `json:"last_login_at" orm:"last_login_at"`
LastLoginIp string `json:"last_login_ip" orm:"last_login_ip"`
CreatedAt *time.Time `json:"created_at" orm:"created_at"`
UpdatedAt *time.Time `json:"updated_at" orm:"updated_at"`
DeletedAt uint64 `json:"deleted_at" orm:"deleted_at"`
}

56
internal/model/news.go Normal file
View File

@@ -0,0 +1,56 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// News 新闻模型
type News struct {
Id int `json:"id" dc:"主键"`
Title string `json:"title" dc:"标题"`
Slug string `json:"slug" dc:"URL别名"`
Summary string `json:"summary" dc:"摘要"`
Content string `json:"content" dc:"内容"`
CoverImage string `json:"cover_image" dc:"封面图片"`
Category string `json:"category" dc:"分类"`
Source string `json:"source" dc:"来源"`
Author string `json:"author" dc:"作者"`
ViewCount int `json:"view_count" dc:"浏览次数"`
IsPublished int `json:"is_published" dc:"是否发布 0草稿 1已发布"`
IsFeatured int `json:"is_featured" dc:"是否推荐 0否 1是"`
IsTop int `json:"is_top" dc:"是否置顶 0否 1是"`
PublishedAt *gtime.Time `json:"published_at" dc:"发布时间"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
DeletedAt int `json:"deleted_at" dc:"删除时间"`
}
// CreateNewsRequest 创建新闻请求
type CreateNewsRequest struct {
Title string `json:"title" v:"required|length:1,255#标题不能为空|标题长度不能超过255字符" dc:"标题"`
Slug string `json:"slug" v:"length:0,255#URL别名长度不能超过255字符" dc:"URL别名"`
Summary string `json:"summary" dc:"摘要"`
Content string `json:"content" v:"required#内容不能为空" dc:"内容"`
CoverImage string `json:"cover_image" dc:"封面图片"`
Category string `json:"category" dc:"分类"`
Source string `json:"source" dc:"来源"`
Author string `json:"author" dc:"作者"`
IsPublished int `json:"is_published" dc:"是否发布"`
IsFeatured int `json:"is_featured" dc:"是否推荐"`
IsTop int `json:"is_top" dc:"是否置顶"`
}
// UpdateNewsRequest 更新新闻请求
type UpdateNewsRequest struct {
Title string `json:"title" v:"required|length:1,255#标题不能为空|标题长度不能超过255字符" dc:"标题"`
Slug string `json:"slug" v:"length:0,255#URL别名长度不能超过255字符" dc:"URL别名"`
Summary string `json:"summary" dc:"摘要"`
Content string `json:"content" v:"required#内容不能为空" dc:"内容"`
CoverImage string `json:"cover_image" dc:"封面图片"`
Category string `json:"category" dc:"分类"`
Source string `json:"source" dc:"来源"`
Author string `json:"author" dc:"作者"`
IsPublished int `json:"is_published" dc:"是否发布"`
IsFeatured int `json:"is_featured" dc:"是否推荐"`
IsTop int `json:"is_top" dc:"是否置顶"`
}

45
internal/model/partner.go Normal file
View File

@@ -0,0 +1,45 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// Partner 合作伙伴模型
type Partner struct {
Id int `json:"id" dc:"主键"`
Name string `json:"name" dc:"合作伙伴名称"`
Logo string `json:"logo" dc:"Logo图片"`
Website string `json:"website" dc:"官网地址"`
Description string `json:"description" dc:"描述"`
Category string `json:"category" dc:"分类"`
SortOrder int `json:"sort_order" dc:"排序"`
IsFeatured int `json:"is_featured" dc:"是否推荐 0否 1是"`
Status int `json:"status" dc:"状态 0禁用 1启用"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
DeletedAt int `json:"deleted_at" dc:"删除时间"`
}
// CreatePartnerRequest 创建合作伙伴请求
type CreatePartnerRequest struct {
Name string `json:"name" v:"required|length:1,100#合作伙伴名称不能为空|名称长度不能超过100字符" dc:"合作伙伴名称"`
Logo string `json:"logo" dc:"Logo图片"`
Website string `json:"website" dc:"官网地址"`
Description string `json:"description" dc:"描述"`
Category string `json:"category" dc:"分类"`
SortOrder int `json:"sort_order" dc:"排序"`
IsFeatured int `json:"is_featured" dc:"是否推荐"`
Status int `json:"status" dc:"状态"`
}
// UpdatePartnerRequest 更新合作伙伴请求
type UpdatePartnerRequest struct {
Name string `json:"name" v:"required|length:1,100#合作伙伴名称不能为空|名称长度不能超过100字符" dc:"合作伙伴名称"`
Logo string `json:"logo" dc:"Logo图片"`
Website string `json:"website" dc:"官网地址"`
Description string `json:"description" dc:"描述"`
Category string `json:"category" dc:"分类"`
SortOrder int `json:"sort_order" dc:"排序"`
IsFeatured int `json:"is_featured" dc:"是否推荐"`
Status int `json:"status" dc:"状态"`
}

View File

@@ -0,0 +1,17 @@
package model
// Permission 权限模型
type Permission struct {
Key string `json:"key" dc:"权限键"`
Name string `json:"name" dc:"权限名称"`
Description string `json:"description" dc:"权限描述"`
Group string `json:"group" dc:"权限分组"`
}
// PermissionGroup 权限分组模型
type PermissionGroup struct {
Key string `json:"key" dc:"分组键"`
Name string `json:"name" dc:"分组名称"`
Description string `json:"description" dc:"分组描述"`
Permissions []Permission `json:"permissions" dc:"权限列表"`
}

41
internal/model/role.go Normal file
View File

@@ -0,0 +1,41 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// Role 角色模型
type Role struct {
Id int `json:"id" dc:"主键"`
Name string `json:"name" dc:"角色名称"`
Description string `json:"description" dc:"角色描述"`
Permissions interface{} `json:"permissions" dc:"权限列表"`
Status int `json:"status" dc:"状态 0禁用 1启用"`
CreatedAt *gtime.Time `json:"created_at" dc:"创建时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"更新时间"`
DeletedAt int `json:"deleted_at" dc:"删除时间"`
}
// RoleListRequest 角色列表请求
type RoleListRequest struct {
Page int `json:"page" dc:"页码"`
PageSize int `json:"page_size" dc:"每页数量"`
Keyword string `json:"keyword" dc:"关键词"`
Status int `json:"status" dc:"状态"`
}
// CreateRoleRequest 创建角色请求
type CreateRoleRequest struct {
Name string `json:"name" v:"required|length:2,32#角色名称不能为空|角色名称长度为2-32位" dc:"角色名称"`
Description string `json:"description" dc:"角色描述"`
Permissions []string `json:"permissions" dc:"权限列表"`
Status int `json:"status" dc:"状态"`
}
// UpdateRoleRequest 更新角色请求
type UpdateRoleRequest struct {
Name string `json:"name" v:"required|length:2,32#角色名称不能为空|角色名称长度为2-32位" dc:"角色名称"`
Description string `json:"description" dc:"角色描述"`
Permissions []string `json:"permissions" dc:"权限列表"`
Status int `json:"status" dc:"状态"`
}

83
internal/model/user.go Normal file
View File

@@ -0,0 +1,83 @@
package model
import (
"github.com/gogf/gf/v2/os/gtime"
)
// User 用户模型
type User struct {
Id int `json:"id" dc:"主键"`
Account string `json:"account" dc:"账号"`
NickName string `json:"nick_name" dc:"昵称"`
Avatar string `json:"avatar" dc:"头像"`
Email string `json:"email" dc:"邮箱"`
Password string `json:"-" dc:"登录密码"`
Balance float64 `json:"balance" dc:"余额"`
QrCode string `json:"qr_code" dc:"微信二维码地址"`
RoleId int `json:"role_id" dc:"角色"`
IsSysNotifications int `json:"is_sys_notifications" dc:"是否接受邮件系统通知"`
IsCollectionNotifications int `json:"is_collection_notifications" dc:"是否接受收藏的项目更新信息"`
IsMarketingNotifications int `json:"is_marketing_notifications" dc:"是否接受营销信息"`
Ip string `json:"ip" dc:"本次登录ip"`
IpTable interface{} `json:"ip_table" dc:"常用登录IP地址列表"`
Status int `json:"status" dc:"状态 0正常 1冻结 2封号 3注销"`
LastResetPasswordAt int `json:"last_reset_password_at" dc:"最后一次修改密码的时间"`
CreatedAt *gtime.Time `json:"created_at" dc:"注册时间"`
UpdatedAt *gtime.Time `json:"updated_at" dc:"编辑时间"`
DeletedAt int `json:"deleted_at" dc:"删除时间"`
}
// UserListRequest 用户列表请求
type UserListRequest struct {
Page int `json:"page" dc:"页码"`
PageSize int `json:"page_size" dc:"每页数量"`
Keyword string `json:"keyword" dc:"关键词"`
Status int `json:"status" dc:"状态"`
RoleId int `json:"role_id" dc:"角色ID"`
}
// CreateUserRequest 创建用户请求
type CreateUserRequest struct {
Account string `json:"account" v:"required|length:3,32#账号不能为空|账号长度为3-32位" dc:"账号"`
NickName string `json:"nick_name" v:"required|length:2,64#昵称不能为空|昵称长度为2-64位" dc:"昵称"`
Email string `json:"email" v:"required|email#邮箱不能为空|邮箱格式不正确" dc:"邮箱"`
Password string `json:"password" v:"required|length:6,32#密码不能为空|密码长度为6-32位" dc:"密码"`
Avatar string `json:"avatar" dc:"头像"`
Balance float64 `json:"balance" dc:"余额"`
RoleId int `json:"role_id" dc:"角色ID"`
IsSysNotifications int `json:"is_sys_notifications" dc:"是否接受邮件系统通知"`
IsCollectionNotifications int `json:"is_collection_notifications" dc:"是否接受收藏的项目更新信息"`
IsMarketingNotifications int `json:"is_marketing_notifications" dc:"是否接受营销信息"`
}
// UpdateUserRequest 更新用户请求
type UpdateUserRequest struct {
NickName string `json:"nick_name" v:"required|length:2,64#昵称不能为空|昵称长度为2-64位" dc:"昵称"`
Email string `json:"email" v:"required|email#邮箱不能为空|邮箱格式不正确" dc:"邮箱"`
Avatar string `json:"avatar" dc:"头像"`
Balance float64 `json:"balance" dc:"余额"`
RoleId int `json:"role_id" dc:"角色ID"`
Status int `json:"status" dc:"状态"`
IsSysNotifications int `json:"is_sys_notifications" dc:"是否接受邮件系统通知"`
IsCollectionNotifications int `json:"is_collection_notifications" dc:"是否接受收藏的项目更新信息"`
IsMarketingNotifications int `json:"is_marketing_notifications" dc:"是否接受营销信息"`
}
// ChangePasswordRequest 修改密码请求
type ChangePasswordRequest struct {
OldPassword string `json:"old_password" v:"required#原密码不能为空" dc:"原密码"`
NewPassword string `json:"new_password" v:"required|length:6,32#新密码不能为空|新密码长度为6-32位" dc:"新密码"`
}
// UserLoginRequest 用户登录请求
type UserLoginRequest struct {
Account string `json:"account" v:"required#账号不能为空" dc:"账号"`
Password string `json:"password" v:"required#密码不能为空" dc:"密码"`
}
// UserLoginResponse 用户登录响应
type UserLoginResponse struct {
Token string `json:"token" dc:"访问令牌"`
ExpiresIn int `json:"expires_in" dc:"过期时间(秒)"`
User *User `json:"user" dc:"用户信息"`
}

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
}