用DDD分项目模块

This commit is contained in:
2025-10-05 01:46:48 +08:00
parent 48461b9c49
commit 28acc5104c
41 changed files with 1138 additions and 17 deletions

2
server/.gitignore vendored
View File

@@ -2,3 +2,5 @@
server/pkg/mod/
server/pkg/cache/
server/pkg/sumdb/
idea/

96
server/api/article.api Normal file
View File

@@ -0,0 +1,96 @@
// 定义文章相关的结构体(与数据表字段映射)
type (
// 文章基础结构(对应数据表字段)
Article {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Cover string `json:"cover"`
CreateAt string `json:"create_at"` // 前端通常用字符串承载时间,可根据需求改为 time.Time
UpdateAt string `json:"update_at"`
IsDelete int `json:"is_delete"` // tinyint 映射为 int1=已删除0=未删除
Topic string `json:"topic"`
Excerpt string `json:"excerpt"`
}
// 1. 创建文章:请求体
CreateArticleReq {
Title string `json:"title"` // 文章标题
Content string `json:"content"` // 文章内容
Cover string `json:"cover"` // 封面图
Topic string `json:"topic"` // 话题
}
// 创建文章:响应
CreateArticleResp {
Id int64 `json:"id"` // 新增文章的 ID
Success bool `json:"success"` // 操作是否成功
}
// 2. 文章列表:请求(带分页/筛选)
ListArticleReq {
Page int `json:"page,optional"` // 页码(可选,默认第一页)
Size int `json:"size,optional"` // 每页条数(可选,默认 10 条)
Topic string `json:"topic,optional"` // 按话题筛选(可选)
}
// 文章列表:响应
ListArticleResp {
Total int64 `json:"total"` // 总条数
List []Article `json:"list"` // 文章列表
}
// 3. 文章详情:请求(通过 ID 查询)
DetailArticleReq {
Id int64 `path:"id"` // 从路径参数取 ID如 /api/articles/:id
}
// 文章详情:响应
DetailArticleResp {
Article Article `json:"article"` // 文章详情
Success bool `json:"success"` // 操作是否成功
}
// 4. 更新文章:请求(部分字段可选更新)
UpdateArticleReq {
Id int64 `path:"id"` // 要更新的文章 ID
Title string `json:"title,optional"`
Content string `json:"content,optional"`
Cover string `json:"cover,optional"`
Topic string `json:"topic,optional"`
Excerpt string `json:"excerpt,optional"`
}
// 更新文章:响应
UpdateArticleResp {
Success bool `json:"success"`
}
// 5. 删除文章(软删除:修改 is_delete 为 1请求
DeleteArticleReq {
Id int64 `path:"id"` // 要删除的文章 ID
}
// 删除文章:响应
DeleteArticleResp {
Success bool `json:"success"`
}
)
// 文章服务的 API 分组与路由前缀
@server (
group: article
prefix: /api/articles
)
service article-api {
// 1. 创建文章POST
@handler CreateArticleHandler
post / (CreateArticleReq) returns (CreateArticleResp)
// 2. 查询文章列表GET
@handler ListArticleHandler
get / (ListArticleReq) returns (ListArticleResp)
// 3. 查询文章详情GET
@handler DetailArticleHandler
get /:id (DetailArticleReq) returns (DetailArticleResp)
// 4. 更新文章PUT
@handler UpdateArticleHandler
put /:id (UpdateArticleReq) returns (UpdateArticleResp)
// 5. 删除文章软删除DELETE
@handler DeleteArticleHandler
delete /:id (DeleteArticleReq) returns (DeleteArticleResp)
}

14
server/api/ping.api Normal file
View File

@@ -0,0 +1,14 @@
type PingReq {}
type PingResp {
Msg string `json:"message"`
}
@server (
group: ping
)
service ping-api {
@handler PingHandler
get /ping (PingReq) returns (PingResp)
}

19
server/api/upload.api Normal file
View File

@@ -0,0 +1,19 @@
type (
UploadImageResp {
Code int `json:"code"` // 业务状态码
Message string `json:"message"` // 提示信息
Data {
Url string `json:"url"` // 图片访问地址
} `json:"data"`
}
)
@server (
group: upload
prefix: /api
)
service upload-api {
@handler UploadImageHandler
post /upload/image returns (UploadImageResp)
}

34
server/article.go Normal file
View File

