固定文章编码
This commit is contained in:
23
README.md
23
README.md
@@ -1,10 +1,11 @@
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
长短效合并
|
清除代理的并发问题,redis 归还后,还有可能 gost 没有完成解除占用,导致端口冲突,考虑先等几秒再释放 redis
|
||||||
|
|
||||||
现在我需要提供一套首页接口,用来展示一些统计信息。检查 [README.md](README.md) 的后台首页接口部分,确保你正确理解了每个接口的含义,如何进行统计。如果有不清除的接口,先提出问题,我会补充相关信息。在没有问题后,给出实施计划
|
现在我需要提供一套首页接口,用来展示一些统计信息。检查 [README.md](README.md) 的后台首页接口部分,确保你正确理解了每个接口的含义,如何进行统计。如果有不清除的接口,先提出问题,我会补充相关信息。在没有问题后,给出实施计划
|
||||||
|
|
||||||
后台首页接口:
|
后台首页接口:
|
||||||
|
|
||||||
- 待认领客户数量
|
- 待认领客户数量
|
||||||
- 活跃代理数
|
- 活跃代理数
|
||||||
- 今日交易额
|
- 今日交易额
|
||||||
@@ -53,20 +54,24 @@
|
|||||||
1. 创建 model 文件
|
1. 创建 model 文件
|
||||||
2. 将 model 按照格式添加声明到 `cmd/gen/main.go` 中
|
2. 将 model 按照格式添加声明到 `cmd/gen/main.go` 中
|
||||||
3. 编辑 `scripts/sql/init.sql` 文件,参照原有格式,需要注意:
|
3. 编辑 `scripts/sql/init.sql` 文件,参照原有格式,需要注意:
|
||||||
- 先写 drop table if exists 语句,确保脚本可以幂等执行
|
|
||||||
- 为有必要的字段添加索引
|
- 先写 drop table if exists 语句,确保脚本可以幂等执行
|
||||||
- 为数据表及其字段添加注释
|
- 为有必要的字段添加索引
|
||||||
- 在文件末尾创建数据表流程全部结束后,为字段添加外键
|
- 为数据表及其字段添加注释
|
||||||
|
- 在文件末尾创建数据表流程全部结束后,为字段添加外键
|
||||||
|
|
||||||
4. 调用 `go run ./cmd/gen/main.go` 生成查询文件
|
4. 调用 `go run ./cmd/gen/main.go` 生成查询文件
|
||||||
|
|
||||||
### 更新数据表流程
|
### 更新数据表流程
|
||||||
|
|
||||||
1. 更新 model 文件
|
1. 更新 model 文件
|
||||||
2. 编辑 `scripts/sql/init.sql` 文件,参照原有格式,需要注意:
|
2. 编辑 `scripts/sql/init.sql` 文件,参照原有格式,需要注意:
|
||||||
- 先写 drop table if exists 语句,确保脚本可以幂等执行
|
|
||||||
- 为有必要的字段添加索引
|
- 先写 drop table if exists 语句,确保脚本可以幂等执行
|
||||||
- 为数据表及其字段添加注释
|
- 为有必要的字段添加索引
|
||||||
- 在文件末尾创建数据表流程全部结束后,为字段添加外键
|
- 为数据表及其字段添加注释
|
||||||
|
- 在文件末尾创建数据表流程全部结束后,为字段添加外键
|
||||||
|
|
||||||
3. 调用 `go run ./cmd/gen/main.go` 更新查询文件
|
3. 调用 `go run ./cmd/gen/main.go` 更新查询文件
|
||||||
|
|
||||||
### 新增接口或修改接口权限
|
### 新增接口或修改接口权限
|
||||||
|
|||||||
@@ -1944,17 +1944,39 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
keyword:
|
keyword:
|
||||||
type: string
|
type: string
|
||||||
|
description: 按文章标题或编码模糊查询
|
||||||
group_id:
|
group_id:
|
||||||
type: integer
|
type: integer
|
||||||
status:
|
status:
|
||||||
type: integer
|
type: integer
|
||||||
enum: [0, 1]
|
enum: [0, 1]
|
||||||
|
|
||||||
|
GetArticleRequest:
|
||||||
|
type: object
|
||||||
|
description: `id` 与 `code` 必须且只能提供一个。按 `id` 查询用于兼容旧文章链接,按 `code` 查询用于固定引用链接。
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 64
|
||||||
|
pattern: "^[a-z0-9][a-z0-9_-]{0,63}$"
|
||||||
|
oneOf:
|
||||||
|
- required: [id]
|
||||||
|
- required: [code]
|
||||||
|
|
||||||
CreateArticleRequest:
|
CreateArticleRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
group_id:
|
group_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 可选的文章编码,用于固定引用链接;在未删除文章中唯一
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 64
|
||||||
|
pattern: "^[a-z0-9][a-z0-9_-]{0,63}$"
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
content:
|
content:
|
||||||
@@ -1975,6 +1997,12 @@ components:
|
|||||||
type: integer
|
type: integer
|
||||||
group_id:
|
group_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 可修改的文章编码;在未删除文章中唯一
|
||||||
|
minLength: 1
|
||||||
|
maxLength: 64
|
||||||
|
pattern: "^[a-z0-9][a-z0-9_-]{0,63}$"
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
content:
|
content:
|
||||||
@@ -2057,6 +2085,9 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 旧文章可能没有编码
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
updated_at:
|
updated_at:
|
||||||
@@ -2105,6 +2136,9 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 旧文章可能没有编码
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
content:
|
content:
|
||||||
@@ -2468,6 +2502,9 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
group_id:
|
group_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 旧文章可能没有编码
|
||||||
title:
|
title:
|
||||||
type: string
|
type: string
|
||||||
sort:
|
sort:
|
||||||
@@ -3834,7 +3871,7 @@ paths:
|
|||||||
post:
|
post:
|
||||||
tags: [client/article]
|
tags: [client/article]
|
||||||
summary: 获取文档详情
|
summary: 获取文档详情
|
||||||
description: 仅官方客户端可调用。
|
description: 仅官方客户端可调用。支持按文章 ID 或编码查询,二者必须且只能提供一个。
|
||||||
security:
|
security:
|
||||||
- basicAuth: []
|
- basicAuth: []
|
||||||
- bearerAuth: []
|
- bearerAuth: []
|
||||||
@@ -3843,7 +3880,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/IdRequest"
|
$ref: "#/components/schemas/GetArticleRequest"
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: 文档详情
|
description: 文档详情
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ drop table if exists article cascade;
|
|||||||
create table article (
|
create table article (
|
||||||
id int generated by default as identity primary key,
|
id int generated by default as identity primary key,
|
||||||
group_id int not null,
|
group_id int not null,
|
||||||
|
code text,
|
||||||
title text not null,
|
title text not null,
|
||||||
content text,
|
content text,
|
||||||
sort int not null default 0,
|
sort int not null default 0,
|
||||||
@@ -183,6 +184,7 @@ create table article (
|
|||||||
updated_at timestamptz default current_timestamp,
|
updated_at timestamptz default current_timestamp,
|
||||||
deleted_at timestamptz
|
deleted_at timestamptz
|
||||||
);
|
);
|
||||||
|
create unique index udx_article_code on article (code) where code is not null and deleted_at is null;
|
||||||
create index idx_article_group_id on article (group_id) where deleted_at is null;
|
create index idx_article_group_id on article (group_id) where deleted_at is null;
|
||||||
create index idx_article_status on article (status) where deleted_at is null;
|
create index idx_article_status on article (status) where deleted_at is null;
|
||||||
create index idx_article_sort on article (sort) where deleted_at is null;
|
create index idx_article_sort on article (sort) where deleted_at is null;
|
||||||
@@ -191,6 +193,7 @@ create index idx_article_sort on article (sort) where deleted_at is null;
|
|||||||
comment on table article is '文章表';
|
comment on table article is '文章表';
|
||||||
comment on column article.id is '文章ID';
|
comment on column article.id is '文章ID';
|
||||||
comment on column article.group_id is '分组ID';
|
comment on column article.group_id is '分组ID';
|
||||||
|
comment on column article.code is '文章编码,用于固定引用链接';
|
||||||
comment on column article.title is '文章标题';
|
comment on column article.title is '文章标题';
|
||||||
comment on column article.content is '文章内容';
|
comment on column article.content is '文章内容';
|
||||||
comment on column article.sort is '文章排序';
|
comment on column article.sort is '文章排序';
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ func GetArticle(c *fiber.Ctx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var req core.IdReq
|
var req s.GetArticleReq
|
||||||
if err := g.Validator.ParseBody(c, &req); err != nil {
|
if err := g.Validator.ParseBody(c, &req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
article, err := s.Article.GetPublic(req.Id)
|
article, err := s.Article.GetPublic(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "platform/web/core"
|
|||||||
type Article struct {
|
type Article struct {
|
||||||
core.Model
|
core.Model
|
||||||
GroupID int32 `json:"group_id" gorm:"column:group_id"` // 分组ID
|
GroupID int32 `json:"group_id" gorm:"column:group_id"` // 分组ID
|
||||||
|
Code *string `json:"code,omitempty" gorm:"column:code"` // 文章编码,用于固定引用链接
|
||||||
Title string `json:"title" gorm:"column:title"` // 文章标题
|
Title string `json:"title" gorm:"column:title"` // 文章标题
|
||||||
Content *string `json:"content,omitempty" gorm:"column:content"` // 文章内容
|
Content *string `json:"content,omitempty" gorm:"column:content"` // 文章内容
|
||||||
Sort int32 `json:"sort" gorm:"column:sort"` // 文章排序
|
Sort int32 `json:"sort" gorm:"column:sort"` // 文章排序
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ func newArticle(db *gorm.DB, opts ...gen.DOOption) article {
|
|||||||
_article.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_article.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_article.DeletedAt = field.NewField(tableName, "deleted_at")
|
_article.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
_article.GroupID = field.NewInt32(tableName, "group_id")
|
_article.GroupID = field.NewInt32(tableName, "group_id")
|
||||||
|
_article.Code = field.NewString(tableName, "code")
|
||||||
_article.Title = field.NewString(tableName, "title")
|
_article.Title = field.NewString(tableName, "title")
|
||||||
_article.Content = field.NewString(tableName, "content")
|
_article.Content = field.NewString(tableName, "content")
|
||||||
_article.Sort = field.NewInt32(tableName, "sort")
|
_article.Sort = field.NewInt32(tableName, "sort")
|
||||||
@@ -56,6 +57,7 @@ type article struct {
|
|||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
DeletedAt field.Field
|
DeletedAt field.Field
|
||||||
GroupID field.Int32
|
GroupID field.Int32
|
||||||
|
Code field.String
|
||||||
Title field.String
|
Title field.String
|
||||||
Content field.String
|
Content field.String
|
||||||
Sort field.Int32
|
Sort field.Int32
|
||||||
@@ -82,6 +84,7 @@ func (a *article) updateTableName(table string) *article {
|
|||||||
a.UpdatedAt = field.NewTime(table, "updated_at")
|
a.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
a.DeletedAt = field.NewField(table, "deleted_at")
|
a.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
a.GroupID = field.NewInt32(table, "group_id")
|
a.GroupID = field.NewInt32(table, "group_id")
|
||||||
|
a.Code = field.NewString(table, "code")
|
||||||
a.Title = field.NewString(table, "title")
|
a.Title = field.NewString(table, "title")
|
||||||
a.Content = field.NewString(table, "content")
|
a.Content = field.NewString(table, "content")
|
||||||
a.Sort = field.NewInt32(table, "sort")
|
a.Sort = field.NewInt32(table, "sort")
|
||||||
@@ -102,12 +105,13 @@ func (a *article) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *article) fillFieldMap() {
|
func (a *article) fillFieldMap() {
|
||||||
a.fieldMap = make(map[string]field.Expr, 10)
|
a.fieldMap = make(map[string]field.Expr, 11)
|
||||||
a.fieldMap["id"] = a.ID
|
a.fieldMap["id"] = a.ID
|
||||||
a.fieldMap["created_at"] = a.CreatedAt
|
a.fieldMap["created_at"] = a.CreatedAt
|
||||||
a.fieldMap["updated_at"] = a.UpdatedAt
|
a.fieldMap["updated_at"] = a.UpdatedAt
|
||||||
a.fieldMap["deleted_at"] = a.DeletedAt
|
a.fieldMap["deleted_at"] = a.DeletedAt
|
||||||
a.fieldMap["group_id"] = a.GroupID
|
a.fieldMap["group_id"] = a.GroupID
|
||||||
|
a.fieldMap["code"] = a.Code
|
||||||
a.fieldMap["title"] = a.Title
|
a.fieldMap["title"] = a.Title
|
||||||
a.fieldMap["content"] = a.Content
|
a.fieldMap["content"] = a.Content
|
||||||
a.fieldMap["sort"] = a.Sort
|
a.fieldMap["sort"] = a.Sort
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"platform/web/core"
|
"platform/web/core"
|
||||||
m "platform/web/models"
|
m "platform/web/models"
|
||||||
q "platform/web/queries"
|
q "platform/web/queries"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -33,6 +34,8 @@ var articleUploadMimeExt = map[string]string{
|
|||||||
"image/webp": ".webp",
|
"image/webp": ".webp",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var articleCodePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,63}$`)
|
||||||
|
|
||||||
type ArticleUploadResult struct {
|
type ArticleUploadResult struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
@@ -151,7 +154,13 @@ func formatUploadSizeLimit(bytes int) string {
|
|||||||
func (s *articleService) Page(req *PageArticleReq) (result []*m.Article, count int64, err error) {
|
func (s *articleService) Page(req *PageArticleReq) (result []*m.Article, count int64, err error) {
|
||||||
do := q.Article.Where()
|
do := q.Article.Where()
|
||||||
if req.Keyword != nil && *req.Keyword != "" {
|
if req.Keyword != nil && *req.Keyword != "" {
|
||||||
do = do.Where(q.Article.Title.Like("%" + *req.Keyword + "%"))
|
do = do.Where(
|
||||||
|
q.Article.Where(
|
||||||
|
q.Article.Title.Like("%" + *req.Keyword + "%"),
|
||||||
|
).Or(
|
||||||
|
q.Article.Code.Like("%" + *req.Keyword + "%"),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if req.GroupID != nil {
|
if req.GroupID != nil {
|
||||||
do = do.Where(q.Article.GroupID.Eq(*req.GroupID))
|
do = do.Where(q.Article.GroupID.Eq(*req.GroupID))
|
||||||
@@ -194,17 +203,28 @@ func (s *articleService) Create(data CreateArticleData) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return q.Article.Create(&m.Article{
|
code, err := optionalArticleCode(data.Code)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.Article.Create(&m.Article{
|
||||||
GroupID: data.GroupID,
|
GroupID: data.GroupID,
|
||||||
|
Code: code,
|
||||||
Title: data.Title,
|
Title: data.Title,
|
||||||
Content: data.Content,
|
Content: data.Content,
|
||||||
Sort: u.Else(data.Sort, 0),
|
Sort: u.Else(data.Sort, 0),
|
||||||
Status: u.Else(data.Status, m.ArticleStatusEnabled),
|
Status: u.Else(data.Status, m.ArticleStatusEnabled),
|
||||||
})
|
})
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return core.NewBizErr("文档编码已存在")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateArticleData struct {
|
type CreateArticleData struct {
|
||||||
GroupID int32 `json:"group_id" validate:"required"`
|
GroupID int32 `json:"group_id" validate:"required"`
|
||||||
|
Code *string `json:"code"`
|
||||||
Title string `json:"title" validate:"required"`
|
Title string `json:"title" validate:"required"`
|
||||||
Content *string `json:"content"`
|
Content *string `json:"content"`
|
||||||
Sort *int32 `json:"sort"`
|
Sort *int32 `json:"sort"`
|
||||||
@@ -222,6 +242,13 @@ func (s *articleService) Update(data UpdateArticleData) error {
|
|||||||
if data.GroupID != nil {
|
if data.GroupID != nil {
|
||||||
do = append(do, q.Article.GroupID.Value(*data.GroupID))
|
do = append(do, q.Article.GroupID.Value(*data.GroupID))
|
||||||
}
|
}
|
||||||
|
if data.Code != nil {
|
||||||
|
code, err := normalizeArticleCode(*data.Code)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
do = append(do, q.Article.Code.Value(code))
|
||||||
|
}
|
||||||
if data.Title != nil {
|
if data.Title != nil {
|
||||||
do = append(do, q.Article.Title.Value(*data.Title))
|
do = append(do, q.Article.Title.Value(*data.Title))
|
||||||
}
|
}
|
||||||
@@ -239,6 +266,9 @@ func (s *articleService) Update(data UpdateArticleData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r, err := q.Article.Where(q.Article.ID.Eq(data.ID)).UpdateSimple(do...)
|
r, err := q.Article.Where(q.Article.ID.Eq(data.ID)).UpdateSimple(do...)
|
||||||
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
|
return core.NewBizErr("文档编码已存在")
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -251,6 +281,7 @@ func (s *articleService) Update(data UpdateArticleData) error {
|
|||||||
type UpdateArticleData struct {
|
type UpdateArticleData struct {
|
||||||
ID int32 `json:"id" validate:"required"`
|
ID int32 `json:"id" validate:"required"`
|
||||||
GroupID *int32 `json:"group_id"`
|
GroupID *int32 `json:"group_id"`
|
||||||
|
Code *string `json:"code"`
|
||||||
Title *string `json:"title"`
|
Title *string `json:"title"`
|
||||||
Content *string `json:"content"`
|
Content *string `json:"content"`
|
||||||
Sort *int32 `json:"sort"`
|
Sort *int32 `json:"sort"`
|
||||||
@@ -314,6 +345,7 @@ func (s *articleService) Nav() ([]*ArticleNavGroup, error) {
|
|||||||
}
|
}
|
||||||
group.Articles = append(group.Articles, &ArticleNavArticle{
|
group.Articles = append(group.Articles, &ArticleNavArticle{
|
||||||
ID: article.ID,
|
ID: article.ID,
|
||||||
|
Code: article.Code,
|
||||||
Title: article.Title,
|
Title: article.Title,
|
||||||
UpdatedAt: article.UpdatedAt,
|
UpdatedAt: article.UpdatedAt,
|
||||||
})
|
})
|
||||||
@@ -331,18 +363,40 @@ type ArticleNavGroup struct {
|
|||||||
|
|
||||||
type ArticleNavArticle struct {
|
type ArticleNavArticle struct {
|
||||||
ID int32 `json:"id"`
|
ID int32 `json:"id"`
|
||||||
|
Code *string `json:"code,omitempty"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *articleService) GetPublic(id int32) (*ArticlePublicDetail, error) {
|
type GetArticleReq struct {
|
||||||
article, err := q.Article.
|
ID *int32 `json:"id"`
|
||||||
|
Code *string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *articleService) GetPublic(req GetArticleReq) (*ArticlePublicDetail, error) {
|
||||||
|
do := q.Article.
|
||||||
Preload(q.Article.Group).
|
Preload(q.Article.Group).
|
||||||
Where(
|
Where(q.Article.Status.Eq(int(m.ArticleStatusEnabled)))
|
||||||
q.Article.ID.Eq(id),
|
|
||||||
q.Article.Status.Eq(int(m.ArticleStatusEnabled)),
|
switch {
|
||||||
).
|
case req.ID != nil && req.Code != nil:
|
||||||
Take()
|
return nil, core.NewBizErr("文档 ID 和编码不能同时提供")
|
||||||
|
case req.ID != nil:
|
||||||
|
if *req.ID <= 0 {
|
||||||
|
return nil, core.NewBizErr("文档 ID 不正确")
|
||||||
|
}
|
||||||
|
do = do.Where(q.Article.ID.Eq(*req.ID))
|
||||||
|
case req.Code != nil:
|
||||||
|
code, err := normalizeArticleCode(*req.Code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
do = do.Where(q.Article.Code.Eq(code))
|
||||||
|
default:
|
||||||
|
return nil, core.NewBizErr("必须提供文档 ID 或编码")
|
||||||
|
}
|
||||||
|
|
||||||
|
article, err := do.Take()
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, core.NewBizErr("文档不存在")
|
return nil, core.NewBizErr("文档不存在")
|
||||||
}
|
}
|
||||||
@@ -355,6 +409,7 @@ func (s *articleService) GetPublic(id int32) (*ArticlePublicDetail, error) {
|
|||||||
|
|
||||||
return &ArticlePublicDetail{
|
return &ArticlePublicDetail{
|
||||||
ID: article.ID,
|
ID: article.ID,
|
||||||
|
Code: article.Code,
|
||||||
Title: article.Title,
|
Title: article.Title,
|
||||||
Content: article.Content,
|
Content: article.Content,
|
||||||
UpdatedAt: article.UpdatedAt,
|
UpdatedAt: article.UpdatedAt,
|
||||||
@@ -368,6 +423,7 @@ func (s *articleService) GetPublic(id int32) (*ArticlePublicDetail, error) {
|
|||||||
|
|
||||||
type ArticlePublicDetail struct {
|
type ArticlePublicDetail struct {
|
||||||
ID int32 `json:"id"`
|
ID int32 `json:"id"`
|
||||||
|
Code *string `json:"code,omitempty"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Content *string `json:"content,omitempty"`
|
Content *string `json:"content,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
@@ -387,3 +443,22 @@ func (s *articleService) ensureGroupExists(groupID int32) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func optionalArticleCode(code *string) (*string, error) {
|
||||||
|
if code == nil || strings.TrimSpace(*code) == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
normalized, err := normalizeArticleCode(*code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &normalized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeArticleCode(code string) (string, error) {
|
||||||
|
normalized := strings.TrimSpace(code)
|
||||||
|
if !articleCodePattern.MatchString(normalized) {
|
||||||
|
return "", core.NewBizErr("文档编码只能包含小写字母、数字、下划线或连字符,长度为 1-64 位")
|
||||||
|
}
|
||||||
|
return normalized, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user