@@ -0,0 +1,34 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package main
import (
"flag"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/internal/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/handler"
"github.com/JACKYMYPERSON/hldrCenter/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
)
var configFile = flag.String("f", "etc/article-api.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@@ -7,6 +7,7 @@ require (
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-gonic/gin v1.11.0 // indirect
@@ -18,14 +19,20 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/zeromicro/go-zero v1.9.1 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
@@ -36,6 +43,9 @@ require (
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.13.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -8,6 +8,8 @@ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
@@ -31,10 +33,14 @@ github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzh
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -44,6 +50,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -55,6 +63,14 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/zeromicro/go-zero v1.9.1 h1:GZCl4jun/ZgZHnSvX3SSNDHf+tEGmEQ8x2Z23xjHa9g=
github.com/zeromicro/go-zero v1.9.1/go.mod h1:bHOl7Xr7EV/iHZWEqsUNJwFc/9WgAMrPpPagYvOaMtY=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
@@ -67,6 +83,7 @@ golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
@@ -76,9 +93,15 @@ golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func CreateArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewCreateArticleLogic(r.Context(), svcCtx)
resp, err := l.CreateArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeleteArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeleteArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewDeleteArticleLogic(r.Context(), svcCtx)
resp, err := l.DeleteArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DetailArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DetailArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewDetailArticleLogic(r.Context(), svcCtx)
resp, err := l.DetailArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewListArticleLogic(r.Context(), svcCtx)
resp, err := l.ListArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpdateArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewUpdateArticleLogic(r.Context(), svcCtx)
resp, err := l.UpdateArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"context"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateArticleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateArticleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateArticleLogic {
return &CreateArticleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateArticleLogic) CreateArticle(req *types.CreateArticleReq) (resp *types.CreateArticleResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"context"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteArticleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteArticleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteArticleLogic {
return &DeleteArticleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteArticleLogic) DeleteArticle(req *types.DeleteArticleReq) (resp *types.DeleteArticleResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"context"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type DetailArticleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDetailArticleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DetailArticleLogic {
return &DetailArticleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DetailArticleLogic) DetailArticle(req *types.DetailArticleReq) (resp *types.DetailArticleResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"context"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ListArticleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListArticleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListArticleLogic {
return &ListArticleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListArticleLogic) ListArticle(req *types.ListArticleReq) (resp *types.ListArticleResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"context"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/article/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateArticleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateArticleLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateArticleLogic {
return &UpdateArticleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateArticleLogic) UpdateArticle(req *types.UpdateArticleReq) (resp *types.UpdateArticleResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -1,4 +1,4 @@
package articlemodel
package model
import "github.com/zeromicro/go-zero/core/stores/sqlx"

View File

@@ -2,7 +2,7 @@
// versions:
// goctl version: 1.9.1
package articlemodel
package model
import (
"context"

View File

@@ -1,4 +1,4 @@
package articlemodel
package model
import "github.com/zeromicro/go-zero/core/stores/sqlx"

View File

@@ -0,0 +1,69 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.9.1
package types
type Article struct {
Id int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Cover string `json:"cover"`
CreateAt string `json:"create_at"` // 前端通常用字符串承载时间,可根据需求改为 time.Time
UpdateAt string `json:"update_at"`
IsDelete int `json:"is_delete"` // tinyint 映射为 int1=已删除0=未删除
Topic string `json:"topic"`
Excerpt string `json:"excerpt"`
}
type CreateArticleReq struct {
Title string `json:"title"` // 文章标题
Content string `json:"content"` // 文章内容
Cover string `json:"cover"` // 封面图
Topic string `json:"topic"` // 话题
}
type CreateArticleResp struct {
Id int64 `json:"id"` // 新增文章的 ID
Success bool `json:"success"` // 操作是否成功
}
type DeleteArticleReq struct {
Id int64 `path:"id"` // 要删除的文章 ID
}
type DeleteArticleResp struct {
Success bool `json:"success"`
}
type DetailArticleReq struct {
Id int64 `path:"id"` // 从路径参数取 ID如 /api/articles/:id
}
type DetailArticleResp struct {
Article Article `json:"article"` // 文章详情
Success bool `json:"success"` // 操作是否成功
}
type ListArticleReq struct {
Page int `json:"page,optional"` // 页码(可选,默认第一页)
Size int `json:"size,optional"` // 每页条数(可选,默认 10 条)
Topic string `json:"topic,optional"` // 按话题筛选(可选)
}
type ListArticleResp struct {
Total int64 `json:"total"` // 总条数
List []Article `json:"list"` // 文章列表
}
type UpdateArticleReq struct {
Id int64 `path:"id"` // 要更新的文章 ID
Title string `json:"title,optional"`
Content string `json:"content,optional"`
Cover string `json:"cover,optional"`
Topic string `json:"topic,optional"`
Excerpt string `json:"excerpt,optional"`
}
type UpdateArticleResp struct {
Success bool `json:"success"`
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func CreateArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewCreateArticleLogic(r.Context(), svcCtx)
resp, err := l.CreateArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DeleteArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DeleteArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewDeleteArticleLogic(r.Context(), svcCtx)
resp, err := l.DeleteArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DetailArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DetailArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewDetailArticleLogic(r.Context(), svcCtx)
resp, err := l.DetailArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func ListArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewListArticleLogic(r.Context(), svcCtx)
resp, err := l.ListArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package article
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/logic/article"
"github.com/JACKYMYPERSON/hldrCenter/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UpdateArticleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateArticleReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := article.NewUpdateArticleLogic(r.Context(), svcCtx)
resp, err := l.UpdateArticle(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package ping
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/logic/ping"
"github.com/JACKYMYPERSON/hldrCenter/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PingHandler(cfg *config.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PingReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := ping.NewPingLogic(r.Context(), cfg)
resp, err := l.Ping(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,3 @@
Name: ping-api
Host: 0.0.0.0
Port: 8888

View File

@@ -0,0 +1,10 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
}

View File

@@ -0,0 +1,31 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package ping
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/logic/ping"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/types"
"github.com/zeromicro/go-zero/rest/httpx"
)
func PingHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PingReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := ping.NewPingLogic(r.Context(), svcCtx)
resp, err := l.Ping(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,25 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.9.1
package handler
import (
"net/http"
ping "github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/handler/ping"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/ping",
Handler: ping.PingHandler(serverCtx),
},
},
)
}

View File

@@ -0,0 +1,33 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package ping
import (
"context"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type PingLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
return &PingLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PingLogic) Ping(req *types.PingReq) (resp *types.PingResp, err error) {
// todo: add your logic here and delete this line
return
}

View File

@@ -0,0 +1,18 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package svc
import (
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/config"
)
type ServiceContext struct {
Config config.Config
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
}
}

View File

@@ -0,0 +1,11 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.9.1
package types
type PingReq struct {
}
type PingResp struct {
Msg string `json:"message"`
}

View File

@@ -0,0 +1,34 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package main
import (
"flag"
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/config"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/handler"
"github.com/JACKYMYPERSON/hldrCenter/internal/ping/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
)
var configFile = flag.String("f", "etc/ping-api.yaml", "the config file")
func main() {
flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

View File

@@ -0,0 +1,24 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package upload
import (
"net/http"
"github.com/JACKYMYPERSON/hldrCenter/internal/upload/internal/logic/upload"
"github.com/JACKYMYPERSON/hldrCenter/internal/upload/internal/svc"
"github.com/zeromicro/go-zero/rest/httpx"
)
func UploadImageHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := upload.NewUploadImageLogic(r.Context(), svcCtx)
resp, err := l.UploadImage()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@@ -0,0 +1,132 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.1
package upload
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/JACKYMYPERSON/hldrCenter/internal/upload/internal/svc"
"github.com/JACKYMYPERSON/hldrCenter/internal/upload/internal/types"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/gin-gonic/gin"
"github.com/zeromicro/go-zero/core/logx"
)
type UploadImageLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUploadImageLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UploadImageLogic {
return &UploadImageLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UploadImageLogic) UploadImage() (resp *types.UploadImageResp, err error) {
// 获取上传的图片文件
fileHeader, err := c.FormFile("image")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "获取图片失败,请重新上传",
"error": err.Error(),
})
return
}
// 打开文件
file, err := fileHeader.Open()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "打开图片文件失败",
"error": err.Error(),
})
return
}
defer file.Close()
// 检查文件大小使用配置中的max_file_size
if fileHeader.Size > cfg.Upload.MaxFileSize {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": fmt.Sprintf("图片大小不能超过 %dMB", cfg.Upload.MaxFileSize>>20),
})
return
}
// 检查文件类型使用配置中的allow_image_types
fileType := fileHeader.Header.Get("Content-Type")
if !strings.Contains(cfg.Upload.AllowImageTypes, fileType) {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": fmt.Sprintf("不支持的图片类型,仅允许:%s", cfg.Upload.AllowImageTypes),
})
return
}
// 初始化OSS客户端使用配置中的OSS参数
client, err := oss.New(
cfg.OSS.Endpoint,
cfg.OSS.AccessKeyID,
cfg.OSS.AccessKeySecret,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "初始化OSS客户端失败",
"error": err.Error(),
})
return
}
// 获取Bucket
bucket, err := client.Bucket(cfg.OSS.BucketName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "获取Bucket失败",
"error": err.Error(),
})
return
}
// 生成唯一文件名
timestamp := time.Now().Format("20060102150405")
filename := fmt.Sprintf("%s_%s", timestamp, fileHeader.Filename)
objectKey := cfg.OSS.ObjectPrefix + filename // OSS中的完整对象键
// 上传文件到OSS
err = bucket.PutObject(objectKey, file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "上传图片到OSS失败",
"error": err.Error(),
})
return
}
// 生成图片访问URL
host := strings.TrimPrefix(cfg.OSS.Endpoint, "https://")
imageURL := fmt.Sprintf("https://%s.%s/%s", cfg.OSS.BucketName, host, objectKey)
// 返回成功响应
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "图片上传成功",
"data": gin.H{"url": imageURL},
})
return
}

View File

@@ -0,0 +1,12 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.9.1
package types
type UploadImageResp struct {
Code int `json:"code"` // 业务状态码
Message string `json:"message"` // 提示信息
Data struct {
Url string `json:"url"` // 图片访问地址
} `json:"data"`
}

View File

@@ -3,9 +3,9 @@ package main
import (
"fmt"
"github.com/JACKYMYPERSON/hldrCenter/config" // 替换为你的项目模块名
"github.com/JACKYMYPERSON/hldrCenter/middleware" // 替换为你的项目模块名
"github.com/JACKYMYPERSON/hldrCenter/router" // 替换为你的项目模块名
"github.com/JACKYMYPERSON/hldrCenter/config"
"github.com/JACKYMYPERSON/hldrCenter/middleware"
"github.com/JACKYMYPERSON/hldrCenter/router"
)
func main() {

View File

@@ -29,12 +29,6 @@ func CorsMiddleware(serverConfig *config.ServerConfig) gin.HandlerFunc {
}
}
// 4. 即使没有匹配到,也可以临时设置为*(仅测试用,生产环境需删除)
// if allowOrigin == "" {
// allowOrigin = "*"
// }
// 5. 设置核心跨域头
if allowOrigin != "" {
c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin)
}

View File

@@ -2,7 +2,9 @@ package router
import (
"github.com/JACKYMYPERSON/hldrCenter/config"
handler "github.com/JACKYMYPERSON/hldrCenter/internal/handler/uploadimg"
"github.com/JACKYMYPERSON/hldrCenter/internal/handler/article"
ping "github.com/JACKYMYPERSON/hldrCenter/internal/handler/ping"
uploadimg "github.com/JACKYMYPERSON/hldrCenter/internal/handler/uploadimg"
"github.com/JACKYMYPERSON/hldrCenter/middleware"
"github.com/gin-gonic/gin"
)
@@ -17,8 +19,27 @@ func SetupRouter(cfg *config.Config) *gin.Engine {
// 定义路由组(必须在中间件之后)
api := r.Group("/api")
{
api.POST("/upload/image", handler.UploadImageHandler(cfg))
api.POST("/upload/cover", handler.UploadImageHandler(cfg)) // 复用上传逻辑
api.POST("/upload/image", uploadimg.UploadImageHandler(cfg))
api.POST("/upload/cover", uploadimg.UploadImageHandler(cfg))
api.GET("/ping", gin.WrapF(ping.PingHandler(cfg)))
articles := api.Group("/articles")
{
// 1. 创建文章POST /api/articles
articles.POST("", gin.WrapH(article.CreateArticleHandler(svcCtx)))
// 2. 文章列表GET /api/articles
articles.GET("", gin.WrapH(article.ListArticleHandler(svcCtx)))
// 3. 文章详情GET /api/articles/:id
articles.GET("/:id", gin.WrapH(article.DetailArticleHandler(svcCtx)))
// 4. 更新文章PUT /api/articles/:id
articles.PUT("/:id", gin.WrapH(article.UpdateArticleHandler(svcCtx)))
// 5. 删除文章DELETE /api/articles/:id
articles.DELETE("/:id", gin.WrapH(article.DeleteArticleHandler(svcCtx)))
}
}
return r